I am having a strange problem with Entity Framework and SQL Server that I cannot figure out.
I am building an online store in ASP.NET MVC 5.
I am inserting statistics about a search into a table called SearchResults - it has this structure:
[SearchID] [int] IDENTITY(1,1) NOT NULL,
[SearchTerm] [varchar](5000) NULL,
[SearchDate] [datetime] NULL,
[Results] [int] NULL
I am just doing a simple EF insert with this C# code in the Search action of a controller, which gets posted to with a search term:
var s = new SearchResult() { SearchTerm = search, SearchDate = DateTime.Now, Results = results };
db.SearchResults.Add(s);
db.SaveChanges();
Results is an int with the count of the products found by the search.
Whenever I do a search, the same search gets inserted exactly 3 times, with slightly different times for each insert. The weird part is that occasionally, it will only insert one entry (as expected). I can't figure out why it is doing this.
I've run an SQL trace, and when it inserts 3, there is only one call to the DB. This is what is in the trace:
exec sp_executesql N'INSERT [dbo].[SearchResults]([SearchTerm],[SearchDate], [Results])
VALUES (#0, #1, #2)
SELECT [SearchID]
FROM [dbo].[SearchResults]
WHERE ##ROWCOUNT > 0 AND [SearchID] = scope_identity()',N'#0 varchar(5000),#1 datetime2(7),#2 int',#0='dew',#1='2015-02-16 16:32:53.4649185',#2=2
The weird part is the datetime shown in the insert is the value for the third insert.
I am at a complete loss for why this is happening. I've tried everything I can think of, but I am still getting repeats on insert.
Maybe you're looking at the wrong piece of code.
Had you logged or debugged the calls to the controller, how many times the first snippet of code you posted get executed?
Related
I have a table like this:
[Id] [INT] IDENTITY(1,1) NOT NULL,
..
..
[ParentId] [INT] NULL,
[CreatedOn] [DATETIME] NOT NULL,
[UpdatedOn] [DATETIME] NOT NULL
In some case I want to update the ParentId with the Id of the table like this:
_dbContext.Add(data);
if (true)
{
data.ParentId = data.Id;
}
_dbContext.Update(data);
await _dbContext.SaveChangesAsync();
When doing so, I am getting this error:
The property 'Id' on entity type 'Data' has a temporary value while
attempting to change the entity's state to 'Modified'. Either set a
permanent value explicitly or ensure that the database is configured
to generate values for this property.
Is it possible what I am trying to do or I need to first call the SaveChangesAsync() before update?
The error you're getting
Entity Framework has something called a change tracker. This performs a few duties, I'll mention the ones relevant for this answer:
It keeps track of what needs to happen when you call SaveChanges()
It keeps tabs on all attached entities
When you call SaveChanges(), EF might need to INSERT some entities, and it needs to UPDATE some others (I'm ignoring other operations as they are irrelevant here. To keep track of this, EF's change tracker attached a particular enum called EntityState to your entity. Based on the method you call, the enum value gets set.
_dbContext.Add(data);
var the_enum_value = _dbContext.Entry(data).State;
You will see that the_enum_value equals EntityState.Added. This means that when you call SaveChanges(), an INSERT statement will be generated.
_dbContext.Update(data); //let's assume this is an existing entity you fetched
var the_enum_value = _dbContext.Entry(data).State;
Here, you will see that the_enum_value equals EntityState.Modified. This means that when you call SaveChanges(), an UPDATE statement will be generated.
You added a new entity, so you clearly want to INSERT it to the database. But by calling Update on this entity, you would change the enum value to EntityState.Modified, therefore causing an UPDATE statement to be generated, even though there is no existing row in the database yet that you need to update.
In the past, EF just changed the enum, tried anyway, and then you'd be scratching your head as to why your changes weren't making it to the database.
Nowadays, EF is smart enough to realize that if the entity doesn't have an actual ID value yet, that setting the enum to EntityState.Modified is going to be a bad idea, so it alerts you that you're trying to do something that won't work.
The general solution here is that you simply didn't need to call Update. You could do this:
var data = new Data() { Name = "Foo" };
_dbContext.Add(data);
data.Name = "Bar";
await _dbContext.SaveChangesAsync();
You will see that the name hitting the database is "Bar", not "Foo". Because EF only generates the INSERT statement when you call SaveChanges, not when you call Add; therefore any changes made before calling SaveChanges are still "seen" by the INSERT statement being generated.
What you're attempting
HOWEVER, you're in a particularly special case because you're trying to access and use the ID property of the to-be-created entity. That value does not yet exist.
EF does its best to ignore this and make it work for you behind the scenes. When you call SaveChanges(), EF will find out what the generated ID is and will silently fill it in for you so you can keep using your data variable without needing to worry.
But I very much doubt that EF is going to be able to realize that data.ParentId needs the same treatment.
Do I need to first call the SaveChangesAsync() before update?
In this very specific case, most likely yes. However, this is because you're trying to use the data.Id value, and this is unrelated to the error message you reported.
I am facing this weird problem and spent several hours. Little help would be greatly appreciated.
This is an ASP.NET MVC app. For simplicity, I have two SQL tables, Employee (ID, Name, JoiningDate) and Benefits (ID, EmployeeID). Both IDs are identity colums. When a new employee joins the company, an entry is created in the Employee table as well as Benefits table.
The stored procedure looks like this
alter procedure usp_CreateEmployee
#Name nvarchar(100),
#JoiningDate datetime
as
declare #lastIdentity int
insert into Employee(Name, JoiningDate) values(#Name, #JoiningDate)
select #lastIdentity = ident_current('Employee')
insert into Benefits(EmployeeID) values(#lastIdentity)
C# side I am using Dapper
var parameters = new DynamicParameters();
parameters.Add("#Name", name);
parameters.Add("#JoiningDate", joiningDate);
affectedRows = connection.Execute("usp_CreateEmployee", parameters, null, commandType: CommandType.StoredProcedure);
When I execute the stored procedure in SSMS, everything works perfect. (ident_current returns the last inserted id). However, when the user interface creates employee (through razor page), a NULL gets inserted as EmployeeID in Benefits table. Employee table shows correct Employee ID.
Doesn't look like a SQL problem. Is there anything wrong with my code? Could this be Dapper related (though I dont think so)?
I think the problem was on the "ident_current". Please refer here: https://sqlperformance.com/2014/01/t-sql-queries/ident-current
alternatively, you may try below sql script.
alter procedure usp_CreateEmployee
#Name nvarchar(100),
#JoiningDate datetime
as
declare #lastIdentity int
insert into Employee(Name, JoiningDate) values(#Name, #JoiningDate)
select top(1) #lastIdentity=ID from Employee where Name=#Name
order by ID desc
insert into Benefits(EmployeeID) values(#lastIdentity)
we are having issues with a stored procedure. When calling it from our website via asp/vb.net it seems to not be executing properly. If I run it from SSMS it works.
I have run the debugger when the call is being placed, the parameters being passed in are correct at the time of the ExecuteNonQuery() call but it is not generating any records in the related tables like it should. If I use the same values seen while debugging our website directly in SSMS, the stored procedure creates the expected records.
Here is our stored procedure:
ALTER PROCEDURE [dbo].[CopyGoals](
#OldVisitID int,
#NewVisitID int,
#CreatedBy NVarChar(30)
) AS BEGIN
declare #GoalMapping As Table(OldGoalID int,NewGoalID int);
Merge Into VisitGoals
Using(
select GoalsID,Goal,ProgressNote,Progress,Completed,CreatedOn,CreatedBy,VisitID
From VisitGoals
Where VisitID = #OldVisitID
) As Src
On 1 = 0
When Not Matched By Target Then
Insert (Goal,ProgressNote,Completed,VisitID,Progress, CreatedOn, CreatedBy)
Values (Src.Goal, Src.ProgressNote, Src.Completed, #NewVisitID, Src.Progress, GetDate(), #CreatedBy)
Output Src.GoalsID As OldGoalID, inserted.GoalsID as NewGoalID
Into #GoalMapping;
Insert Into SubGoals(GoalID,VisitID,GoalText,HasCompleted,WillComplete,GoalStatus)
(
Select GM.NewGoalID, #NewVisitID, SG.GoalText, SG.HasCompleted, SG.WillComplete, SG.GoalStatus
From SubGoals As SG inner join #GoalMapping As GM on SG.GoalID = GM.OldGoalID
Where SG.VisitID = #OldVisitID
)
END
Here is the procedure call from our website page:
Dim conStr As String = ConfigurationManager.ConnectionStrings("ConnectionString").ConnectionString
Dim curUsr As New Supervisor(Context.User.Identity.Name, True)
Using con As New SqlConnection(conStr)
Using cmd As New SqlCommand("CopyGoals", con)
cmd.CommandType = CommandType.StoredProcedure
cmd.Parameters.Add("#OldVisitID", SqlDbType.Int).Value = oldVID
cmd.Parameters.Add("#NewVisitID", SqlDbType.Int).Value = newVID
cmd.Parameters.Add("#CreatedBy", SqlDbType.NVarChar, 30).Value = curUsr.Name
con.Open()
cmd.ExecuteNonQuery()
con.Close()
End Using
End Using
What this procedure should do, and does if run from SSMS, is look at our Goals table with the existing IDs matching a foreign key corresponding to the VisitID in a different table to find all goals associated with that old visit.
It them copies the goal information and inserts it into the Goals table, outputting the old goal IDs and the newly inserted goal IDs into the #GoalMapping table.
It then looks into our SubGoals table and does a similar thing by copying each subgoal related to the goals we just copied. For whatever reason, this procedure does not execute properly when called from our page even when it runs in SSMS when we feed it the same input values as seen during debugging of the page. There are no errors reported in chrome's debugger, we tried wrapping execute in a try with an SQL and normal exception but neither of them tripped. We are pretty stumped. Maybe someone will spot something we haven't over the last few days.
Here is the output from Sql Profiler:
Okay, I finally figured it out. There was a permission issue as Mohsin suggested. I logged in as the user that we use for our ASP.net connection strings and attempted to run the query in question and it failed to generate the records from SSMS. So after some digging I found this question:
Stored Procedure and Permissions - Is EXECUTE enough?
Which lead to this question when I got the same error:
SQL Server principal "dbo" does not exist,
Combined together, the answers to these questions helped me fix the issue.
I have a Microsoft Access ADP-type database, linking to a SQL Server 2012 database, which was recently upgraded from Office XP (2002) to Office 2010. The upgrade was mostly successful except for an issue with a Combo Box on a form with an updateable data source - In Access XP/2002, the user could select values from the dropdown list and the value would be updated in the table. However, when the user tries to modify the record using the 'tblL.LMID' Combo Box in Access 2010, an error briefly flashes in the status bar at the bottom of the screen (and the record is not updated):
"Control can't be edited; it's bound to AutoNumber field 'LMID'"
I understand this is normal functionality if the field in question was an Identity column in SQL Server, but that's not the case here. However, the 'tblL.LMID' field does get used as a join in the SQL query behind the scenes.
The data source on the form is as follows:
SELECT dbo.tblLM.OpID, dbo.tblL.*, dbo.tblLM.DR
FROM dbo.tblL INNER JOIN dbo.tblLM ON dbo.tblL.LMID = dbo.tblLM.LMID
WHERE (dbo.tblLM.DR = 1)
ORDER BY dbo.tblL.DS
The tables involved in the query are as follows:
CREATE TABLE [dbo].[tblL](
[LID] [int] IDENTITY(1,1) NOT NULL,
[LMID] [int] NOT NULL,
[DS] [nvarchar](10) NOT NULL)
CREATE TABLE [dbo].[tblLM](
[LMID] [int] IDENTITY(1,1) NOT NULL,
[OpID] [int] NULL,
[DR] [bit] NULL DEFAULT ((1)))
As per the table structure, tblL.LMID is a simple column (not Autonumber/Identity), and we should be able to modify it like we did in the Access XP/2002 version of the application.
I would happily accept any assistance on this issue, much appreciated! :)
The problem was with the query itself. To resolve, we had to replace the "dbo.tblL.*" select with specific column names, as well as giving the problematic column an alias:
SELECT dbo.tblLM.OpID, dbo.tblL.LMID as l_MID, dbo.tblLM.DR
FROM dbo.tblL INNER JOIN dbo.tblLM ON dbo.tblL.LMID = dbo.tblLM.LMID
WHERE (dbo.tblLM.DR = 1)
ORDER BY dbo.tblL.DS
We then updated the combo box to use the new alias ("l_MID"), and then it started working correctly.
Put the form into design view; delete the current combobox. Create a new combobox and follow the wizard.
I have an application (ASP.NET 3.5) that allows users to rerun a particular process if required. The process inserts records into an MS SQL table. I have the insert in a Try / Catch and ignore the catch if a record already exists (the error in the Title would be valid). This worked perfectly using ADO but after I conveted to LINQ I noticed an interesting thing. If on a re-run of the process there was already records in the table, any new records would be rejected with the same error even though there was no existing record.
The code is as follows:
Dim ins = New tblOutstandingCompletion
With ins
.ControlID = rec.ControlID
.PersonID = rec.peopleID
.RequiredDate = rec.NextDue
.RiskNumber = 0
.recordType = "PC"
.TreatmentID = 0
End With
Try
ldb.tblOutstandingCompletions.InsertOnSubmit(ins)
ldb.SubmitChanges()
Catch ex As Exception
' An attempt to load a duplicate record will fail
End Try
The DataContext for database was set during Page Load .
I resolved the problem by redefining the DataContext before each insert:
ldb = New CaRMSDataContext(sessionHandler.connection.ToString)
Dim ins = New tblOutstandingCompletion
While I have solved the problem I would like to know if anyone can explain it. Without the DataContext redefinition the application works perfectly if there are no duplicate records.
Regards
James
It sounds like the DataContext thinks the record was inserted the first time, so if you don't redefine the context, it rejects the second insert because it "knows" the record is already there. Redefining the context forces it to actually check the database to see if it's there, which it isn't. That's LINQ trying to save a round trip to the database. Creating a new context as you've done forces it to reset what it "knows" about the database.
I had seen a very similar issue in my code were the identity column wasn't an autoincrementing int column, but a GUID with a default value of newguid() - basically LINQ wasn't allowing the database to create the GUID, but inserting Guid.Empty instead, and the second (or later) attempts would (correctly) throw this error.
I ended up ensuring that I generated a new GUID myself during the insert. More details can be seen here: http://www.doodle.co.uk/Blogs/2007/09/18/playing-with-linq-in-winforms.aspx
This allowed me to insert multiple records with the same DataContext.
Also, have you tried calling InsertOnSubmit multiple times (once for each new record) but only calling SubmitChanges once?
gfrizzle seems to be right here...
My code fails with the duplicate key error even though I've just run a stored proc to truncate the table on the database. As far as the data context knows, the previous insertion of a record with the same key is in fact a duplicate key, and an exception is thrown.
The only way that I've found around this is:
db = null;
db = new NNetDataContext();
right after the SubmitChanges() call that executes the previous InsertOnSubmit requests. Seems kind of dumb, but it's the only way that works for me other than redesigning the code.