Closed. This question needs debugging details. It is not currently accepting answers.
Edit the question to include desired behavior, a specific problem or error, and the shortest code necessary to reproduce the problem. This will help others answer the question.
Closed 4 years ago.
The community reviewed whether to reopen this question 1 year ago and left it closed:
Original close reason(s) were not resolved
Improve this question
I am using Entity Framework to populate a grid control. Sometimes when I make updates I get the following error:
Store update, insert, or delete statement affected an unexpected number of rows (0). Entities may have been modified or deleted since entities were loaded. Refresh ObjectStateManager entries.
I can't figure out how to reproduce this. But it might have something to do with how close together I make the updates. Has anyone seen this or does anyone know what the error message refers to?
Edit: Unfortunately I am no longer at liberty to reproduce the problem I was having here, because I stepped away from this project and don't remember if I eventually found a solution, if another developer fixed it, or if I worked around it. Therefore I cannot accept any answers.
I ran into this and it was caused by the entity's ID (key) field not being set. Thus when the context went to save the data, it could not find an ID = 0. Be sure to place a break point in your update statement and verify that the entity's ID has been set.
From Paul Bellora's comment
I had this exact issue, caused by forgetting to include the hidden ID
input in the .cshtml edit page
That's a side-effect of a feature called optimistic concurrency.
Not 100% sure how to turn it on/off in Entity Framework but basically what it's telling you is that between when you grabbed the data out of the database and when you saved your changes someone else has changed the data (Which meant when you went to save it 0 rows actually got updated). In SQL terms, their update query's where clause contains the original value of every field in the row, and if 0 rows are affected it knows something's gone wrong.
The idea behind it is that you won't end up overwriting a change that your application didn't know has happened - it's basically a little safety measure thrown in by .NET on all your updates.
If it's consistent, odds are it's happening within your own logic (EG: You're actually updating the data yourself in another method in-between the select and the update), but it could be simply a race condition between two applications.
Wow, lots of answers, but I got this error when I did something slightly different that no on else has mentioned.
Long story short, if you create a new object and tell EF that its modified using the EntityState.Modified then it will throw this error as it doesn't yet exist in the database. Here is my code:
MyObject foo = new MyObject()
{
someAttribute = someValue
};
context.Entry(foo).State = EntityState.Modified;
context.SaveChanges();
Yes, this seems daft, but it arose because the method in question used to have foo passed to it having been created earlier on, now it only has someValue passed to it and creates foo itself.
Easy fix, just change EntityState.Modified to EntityState.Added or change that whole line to:
context.MyObject.Add(foo);
I was facing this same scaring error... :) Then I realized that I was forgetting to set a
#Html.HiddenFor(model => model.UserProfile.UserId)
for the primary key of the object being updated! I tend to forget this simple, but very important thingy!
By the way: HiddenFor is for ASP.NET MVC.
Check whether you forgot the "DataKeyNames" attribute in the GridView.
it's a must when modifying data within the GridView
http://msdn.microsoft.com/en-us/library/system.web.ui.webcontrols.gridview.datakeynames.aspx
The issue is caused by either one of two things :-
You tried to update a row with one or more properties are Concurrency Mode: Fixed .. and the Optimistic Concurrency prevented the data from being saved. Ie. some changed the row data between the time you received the server data and when you saved your server data.
You tried to update or delete a row but the row doesn't exist. Another example of someone changing the data (in this case, removing) in between a retrieve then save OR you're flat our trying to update a field which is not an Identity (ie. StoreGeneratedPattern = Computed) and that row doesn't exist.
I got this same error because part of the PK was a datetime column, and the record being inserted used DateTime.Now as the value for that column. Entity framework would insert the value with millisecond precision, and then look for the value it just inserted also with millisecond precision. However SqlServer had rounded the value to second precision, and thus entity framework was unable to find the millisecond precision value.
The solution was to truncate the milliseconds from DateTime.Now before inserting.
I was having same problem and #webtrifusion's answer helped find the solution.
My model was using the Bind(Exclude) attribute on the entity's ID which was causing the value of the entity's ID to be zero on HttpPost.
namespace OrderUp.Models
{
[Bind(Exclude = "OrderID")]
public class Order
{
[ScaffoldColumn(false)]
public int OrderID { get; set; }
[ScaffoldColumn(false)]
public System.DateTime OrderDate { get; set; }
[Required(ErrorMessage = "Name is required")]
public string Username { get; set; }
}
}
I had the same problem, I figure out that was caused by the RowVersion which was null.
Check that your Id and your RowVersion are not null.
for more information refer to this tutorial
http://www.asp.net/mvc/tutorials/getting-started-with-ef-using-mvc/handling-concurrency-with-the-entity-framework-in-an-asp-net-mvc-application
I also came across this error. The problem it turned out was caused by a Trigger on the table I was trying to save to. The Trigger used 'INSTEAD OF INSERT' which means 0 rows ever got inserted to that table, hence the error. Luckily in may case the trigger functionality was incorrect, but I guess it could be a valid operation that should somehow be handled in code. Hope this helps somebody one day.
While editing include the id or primary key of the entity as a hidden field in the view
ie
#Html.HiddenFor(m => m.Id)
that solves the problem.
Also if your model includes non-used item include that too and post that to the controller
I started getting this error after changing from model-first to code-first. I have multiple threads updating a database where some might update the same row. I don't know why I didn't have a problem using model-first, assume that it uses a different concurrency default.
To handle it in one place knowing the conditions under which it might occur, I added the following overload to my DbContext class:
using System.Data.Entity.Core.Objects;
using System.Data.Entity.Infrastructure;
public class MyDbContext: DbContext {
...
public int SaveChanges(bool refreshOnConcurrencyException, RefreshMode refreshMode = RefreshMode.ClientWins) {
try {
return SaveChanges();
}
catch (DbUpdateConcurrencyException ex) {
foreach (DbEntityEntry entry in ex.Entries) {
if (refreshMode == RefreshMode.ClientWins)
entry.OriginalValues.SetValues(entry.GetDatabaseValues());
else
entry.Reload();
}
return SaveChanges();
}
}
}
Then called SaveChanges(true) wherever applicable.
The line [DatabaseGenerated(System.ComponentModel.DataAnnotations.DatabaseGeneratedOption.None)] did the trick in my case:
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.None)]
public int? SomeNumber { get; set; }
You need to explicitly include a BoundField of the primary key. If you don't want the user to see the primary key, you have to hide it via css:
<asp:BoundField DataField="Id_primary_key" ItemStyle-CssClass="hidden"
HeaderStyle-CssClass="hidden" />
Where 'hidden' is a class in css that has it's display set to 'none'.
I came across this issue on a table that was missing a primary key and had a DATETIME(2, 3) column (so the entity's "primary key" was a combination of all the columns)... When performing the insert the timestamp had a more precise time (2018-03-20 08:29:51.8319154) that was truncated to (2018-03-20 08:29:51.832) so the lookup on key fields fails.
Just make sure table and form both have primary key and edmx updated.
i found that any errors during update were usually because of:
- No primary key in Table
- No primary key in Edit view/form (e.g. #Html.HiddenFor(m=>m.Id)
I also had this error. There are some situations where the Entity may not be aware of the actual Database Context you are using or the Model may be different. For this, set: EntityState.Modified; to EntityState.Added;
To do this:
if (ModelState.IsValid)
{
context.Entry(yourModelReference).State = EntityState.Added;
context.SaveChanges();
}
This will ensure the Entity knows youre using or adding the State youre working with. At this point all the correct Model Values need to be set. Careful not to loose any changes that may have been made in the background.
Hope this helps.
#Html.HiddenFor(model => model.RowVersion)
My rowversion was null, so had to add this to the view
which solved my issue
public void Save(object entity)
{
using (var transaction = Connection.BeginTransaction())
{
try
{
SaveChanges();
transaction.Commit();
}
catch (OptimisticConcurrencyException)
{
if (ObjectStateManager.GetObjectStateEntry(entity).State == EntityState.Deleted || ObjectStateManager.GetObjectStateEntry(entity).State == EntityState.Modified)
this.Refresh(RefreshMode.StoreWins, entity);
else if (ObjectStateManager.GetObjectStateEntry(entity).State == EntityState.Added)
Detach(entity);
AcceptAllChanges();
transaction.Commit();
}
}
}
I had the same problem.
In my case I was trying to update the primary key, which is not allowed.
I got this error sporadically when using an async method. Has not happened since I switched to a synchronous method.
Errors sporadically:
[Authorize(Roles = "Admin")]
[HttpDelete]
[Route("file/{id}/{customerId}/")]
public async Task<IHttpActionResult> Delete(int id, int customerId)
{
var file = new Models.File() { Id = id, CustomerId = customerId };
db.Files.Attach(file);
db.Files.Remove(file);
await db.SaveChangesAsync();
return Ok();
}
Works all the time:
[Authorize(Roles = "Admin")]
[HttpDelete]
[Route("file/{id}/{customerId}/")]
public IHttpActionResult Delete(int id, int customerId)
{
var file = new Models.File() { Id = id, CustomerId = customerId };
db.Files.Attach(file);
db.Files.Remove(file);
db.SaveChanges();
return Ok();
}
I got that error when I was deleting some rows in the DB (in the loop), and the adding the new ones in the same table.
The solutions for me was, to dynamicaly create a new context in each loop iteration
This will also happen if you are trying to insert into a unique constraint situation, ie if you can only have one type of address per employer and you try to insert a second of that same type with the same employer, you will get the same problem.
OR
This could also happen if all of the object properties that were assigned to, they were assigned with the same values as they had before.
using(var db = new MyContext())
{
var address = db.Addresses.FirstOrDefault(x => x.Id == Id);
address.StreetAddress = StreetAddress; // if you are assigning
address.City = City; // all of the same values
address.State = State; // as they are
address.ZipCode = ZipCode; // in the database
db.SaveChanges(); // Then this will throw that exception
}
I got this exception when attaching an object that didn't exist in the database. I had assumed the object was loaded from a separate context, but if it was the user's first time visiting the site, the object was created from scratch. We have auto-incrementing primary keys, so I could replace
context.Users.Attach(orderer);
with
if (orderer.Id > 0) {
context.Users.Attach(orderer);
}
Well i have this same issue. But this was due to my own mistake. Actually i was saving an object instead of adding it. So this was the conflict.
One way to debug this problem in an Sql Server environment is to use the Sql Profiler included with your copy of SqlServer, or if using the Express version get a copy of Express Profiler for free off from CodePlex by the following the link below:
Express Profiler
By using Sql Profiler you can get access to whatever is being sent by EF to the DB. In my case this amounted to:
exec sp_executesql N'UPDATE [dbo].[Category]
SET [ParentID] = #0, [1048] = NULL, [1033] = #1, [MemberID] = #2, [AddedOn] = #3
WHERE ([CategoryID] = #4)
',N'#0 uniqueidentifier,#1 nvarchar(50),#2 uniqueidentifier,#3 datetime2(7),#4 uniqueidentifier',
#0='E060F2CA-433A-46A7-86BD-80CD165F5023',#1=N'I-Like-Noodles-Do-You',#2='EEDF2C83-2123-4B1C-BF8D-BE2D2FA26D09',
#3='2014-01-29 15:30:27.0435565',#4='3410FD1E-1C76-4D71-B08E-73849838F778'
go
I copy pasted this into a query window in Sql Server and executed it. Sure enough, although it ran, 0 records were affected by this query hence the error being returned by EF.
In my case the problem was caused by the CategoryID.
There was no CategoryID identified by the ID EF sent to the database hence 0 records being affected.
This was not EF's fault though but rather a buggy null coalescing "??" statement up in a View Controller that was sending nonsense down to data tier.
None of the above answers quite covered my situation and the solution to it.
Code where the error was thrown in MVC5 controller:
if (ModelState.IsValid)
{
db.Entry(object).State = EntityState.Modified;
db.SaveChanges(); // line that threw exception
return RedirectToAction("Index");
}
I received this exception when I was saving an object off an Edit view. The reason it threw it was because when I went back to save it, I had modified the properties that formed the primary key on the object. Thus, setting its state to Modified didn't make any sense to EF - it was a new entry, not a previously saved one.
You can solve this by either A) modifying the save call to Add the object, or B) just don't change the primary key on edit. I did B).
When the accepted answer said "it won't end up overwriting a change that your application didn't know has happened", I was skeptic because my object was newly created. But then it turns out, there was an INSTEAD OF UPDATE, INSERT- TRIGGER attached to the table which was updating a calculated column of the same table.
Once I change this to AFTER INSERT, UPDATE, it was working fine.
This happened to me due to a mismatch between datetime and datetime2. Strangely, it worked fine prior to a tester discovering the issue. My Code First model included a DateTime as part of the primary key:
[Key, Column(Order = 2)]
public DateTime PurchasedDate { get; set; } = (DateTime)SqlDateTime.MinValue;
The generated column is a datetime column. When calling SaveChanges, EF generated the following SQL:
-- Region Parameters
DECLARE #0 Int = 2
DECLARE #1 Int = 25
DECLARE #2 Int = 141051
DECLARE #3 DateTime2 = '2017-07-27 15:16:09.2630000' --(will not equal a datetime value)
-- EndRegion
UPDATE [dbo].[OrganizationSurvey]
SET [OrganizationSurveyStatusId] = #0
WHERE ((([SurveyID] = #1) AND ([OrganizationID] = #2)) AND ([PurchasedDate] = #3))
Because it was trying to match a datetime column with a datetime2 value, it returned no results. The only solution I could think of was to change the column to a datetime2:
[Key, Column(Order = 2, TypeName = "DateTime2")]
public DateTime PurchasedDate { get; set; } = (DateTime)SqlDateTime.MinValue;
If you are trying to create mapping in your edmx file to a "function Imports", this can result this error. Just clear the fields for insert, update and delete that is located in Mapping Details for a given entity in your edmx, and it should work.
I hope I made it clear.
Related
Is there a best practice for doing a copy of an entity, making some changes to it based on user input, and then re-inserting it into the database?
Some other Stackoverflow threads have mentioned that EF will handle inserting new objects for you even if the same primary key exists in the database, but I'm not quite sure that's how EF Core is handling it. Whenever I try and copy an object I get an error of
Cannot insert explicit value for identity column in table when IDENTITY_INSERT is set to OFF
Basically I just need a clean way to copy an object, make some changes to it based on user input, and then insert that copy back into the database, and have the Id auto-increment properly. Is there a best practice or simple way of doing this without having to manually set properties to null or empty?
EDIT: Example code for retrieving the object from the database:
public Incident GetIncidentByIdForCloning(int id)
{
try
{
return _context.Incident.Single(i => i.IncidentId == id);
}
catch
{
return null;
}
}
Code after retrieving object (As some fields are auto-generated like RowVersion which is a Timestamp):
public IActionResult Clone([FromBody]Incident Incident)
{
var incidentToCopy = _incidentService.IncidentRepository.GetIncidentByIdForCloning(Incident.IncidentId);
incidentToCopy.IncidentTrackingRefId = _incidentService.IncidentRepository.GetNextIdForIncidentCategoryAndType(
Incident.IncidentCategoryLookupTableId, Incident.IncidentTypeLookupTableId).GetValueOrDefault(0);
incidentToCopy.RowVersion = null;
incidentToCopy.IncidentId = 0; //This will fail with or without this line, this was more of a test to see if manually setting would default the insert operation, such as creating a brand new object would normally do.
incidentToCopy.IncidentCategoryLookupTableId = Incident.IncidentCategoryLookupTableId;
incidentToCopy.IncidentTypeLookupTableId = Incident.IncidentTypeLookupTableId;
var newIncident = _incidentService.IncidentRepository.CreateIncident(incidentToCopy);
...
I realize I could just make an entirely new object and do left-hand copying, but that seems terribly inefficient and I want to know if EF Core offers better solutions.
So I went through the "Possible duplicate" thread a bit more than I did when I initially stumbled upon it before creating this one, and there was a not-so-highly upvoted solution that I overlooked that essentially just grabs all of the values at once when retrieving the object from the database - and it doesn't retrieve a reference to that object in the process. My code now looks something like this:
try
{
var incidentToCopy = _context.Incident.Single(i => i.IncidentId == id);
return (Incident) _context.Entry(incidentToCopy).CurrentValues.ToObject();
}
In your IncidentRepository class try getting the Incident by using AsNoTracking and it should get tracked as a new entity when it is added.
public void Clone(int id)
{
// Prevent tracking changes to the object.
var incident = _context.AsNoTracking().SingleOrDefault(i => i.Id == id);
// Setting back to 0 should treat the object Id as unset.
incident.Id = 0;
// Add the Incident while it is untracked will treat it as a new entity.
_context.Incidents.Add(incident);
_context.SaveChanges();
}
I believe what is happening here is the following:
When you retrieve a value from the database, it gets stored in something like
context.ChangeTracker.Entries<Incident>
Which is a collection of Incident entries being tracked. When you change the id property of the incident object you've retrieved you are sort of abusing the ChangeTracker in the name of efficiency. The ChangeTracker does not believe you have created a new object. You might be able to try something like finding the entry in the ChangeTracker and set it's state to detached then after you've set the id to 0 add the object back to the context.DbSet but at that point you have probably made things way more complicated than simply copying the object.
I've shown three programmers this problem and we're all stumped. I call a Sql Server stored procedure in a foreach loop and the result always is the same as the first call. Even if I hard code parameters (removing the loop) only the first result is assigned to all subsequent calls.
The stored procedure is called by an Entity Framework function import (EF4 database first using the designer). The calling code lives in a repository that is a class library. The repository is called by a separate Asp.net webforms project. The problem code looks like this:
IEnumerable<WorkOrder> orders = _context.GetWorkOrders(UserName, workOrder, customerCode).ToList();
OrderStatus lastStatus = new OrderStatus();
foreach (Order order in orders)
{
lastStatus = _context.GetOrderStatus(order.OrderNumber).FirstOrDefault();
order.LastOrderStatus = lastStatus.OrderStatus;
}
As you can see this is pretty basic stuff. Depending on the order numbers passed in I always get the result of the first order number in the loop. I've turned off Ajax (part of the Telerik controls I use) because that has caused baffling errors for me in the past. I really hope you can suggest a way to debug this problem! Thanks in advance.
EDIT: Daniel J.G.'s comment led me to this possible solution. Now I need to figure out how to apply Ladislav Mrnka's answer..."Try to call ExecuteFunction directly with MergeOption.OverwriteChanges."
I'm answering my own question (since no one else has after a few days). The problem is caused by the Entity Framework database first designer. It generates code that caches the first stored procedure result causing the bad results in subsequent calls.
As I mentioned in the edit to my question the fix involves replacing the default MergeOption parameter used by ExecuteFunction. You need to use MergeOption.OverwriteChanges instead of the default (which I believe is MergeOption.PreserveChanges).
You could change that parameter in the generated code but your changes would be lost each time the designer is rebuilt. Instead I simply copied the generated code to my repository class, changed the MergeOption to OverwriteChanges, and stopped using the generated code. The end result looks like this:
IEnumerable<WorkOrder> orders = _context.GetWorkOrders(UserName, workOrder, customerCode).ToList();
OrderStatus lastStatus = new OrderStatus();
foreach (Order order in orders)
{
ObjectParameter workOrderParameter;
if (wo.WorkOrder != null)
{
workOrderParameter = new ObjectParameter("WorkOrder", order.WorkOrder);
}
else
{
workOrderParameter = new ObjectParameter("WorkOrder", typeof(global::System.String));
}
lastStatus = _context.ExecuteFunction<OrderStatus>("GetOrderStatus", MergeOption.OverwriteChanges, workOrderParameter).FirstOrDefault();
if (status != null)
{
order.LastOrderStatus = status.OrderStatus;
}
}
I also see that there is a way you can modify the T4 template to make the generated code use the correct MergeOption parameter. I haven't tried it though. If you're interested take a look here.
I'm back with a second answer to my own question. Be sure the Entity Key is truly a unique identifier for each Entity!
In my case, the OrderStock Entity was missing the OrderID (along with StockID) as the Entity Key. Typically the designer culls the primary key fields from the database but I have a unique situation (where my entity is based on a view). Since I left off OrderID from the Entity Key I saw duplicate rows for a single OrderStock Entity.
When I marked OrderID Entity Key = True the duplicate problem went away.
I just spent some days now to find a bug caused by some strange behavior of the Entity Framework (version 4.4.0.0). For an explanation I wrote a small test program. At the end you'll find some questions I have about that.
Declaration
Here we have a class "Test" which represents our test dataset. It only has an ID (primary key) and a "value" property. In our TestContext we implement a DbSet Tests, which shall handle our "Test" objects as a database table.
public class Test
{
public int ID { get; set; }
public int value { get; set; }
}
public class TestContext : DbContext
{
public DbSet<Test> Tests { get; set; }
}
Initialization
Now, we remove any (if existent) entries from our "Tests" table and add our one and only "Test" object. It has ID=1 (primary key) and value=10.
// Create a new DBContext...
TestContext db = new TestContext();
// Remove all entries...
foreach (Test t in db.Tests) db.Tests.Remove(t);
db.SaveChanges();
// Add one test entry...
db.Tests.Add(new Test { ID = 1, value = 10 });
db.SaveChanges();
Tests
Finally, we run some tests. We select our entry by it's original value (=10) and we change the "value" of our entry to 4711. BUT, we do not call db.SaveChanges(); !!!
// Find our entry by it's value (=10)
var result = from r in db.Tests
where r.value == 10
select r;
Test t2 = result.FirstOrDefault();
// change its value from 10 to 4711...
t2.value = 4711;
Now, we try to find the (old) entry by the original value (=10) and do some tests on the results of that.
// now we try to select it via its old value (==10)
var result2 = from r in db.Tests
where r.value == 10
select r;
// Did we get it?
if (result2.FirstOrDefault() != null && result2.FirstOrDefault().value == 4711)
{
Console.WriteLine("We found the changed entry by it's old value...");
}
When running the program we'll actually see "We found the changed entry by it's old value...". That means we have run a query for r.value == 10, found something... This would be acceptable. BUT, get receive the already changed object (not fulfilling value == 10)!!!
Note: You'll get an empty result set for "where r.value == 4711".
In some further testing, we find out, that the Entity Framework always hands out a reference to the same object. If we change the value in one reference, it's changed in the other one too. Well, that's ok... but one should know it happens.
Test t3 = result2.FirstOrDefault();
t3.value = 42;
if (t2.value == 42)
{
Console.WriteLine("Seems as if we have a reference to the same object...");
}
Summary
When running a LINQ query on the same Database Context (without calling SaveChanges()) we will receive references to the same object, if it has the same primary key. The strange thing is: Even, if we change an object we will find it (only!) by it's old values. But we will receive a reference to the already changed object. This means that the restrictions in our query (value == 10) is not guaranteed for any entries that we changed since our last call of SaveChanges().
Questions
Of course, I'll probably have to live with some effects here. But I, would like to avoid to "SaveChanges()" after every little change. Especially, because I would like to use it for transaction handling... to be able to revert some changes, if something goes wrong.
I would be glad, if anyone could answer me one or even both of the following questions:
Is there a possibility to change the behavior of entity framework to work as if I would communicate with a normal database during a transaction? If so... how to do it?
Where is a good resource for answering "How to use the context of entity framework?" which answers questions like "What can I rely on?" and "How to choose the scope of my DBContext object"?
EDIT #1
Richard just explained how to access the original (unchanged) database values. While this is valuable and helpful I've got the urge to clarify the goal ...
Let's have a look at what happens when using SQL. We setup a table "Tests":
CREATE TABLE Tests (ID INT, value INT, PRIMARY KEY(ID));
INSERT INTO Tests (ID, value) VALUES (1,10);
Then we have a transaction, that first looks for entities whose values are 10. After this, we update the value of these entries and look again for those entries. In SQL we already work on the updated version, so we will not find any results for our second query. After all we do a "rollback", so the value of our entry should be 10 again...
START TRANSACTION;
SELECT ID, value FROM Tests WHERE value=10; {1 result}
UPDATE Tests SET value=4711 WHERE ID=1; {our update}
SELECT ID, value FROM Tests WHERE value=10; {no result, as value is now 4711}
ROLLBACK; { just for testing transactions... }
I would like to have exactly this behavior for the Entity Framework (EF), where db.SaveChanges(); is equivalent to "COMMIT", where all LINQ queries are equivalent to "SELECT" statements and every write access to an entity is just like an "UPDATE". I don't care about when the EF does actually calls the UPDATE statement, but it should behave the same way as using a SQL Database the direct way... Of course, if "SaveChanges()" is called and returning successfully it should be guaranteed that all data was persisted correctly.
Note: Yes, I could call db.SaveChanges() before every query, but then I would loose the possibility for a "Rollback".
Regards,
Stefan
As you've discovered, Entity Framework tracks the entities it has loaded, and returns the same reference for each query which accesses the same entity. This means that the data returned from your query matches the current in-memory version of the data, and not necessarily the data in the database.
If you need to access the database values, you have several options:
Use a new DbContext to load the entity;
Use .AsNoTracking() to load an un-tracked copy of your entity;
Use context.Entry(entity).GetDatabaseValues() to load the property values from the database;
If you want to overwrite the properties of the local entity with the values from the database, you'll need to call context.Entry(entity).Reload().
You can wrap your updates in a transaction to achive the same result as in your SQL example:
using (var transaction = new TransactionScope())
{
var result = from r in db.Tests
where r.value == 10
select r;
Test t2 = result.FirstOrDefault();
// change its value from 10 to 4711...
t2.value = 4711;
// send UPDATE to Database but don't commit transcation
db.SaveChanges();
var result2 = from r in db.Tests
where r.value == 10
select r;
// should not return anything
Trace.Assert(result2.Count() == 0);
// This way you can commit the transaction:
// transaction.Complete();
// but we do nothing and after this line, the transaction is rolled back
}
For more information see http://msdn.microsoft.com/en-us/library/bb896325(v=vs.100).aspx
I think your problem is the expression tree. The Entity Framework executes your query to the database when you say SaveChanges(), as you allready mentioned. When manipulating something within the context, the changes do not happen on the database, they happen in your physical memory. Just when you call SaveChanges() your actions are translated to let's say SQL.
When you do a simple select the database is queried just in the moment when you acces the data. So if your have not call SaveChanges(), it finds the dataset in the database with (SQL)SELECT* FROM Test WHERE VALUE = 10 but interprets from the expression tree, that it has to be value == 4711.
The transaction in EF is happening in your storage. Everything you do before SaveChanges() is your transaction. Read for further information: MSDN
A really good ressource, which is probably up to date, for infomations about the EF is the Microsoft Data Developer Center
I have the following Model
public class CourseModel
{
[Key]
public int courseID { get; set; }
...
public virtual ICollection<CourseMeetModel> meets { get; set; }
}
When I try to edit one of the entries and if the input is valid it works fine.
However if the if its not valid it alerts the user of the mistakes. Once the user fixes the mistakes and tries to save i get the following exception.
Store update, insert, or delete statement affected an unexpected
number of rows (0). Entities may have been modified or deleted since
entities were loaded. Refresh ObjectStateManager entries.
I have noticed this happens if the input fails the validation steps in my controller.
My Controller
public ActionResult EditCourseConfirmed(CourseModel course)
{
CoursesDBContext db = new CoursesDBContext();
bool valid = validateCouse(course); //If this fails and the course model is returned back to the view I get that error
if (valid)
{
try
{
db.Entry(course).State = EntityState.Modified;
db.SaveChanges();
Session[d.s_Clear] = false;
return RedirectToAction("Index");
}
catch (Exception e)
{
ModelState.AddModelError(string.Empty, "Unable to save the course, please try again." + e.Message);
return View(course);
}
}
return View(course);
}
Try this
try {
context.SaveChanges();
}
catch (OptimisticConcurrencyException)
{
context.Refresh(RefreshMode.ClientWins, db.table);
context.SaveChanges();
}
Props: https://stackoverflow.com/a/6848729/1166147
Adding on to explain, per your comments. Thanks for your EF4 CTP5 tip. Glad this fixed it, please mark as accepted. When you get this error, something happened that changed the data (another user, etc) between load and update, there is a trigger causing problems, or if using a stored procedure it hit 0 recs. It is hard to know without more info. Is this the first update hit? Was there another update run first that succeeded before the user hit the error, modified, and then tried to go on without refreshing? Do you have any triggers? What is your degree of concurrency - did another user edit and save between this users' query and update? Read the link to the post I gave - someone mentioned a ReadOnlyAttribute in an entity key's metadata being replaced, which causes its value to become zero as a potential cause - I am not sure about this, but it makes sense -
(Modified from MSDN) By default, the Entity Framework implements an optimistic concurrency model. This means that locks are not held on data in the data source between when the data is queried and the data is updated, creating the potential for this error if another user modified the data. When this attribute is used, the Entity Framework checks for changes in the database before saving changes.
Any conflicting changes will cause an OptimisticConcurrencyException.
An OptimisticConcurrencyException can also occur when you define an Entity Data Model that uses stored procedures to make updates to the data source. In this case, the exception is raised when the stored procedure that is used to perform updates reports that zero rows were updated. SET NOCOUNT ON would fix thia.
Thanks to user #user1166147 it was causing a dbupdate exception though I still don't know why ...
Since EF4 CTP5 DbContext doesnt have a refresh method i ended up doing this:
try
{
db.SaveChanges();
}
catch (DbUpdateConcurrencyException e)
{
var entry = e.Entries.Single();
entry.OriginalValues.SetValues(entry.CurrentValues);
entry.CurrentValues.SetValues(entry.CurrentValues);
db.SaveChanges();
}
some more details here http://blogs.msdn.com/b/adonet/archive/2011/02/03/using-dbcontext-in-ef-feature-ctp5-part-9-optimistic-concurrency-patterns.aspx
In your view make sure to add this
#Html.HiddenFor(m => m.courseID)
I got the exception Reason was my entity has a Key with a mapping of
HasDatabaseGeneratedOption(DatabaseGeneratedOption.Identity);
and when i added a new Entity and supplied an id, at save time the context will think it is a modified Entity and not a new one, so i did not supply a value to the Key and at save time i check for the keys default value, from this i know if its new or modified
After days studying EF to understand (kinda..) how it works, I finally realized that I might have a big problem.
Imagine that I have two entities: Pais and UF. The relationship between them is Pais (0..1) ... (*) UF. A screenshot: http://i.imgur.com/rSOFU.jpg.
Said that, consider that I have a controller called UFController and it has actions for Edit and Create, which are just fine. My views are using the EditorFor helper (or similar ones) for inputs, so when I submit the form the controller will receive a UF object filled with all the data (automatically) with a reference to an almost-empty Pais. My view code (part of it):
#* UF attributes *#
#Html.EditorFor(m => m.Sigla)
#Html.EditorFor(m => m.Descricao)
#Html.EditorFor(m => m.CodigoIBGE)
#Html.EditorFor(m => m.CodigoGIA)
#* Pais primary key ("ID") *#
#Html.EditorFor(m => m.Pais.Codigo) // Pais id
The controller Edit action code:
[HttpPost]
public ActionResult Edit(UF uf)
{
try
{
if (ModelState.IsValid)
{
db.UFs.Attach(uf);
db.ObjectStateManager.ChangeObjectState(uf, EntityState.Modified);
db.SaveChanges();
return this.ClosePage(); // An extension. Just ignore it.
}
}
catch (Exception e)
{
this.ModelState.AddModelError("Model", e.Message.ToString());
}
return View(uf);
}
When I submit the form, this is what the action receives as uf:
{TOTALWeb.UF}
base {System.Data.Objects.DataClasses.EntityObject}: {TOTALWeb.UF}
(...)
CodigoGIA: 0
CodigoIBGE: 0
Descricao: "Foobar 2001"
ID: 936
Pais: {TOTALWeb.Pais}
PaisReference: {System.Data.Objects.DataClasses.EntityReference<TOTALWeb.Pais>}
And uf.Pais:
{TOTALWeb.Pais}
base {System.Data.Objects.DataClasses.EntityObject}: {TOTALWeb.Pais}
Codigo: 0
CodigoBACEN: null
CodigoGIA: null
CodigoIBGE: null
Descricao: null
UF: {System.Data.Objects.DataClasses.EntityCollection<TOTALWeb.UF>}
The original information (the one on the database) is uf.Pais.Codigo == 716. So, right now I'm receiving the updated information. The problem on that the controller is not upading the FK in the database.
I don't want to set the EntityState from uf.Pais to Modified because the entity itself wasn't changed (I didn't changed the information from that entry), but the relationship was.
In other words, what I'm trying to do is change the value of the FK, pointing the uf.Pais to another instance of Pais. Afaik, it's impossible to change the relationship state to Modified (throw an exception), so I'm looking for alternative solutions.
I've read a bunch of topics I've found on Google about this kind of problem, but I still didn't find a simple and elegant solution. The last ones I read here on stackoverflow:
How to work with navigation properties (/foreign keys) in ASP.NET MVC 3 and EF 4.1 code first
Strongly-Typed ASP.NET MVC with ADO.NET Entity Framework
Getting Error 3007 when I add my Entity Model
I asked a question a few days ago about a similar problem ( Entity Framework 4.1 - default EntityState for a FK? ). I didn't understand how EF works that time, so now a bunch of things look clear to me (that's why I'm opening a new question).
For the Create action I've been testing this solution (proposed by Ladislav on my other question), but it generates an additional select (which can be eventually slow for us):
// Here UF.Pais is null
db.UFs.AddObject(uf);
// Create dummy Pais
var pais = new Pais { Id = "Codigo" };
// Make context aware of Pais
db.Pais.Attach(pais); // <- Executing a SELECT on the database, which -can- be slow.
// Now make the relation
uf.Pais = pais;
db.SaveChanges();
I can replicate this for the Edit (I guess), but I don't want that additional SELECT.
So, in resume: I'm trying to use navigation properties to send data to the controller and save them directly in the database using a fast and easy way (without messing too much with the entity - these ones are simple, but we have huge and very complex ones with a lot of FKs!). My question is: there's a solution that doesn't involve executing another query in the database (a simple one)?
Thanks,
Ricardo
PS: sorry for any english mistakes and any confusions.
Update 1: using BennyM's solution (kind of..)
I tested the following code, but it doesn't work. It throws an exception: "An object with the same key already exists in the ObjectStateManager. The ObjectStateManager cannot track multiple objects with the same key." Probably because Pais is already in the context, I guess?
I'm using a Entities (created by EF) class as context. Also, I don't know what is the method Entry, and I don't know where is it. Just for "fun", I tested this:
// Attach the Pais referenced on editedUF, since editedUF has the new Pais ID, not the old one.
Pais existingPais = new Pais { Codigo = editedUF.Pais.Codigo };
db.Paises.Attach(existingPais);
// Attach the edited UF.
db.UFs.Attach(editedUF);
// Set the correct Pais reference (ignoring the current almost-null one).
editedUF.Pais = existingPais;
// Change the object state to modified.
db.ObjectStateManager.ChangeObjectState(editedUF, EntityState.Modified);
// Save changes.
db.SaveChanges();
return this.ClosePage();
The exception is throwed when I try to attach the editedUF to the current context. I'm working with this idea right now, trying to find other solutions. Also, you're right BennyM, attaching the Pais to the context is not generating an additional SELECT. I don't know what happened that time, it really doesn't do anything with the database.
Still, this is a manual solution: I have to do that for each FK. That's what I'm trying to avoid. You see, some programmers, even if you explain 100 times, won't remember to do that with each FK. Eventually that'll come back to me, so I'm trying to avoid anything that can lead into errors (database or code ones) to make sure everyone can work without any stress. :)
I'm answering my own question because I've found a simple solution (at least in my case). My scenario uses a lot of Views for data input (which means that I have a lot of entities). I needed a simple and easy to use solution, so I deleted my entire Entities EDMX file (Ctrl+A, Delete!).
Then I decided to add again Pais and UF entities, but checking the checkbox for exposing the FK attribute. On first I though they can't work together, but they can, but you need to be a little careful on how to use it. They're now linked with navigation properties and the exposed FK.
The reason I couldn't add the FK attribute is because I was doing it manually. Using the "Update model from database" again checking the correct option it worked flawless.
In my edit view, I'm setting the ID of Pais into the FK attribute, not the Pais.Codigo. The reason why I do that is because the FK attribute is a scalar property and then I can detect changes.
This is the current view code for the Pais input (it's not exactly it, but it's similar to this):
#Html.EditorFor(m => m.PaisCodigo)
Btw, PaisCodigo is the FK. Yes, it can get a little confusing with Pais.Codigo, but we didn't decided any naming rules (yet). Any suggestions on this idea would be appreciated.
The final Edit action code is like this (I removed error processing to make it look simple!):
[HttpPost]
public ActionResult Edit(UF editedUF)
{
if (ModelState.IsValid)
{
// Attach the edited UF into the context and change the state to Modified.
db.UFs.Attach(editedUF);
db.ObjectStateManager.ChangeObjectState(editedUF, EntityState.Modified);
// Save changes.
db.SaveChanges();
// Call an extension (it's a redirect action to another page, just ignore it).
return this.ClosePage();
}
}
This is what is received when I submit the form for editedUF:
{TOTALWeb.UF}
base {System.Data.Objects.DataClasses.EntityObject}: {TOTALWeb.UF}
(...)
CodigoGIA: 0
CodigoIBGE: 0
CodigoPais: 0 <-- new Pais ID!
Descricao: "Foobar 2000"
ID: 902
Pais: {TOTALWeb.Pais}
PaisReference: {System.Data.Objects.DataClasses.EntityReference<TOTALWeb.Pais>}
Sigla: "RI"
Usuarios: {System.Data.Objects.DataClasses.EntityCollection<TOTALWeb.Usuario>}
As you can see, CodigoPais is pointing to the new Pais ID.
About the editedUF.Pais navigation property, there's a small detail. Before attaching it into the context, it's null. But, hey, after adding, this is what happens:
{TOTALWeb.Pais}
base {System.Data.Objects.DataClasses.EntityObject}: {TOTALWeb.Pais}
(...)
Codigo: 0
CodigoBACEN: 1058
CodigoGIA: 0
CodigoIBGE: null
Descricao: "Brasil"
UFs: {System.Data.Objects.DataClasses.EntityCollection<TOTALWeb.UF>}
So, it has been filled. The cost for that should be one query, but I couldn't capture it on the monitor.
In other words, just expose the FK, change it using the View and use the navigation property to make the code a little more clear. That's it! :)
Thanks everyone,
Ricardo
PS: I'm using dotConnect for Oracle as a base for the EF 4.1. We don't use SQL Server (at least for now). The "monitor" I said before was devArt's dbMonitor, so I can see all queries sent to the Oracle database. And, again, sorry for any english mistakes!
If you include the foreign keys in your model. So add a PaisId property to the UF entity, you can directly set it and it will update the association.
using (var db = new Context())
{
db.UFs.Attach(editedUF);
editedUF.PaisId = theForeignKey;
db.Entry(editedUF).State = EntityState.Modified;
db.SaveChanges();
}
Also I've tested the approach you already mentioned in your question, and I don't get that extra select when attaching an entity. So this works as well:
using (var db = new Context())
{
ExistingPais pais = new Pais{Id =theId};
db.Pais.Attach(pais);
db.UF.Attach(editedUF);
editedUF.Pais = pais;
db.Entry(editedUF).State = EntityState.Modified;
db.SaveChanges();
}
Assuming your code looks like this:
public class Pais
{
public int Id { get; set; }
public virtual ICollection<UF> UFs { get; set; }
}
public class UF
{
public int Id { get; set; }
public virtual Pais Pais { get; set; }
public int PaisId { get; set; }
}