I saw a book with some code like this:
public class Order
{
public int OrderID { get; set; }
public ICollection<CartLine> Lines { get; set; }
...
}
public class CartLine
{
public int CartLineID { get; set; }
public Product Product { get; set; }
public int Quantity { get; set; }
}
//Product class is just a normal class that has properties such as ProductID, Name etc
and in the order repository, there is a SaveOrder method:
public void SaveOrder(Order order)
{
context.AttachRange(order.Lines.Select(l => l.Product));
if (order.OrderID == 0)
{
context.Orders.Add(order);
}
context.SaveChanges();
}
and the book says:
when store an Order object in the database. When the user’s cart data is deserialized from the session store, the JSON package creates new objects that are not known to
Entity Framework Core, which then tries to write all the objects into the database. For the Product objects, this means that Entity Framework Core tries to write objects that have already been stored, which causes an error. To avoid this problem, I notify Entity Framework Core that the objects exist and shouldn’t be stored in the database unless they are modified
I'm confused, and have two questions:
Q1-why writing objects that have already been stored will cause an error, in the point of view of underlying database, it's just an update SQL statement that modify all columns to their current values?I know it does unnecessary works by changing nothing and rewrite everything, but it shouldn't throw any error in database level?
Q2-why we don't do the same thing to CartLine as:
context.AttachRange(order.Lines.Select(l => l.Product));
context.AttachRange(order.Lines);
to prevent CartLine objects stored in the database just as the way we do it to Product object?
Okay, so this is gonna be a long one:
1st Question:
In Entity Framework (core or "old" 6), there's this concept of "Change tracking". The DbContext class is capable of tracking all the changes you made to your data, and then applying it in the DB via SQL statements (INSERT, UPDATE, DELETE). To understand why it throws an error in your case, you first need to understand how the DbContext / change tracking actually works. Let's take your example:
public void SaveOrder(Order order)
{
context.AttachRange(order.Lines.Select(l => l.Product));
if (order.OrderID == 0)
{
context.Orders.Add(order);
}
context.SaveChanges();
}
In this method, you receive an Order instance which contains Lines and Products. Let's assume that this method was called from some web application, meaning you didn't load the Order entity from the DB. This is what's know as the Disconected Scenario
It's "disconnected" in the sense that your DbContext is not aware of their existence. When you do context.AttachRange you are literally telling EF: I'm in control here, and I'm 100% sure these entities already exist in the DB. Please be aware of them for now on!,
Let's use your code again: Imagine that it's a new Order (so it will enter your if there) and you remove the context.AttachRange part of the code. As soon as the code reaches the Add and SaveChanges these things will happen internally in the DbContext:
The DetectChanges method will be called
It will try to find all the entities Order, Lines and Products in its current graph
If it doesn't find them, they will be added to the "pending changes" as a new records to be inserted
Then you continue and call SaveChanges and it will fail as the book tells you. Why? Imagine that the Products selected were:
Id: 1, "Macbook Pro"
Id: 2, "Office Chair"
When the DbContext looked at the entities and didn't know about them, it added them to the pending changes with a state of Added. When you call SaveChanges, it issues the INSERT statements for these products based on their current state in the model. Since Id's 1 and 2 already exists in the database, the operation failed, with a Primary Key violation.
That's why you have to call Attach (or AttachRange) in this case. This effectively tells EF that the entities exist in the DB, and it should not try to insert them again. They will be added to the context with a state of Unchanged. Attach is often used in these cases where you didn't load the entities from the dbContext before.
2nd question:
This is hard for me to access because I don't know the context/model at that level, but here's my guess:
You don't need to do that with the Cartline because with every order, you probably want to insert new Order line. Think like buying stuff at Amazon. You put the products in the cart and it will generate an Order, then Order Lines, things that compose that order.
If you were then to update an existing order and add more items to it, then you would run into the same issue. You would have to load the existing CartLines prior to saving them in the db, or call Attach as you did here.
Hope it's a little bit clearer. I have answered a similar question where I gave more details, so maybe reading that also helps more:
How does EF Core Modified Entity State behave?
Probably not pertinent, but I'm using:
.NET MVC 5.2.3 w/ Razor 3.2.3, Entity Framework 6.1.3 Code First, Visual Studio 2015.
Okay, in my controller method, I have--in essence, but dumbed down for conciseness:
using( var context = new MyContext() ) {
var person = context.Persons.Include( x => x.PostalCode ).FirstOrDefault();
return View(person);
}
Now, originally the Zip data entry property was not a foreign key...just whatever 5-digit string the user entered. Now, however, it's a foreign key, in essence, so that we can get postal-code information.
public string Zip { get; set; }
[ForeignKey("Zip")]
public virtual PostalCode PostalCode { get; set; }
Not ideal structure...I know...
But anyway, if the user has a postal-code recognized by our system, all great, everything loads. However, if the user has an unknown or invalid zip code, e.g. 00000, EF sees a non-null Foreign Key and this results in the following problem:
In my view-file (so after I've disposed of my context), I check the property of our greedily-loaded entity:
#if( person.PostalCode != null && person.PostalCode.IsInServiceArea ) {
<div>Service Area HTML</div>
}
Unfortunately, because of EF overriding my virtual property, even when there is no PostalCode, it's not NULL. So, when this code runs it throws an exception that the ObjectContext has been disposed of, which means that EF is trying to lazy-load an Entity even though it already tried to greedily load and should know it doesn't exist :(
The obvious solutions (please don't answer w/ these):
Validate zip codes on entry and only allow ones we know about and set unknown zip codes to NULL (I like this, but I don't have time to re-engineer this)
Get the value of IsInServiceArea while the context is opened and put it directly in my View Model so that I set the value before my context is disposed of. (This is actually what I'm planning to do, so I don't need this answer):
The Question
In Entity Framework, Code-First, what is the correct way to check to
see if a greedily loaded, LEFT OUTER JOIN'd entity, was loaded AFTER
the context is disposed of?
Based on answers I have found, (e.g. the below), I'm thinking it's likely that this is not possible without the context being open...but thought I'd ask anyway:
How to determine if Navigation Property in the Entity Framework is set without loading the related records
So, referring back to my comment, you can disable lazy-loading and proxy creation altogether. Just find the constructor(s) of your dbcontext type, and then add these two lines into the method:
this.Configuration.LazyLoadingEnabled = false;
this.Configuration.ProxyCreationEnabled = false;
This way you can disable it globally (whenever a context is created the ctor is run, these settings are applied).
Or, you can just disable it for one instance if you set these on the context object itself after it's created.
Alternatively let your context live through the entire request so that lazy loading can be fulfilled on the view.
Startup.Auth.cs
public void ConfigureAuth(IAppBuilder app)
{
MyContext Create() { return new MyContext(); }
app.CreatePerOwinContext(Create);
...
}
MyController.cs
var context = HttpContext.GetOwinContext().Get<MyContext>();
var person = context.Persons.Include( x => x.PostalCode ).FirstOrDefault();
return View(person);
I know it can be silly question, but although ...
Im learning ASP.NET MVC 4 and got one question about the dbcontext.Entry method, lets assume we got such method :
public ActionResult Edit(Movie movie)
{
if (ModelState.IsValid)
{
db.Entry(movie).State = EntityState.Modified;
db.SaveChanges();
return RedirectToAction("Index");
}
return View(movie);
}
How the db.Entry(movie) determine which exactly row from the database is this "movie" instance? If all properties of this movie parameter object will be changed, how will it be possible to determine which row this instance should be attached to? I got smth on my head like it will determine by the Id which we cant change but not sure, so better if I ask :)
When EF generates the models it includes information on what column is the primary key in the database. db.Entry(model) binds the model to the context using that primary key and whatever primary key it shares in the database will be overwriten by the data in the model when SaveChanges() is called. If the primary key is 0, or empty you must set the State to EntityState.Added instead of EntityState.Modified.
As another answer said this can fail. The reason that binding can fail is that a context can only track a single distinct entity once (entity being a distinct class and id). If you attempt to do the following you'll get an exception:
var dbModel = db.Movie.Find(movie.ID);
db.Entry(movie.ID) // Doesn't matter what you do, this will always fail
Because the db context is already tracking a Movie with the same ID.
This is one of the larger reasons that you don't want to share db contexts between users, because another user attempting to update the same entry will cause the second one to throw an exception. While the transaction will be ACID, you can end up with edge case race conditions (for lack of a better term) between users.
Every Table that used by EF must have a Primary key, unless it is a collection , so the EF know the row by the primary key that stay the same.
This method has a parameter Movie, but it has nothing to do with database. You must update object that you will get from db like:
public ActionResult Edit(Movie updatedMovie)
{
if (ModelState.IsValid)
{
//db=your entities context
//here you will get instance of record from db that you want to update
Movie movie = db.Movie.Find(updatedMovie.Id);
TryUpdateModel(movie);
ctx.SaveChanges();
return RedirectToAction("Index");
}
return View(movie);
}
Maybe there is more efficient way , but I'm using it like this and it works fine.
If code in your question is a version that is supposed to be a working version, then it wasn't working for me when I have been trying to solve a similar problem so I had to solve it by using code above finally.
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.