I'm working on an application that's using EF Core (version 5.0.5) with a MySql database. My context is setup so that QueryTrackingBehavior is NoTracking and when i try to save a modified entity I get the following exception:
Database operation expected to affect 1 row(s) but actually affected 0 row(s). Data may have been modified or deleted since entities were loaded. See ...
The application uses the Repository/UnitOfWork pattern but the code essentially boils down to:
public class MyEntity
{
public Guid Id {get; set;}
public int Counter {get; set;}
}
public async Task UpdateCounter(Guid id)
{
DbContextOptionsBuilder<DatabaseContext> builder = new DbContextOptionsBuilder<DatabaseContext>()
.UseMySql(CONNECTION_STRING, ServerVersion.AutoDetect(CONNECTION_STRING), options =>
{
options.UseNetTopologySuite();
})
.UseQueryTrackingBehavior(QueryTrackingBehavior.NoTracking);
MyDbContext context = new MyDbContext(builder.Options);
MyEntity entity = await context.Set<MyEntity>().FirstOrDefaultAsync(e => e.Id == id);
entity.Counter += 1;
context.Update(entity);
await context.SaveChangesAsync();
}
As a test, I stored the result of context.Update(entity); to a variable so I could inspect it in debug and it looks correct (as far as I understand), the state says it's modified but yet I still get the exception.
I did some reading online and found a few suggestions and tried replacing context.Update(entity); with:
context.Attach(entity).State = EntityState.Modified;
Which had the same result. I also tried:
context.Entry(entity).State = EntityState.Modified;
Which also had the same result. In my reading I was a little confused what the difference between these three lines even were.
My main question is: Why is EF Core failing to save my entity?
Edit
A couple pieces of information that might be important:
MyEntity.Counter is set as a concurrency token and has a default value
Adding a new entity to the database works no problem, it's just the update that is causing the exception
Related
I am having the following entity in the code:
public class Referral : AggregateRoot<Guid>
{
...
public virtual ICollection<ReferralAccomplishment> ReferralAccomplishments { get; }
}
I would like to add new ReferralAccomplishment item to the ReferralAccomplishments collection. The following snippet has been implemented to do so:
var referral = await referralRepository.GetAsync(referral => referral.SenderId == GivenReferral.SenderId);
var newReferralAccomplishment = new ReferralAccomplishment();
referral.ReferralAccomplishments.Add(newReferralAccomplishment);
referralRepository.Update(referral);
referralRepository.CommitAsync(); // DbContext.SaveChangesAsync
Update method:
public void Update(Referral referral) =>
_context
.Referrals
.Update(referral);
The snippet above results in such exception:
Microsoft.EntityFrameworkCore.DbUpdateConcurrencyException: The database operation was expected to affect 1 row(s), but actually affected 0 row(s); da...
The problem as far as I noticed, is that ReferralAccomplishment EF Core context entry has EntityState = EntityState.Modified (for some not understandable reason in my opinion). I would expect it to be EntityState.Added. I'm not sure how to implement this correctly, I would like to have deep update, and I understand there could be need to implement update logic manually, and manage those states manually, but the problem is that new entity state is totally not understandable for me and it results in the mentioned error. How to overcome that? Thanks in advance for any help!
I have question about the change tracker in Entity Framework. I read the change tracker document on MSDN - it said that if you insert a model into a database, its status in context will be Added, and when you execute SaveChanges(), it inserts a new row into the database and its status becomes unchanged.
So why should we attach a model before updating it in Entity Framework?
public virtual void Update(TEntity entity)
{
_dbset.Attach(entity); => ???
_context.Entry(entity).State = EntityState.Modified;
_context.SaveChanges();
}
Update :
While an entity is altered within the scope of a DbContext, and was not loaded /w AsNoTracking, the change tracker for that DbContext will pick up changes and apply updates when you call SaveChanges. As in the example you've given, in many cases you might be dealing with an Entity that currently isn't tracked by a DbContext anymore.
As a simple example: (Tracked entities)
public void UpdateDescription(int orderId, string description)
{
using (var context = new AppDbContext())
{
var order = context.Orders.Single(x => x.OrderId == orderId);
order.Description = description;
context.SaveChanges();
}
}
In this example, the Order entity is loaded, tracked, and updated within the scope of a single DbContext. Instead, if we have a method structure like this:
public Order GetOrderById(int orderId)
{
using (var context = new AppDbContext())
{
return context.Orders.Single(x => x.OrderId == orderId);
}
}
public void UpdateOrder(Order order)
{
using (var context = new AppDbContext())
{
context.Attach(order);
context.Entity(order).State = EntityState.Modified;
context.SaveChanges();
}
}
Such as in the case of web applications where an Edit page retrieves an Order entity and passes that to a view. When the user updates some fields on the page Model and submits, a separate Update call is made passing the model. With the "GetOrderById" call, the Order entity was loaded by one instance of the DbContext. When the "UpdateOrder" call occurs, that Order is effectively a deserialized POCO, not a tracked entity anymore. This would be why you need to attach it to the DbContext instance that Update will be using, and set it's entity state to Modified in order for EF to treat it as an entity that needs to be updated.
It is worth noting that in the first tracked example, the UPDATE SQL statement will effectively be something like:
UPDATE Orders SET Description = 'newDescription' WHERE OrderId = 1
where in the second example, the UPDATE statement would be more like:
UPDATE Orders SET OrderNumber = 21, CustomerId = 12, Description = 'newDescription' /* + all fields/FKs in Order table... */ WHERE OrderId = 1
By attaching and setting an entity state to Modified, EF will update all non-PK properties on the entity, even if you only intend/expect for one field to have been modified. In the case of a web application, passing entities back to a controller to be updated this way can leave your system vulnerable to data tampering. (Changing fields/FKs that your UI did not allow) It also means you need to pass a complete entity all of the time to avoid data possibly getting wiped unexpectedly which increases the message payload size going back and forth between client and server.
Because EntityFramework only changed entities that exist in ChangeTracker.
And when you Attaching an entity to DbContext, entity framework know that one entity has tracked and maybe contains changes and apply changes in DataBase after SaveChanges.
Below is the code of my API, but it always returns a 500 Internal server error. The exception is thrown when executing SaveChangesAsync().
The instance of entity type 'NewsReport' cannot be tracked because another instance with the same key value for {'Id'} is already being tracked. When attaching existing entities, ensure that only one entity instance with a given key value is attached. Consider using 'DbContextOptionsBuilder.EnableSensitiveDataLogging' to see the conflicting key values
[HttpPut("{id}")]
public async Task<IActionResult> EditDevblog([FromBody] DevblogModel devblog, int id)
{
if (!ModelState.IsValid)
return BadRequest();
var devblogInDb = _context.DevblogModels
.Include(d => d.Fixs)
.Include(d => d.News)
.Include(d => d.Removes)
.Include(d => d.Updates)
.SingleOrDefault(d => d.Id == id);
if (devblogInDb == null)
return NotFound();
devblogInDb.Fixs = devblog.Fixs;
devblogInDb.News = devblog.News;
devblogInDb.Removes = devblog.Removes;
devblogInDb.Updates = devblog.Updates;
devblogInDb.PatchName = devblog.PatchName;
await _context.SaveChangesAsync();
var h = _context.DevblogModels.SingleOrDefault(d => d.Id == id);
return Ok();
}
Please see my answer here.
EF Core throws this exception when you replace a child entity instance with a new one like you are doing here:
devblogInDb.Fixs = devblog.Fixs;
devblogInDb.News = devblog.News;
devblogInDb.Removes = devblog.Removes;
devblogInDb.Updates = devblog.Updates;
devblogInDb.PatchName = devblog.PatchName;
Unfortunately the only workable alternative has been to update the current child entity properties directly. Per my original answer:
// In your DevblogModels and assuming a 1:1 relationship
public void SetNewsReport(NewsReport newsReport)
{
this.News.UpdateFrom(newsReport);
}
// Then, on your NewsReport entity
internal void UpdateFrom(NewsReport other)
{
this.Title = other.Title;
this.Article = other.Article;
// other properties...
}
The second approach in my original answer suggests to detach the entity, replace the child instance, then reattach and try to save. In my attempts with this approach the exception went away, but the data did not persist to the database. Maybe you'll have better luck with it than I did...
context.Entry(devblogInDb).State = EntityState.Detached;
devblogInDb.Fixs = devblog.Fixs;
devblogInDb.News = devblog.News;
devblogInDb.Removes = devblog.Removes;
devblogInDb.Updates = devblog.Updates;
devblogInDb.PatchName = devblog.PatchName;
context.Entry(devblogInDb).State = EntityState.Modified;
A Bit Further
As my original answer suggests this has been a longstanding issue with EF Core. This issue was supposed to be fixed in EF Core 2.1, however, the OP of my original answer was using the release candidate for EF Core 2.1. Apparently this issue either didn't make the cut or the fixes put in place still have a few bugs in them (it is just a release candidate after all...)
For a great read on this topic Julie Lerman posted an article in the MSDN magazine that you can find here. She also has a few alternative solutions that you could use as well.
Update: Ahh! Dumb error: I had two instances of a Repository available, and, our implemntation requires that we provide a connection string for each Repo, so the two Repos were pointing to different databases, and I was adding an entity from one DB to another, and its id probably could not be found in the updated DB.
I'm running .Net MVC 4.0, with EF 5. We implement a repository pattern. In the following Delete method of a WebApi controller I have the following code:
int userID = UserHelper.GetCurrentUserID();
DateTime now = DateTime.UtcNow;
ExhibitLinkRepository el = new ExhibitLinkRepository();
el.setCase = caseID;
ExhibitLink link = el.All.SingleOrDefault(l => l.id == id);
//Mark Link Deleted
link.usermodified_id = userID;
link.datetimemodified = now;
link.deleted_flag = true;
exhibitLinkRepository.InsertOrUpdate(link);
exhibitLinkRepository.SaveChanges();
Where exhibitLinkRepository.InsertOrUpdate:
public void InsertOrUpdate(ExhibitLink exhibitLink)
{
if (exhibitLink.id == default(int))
{
// New entity
context.ExhibitLinks.Add(exhibitLink);
}
else
{
// Existing entity
context.Entry(exhibitLink).State = EntityState.Modified;
}
}
When I invoke context.SaveChanges() I get the dreaded:
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.
Now, the ExhibitLink table has foreign key constraints as follows:
There happen to be triggers on the underlying DB for some of these related tables, but disabling them did not change the outcome.
I just don't get it. Any ideas?
UPDATE (2010-12-21): Completely rewrote this question based on tests that I've been doing. Also, this used to be a POCO specific question, but it turns out that my question isn't necessarily POCO specific.
I'm using Entity Framework and I've got a timestamp column in my database table that should be used to track changes for optimistic concurrency. I've set the concurrency mode for this property in the Entity Designer to "Fixed" and I'm getting inconsistent results. Here are a couple of simplified scenarios that demonstrate that concurrency checking works in one scenario but not in another.
Successfully throws OptimisticConcurrencyException:
If I attach a disconnected entity, then SaveChanges will throw an OptimisticConcurrencyException if there is a timestamp conflict:
[HttpPost]
public ActionResult Index(Person person) {
_context.People.Attach(person);
var state = _context.ObjectStateManager.GetObjectStateEntry(person);
state.ChangeState(System.Data.EntityState.Modified);
_context.SaveChanges();
return RedirectToAction("Index");
}
Does not throw OptimisticConcurrencyException:
On the other hand, if I retrieve a new copy of my entity from the database and I do a partial update on some fields, and then call SaveChanges(), then even though there is a timestamp conflict, I don't get an OptimisticConcurrencyException:
[HttpPost]
public ActionResult Index(Person person) {
var currentPerson = _context.People.Where(x => x.Id == person.Id).First();
currentPerson.Name = person.Name;
// currentPerson.VerColm == [0,0,0,0,0,0,15,167]
// person.VerColm == [0,0,0,0,0,0,15,166]
currentPerson.VerColm = person.VerColm;
// in POCO, currentPerson.VerColm == [0,0,0,0,0,0,15,166]
// in non-POCO, currentPerson.VerColm doesn't change and is still [0,0,0,0,0,0,15,167]
_context.SaveChanges();
return RedirectToAction("Index");
}
Based on SQL Profiler, it looks like Entity Framework is ignoring the new VerColm (which is the timestamp property) and instead using the originally loaded VerColm. Because of this, it will never throw an OptimisticConcurrencyException.
UPDATE: Adding additional info per Jan's request:
Note that I also added comments to the above code to coincide with what I see in my controller action while working through this example.
This is the value of the VerColm in my DataBase prior to the update: 0x0000000000000FA7
Here is what SQL Profiler shows when doing the update:
exec sp_executesql N'update [dbo].[People]
set [Name] = #0
where (([Id] = #1) and ([VerColm] = #2))
select [VerColm]
from [dbo].[People]
where ##ROWCOUNT > 0 and [Id] = #1',N'#0 nvarchar(50),#1 int,#2 binary(8)',#0=N'hello',#1=1,#2=0x0000000000000FA7
Note that #2 should have been 0x0000000000000FA6, but it's 0x0000000000000FA7
Here is the VerColm in my DataBase after the update: 0x0000000000000FA8
Does anyone know how I can work around this problem? I'd like Entity Framework to throw an exception when I update an existing entity and there's a timestamp conflict.
Thanks
Explanation
The reason why you aren't getting the expected OptimisticConcurrencyException on your second code example is due to the manner EF checks concurrency:
When you retrieve entities by querying your db, EF remembers the value of all with ConcurrencyMode.Fixed marked properties by the time of querying as the original, unmodified values.
Then you change some properties (including the Fixed marked ones) and call SaveChanges() on your DataContext.
EF checks for concurrent updates by comparing the current values of all Fixed marked db columns with the original, unmodified values of the Fixed marked properties.
The key point here is that EF treats the update of you timestamp property as a normal data property update. The behavior you see is by design.
Solution/Workaround
To workaround you have the following options:
Use your first approach: Don't requery the db for your entity but Attach the recreated entity to your context.
Fake your timestamp value to be the current db value, so that the EF concurrency check uses your supplied value like shown below (see also this answer on a similar question):
var currentPerson = _context.People.Where(x => x.Id == person.Id).First();
currentPerson.VerColm = person.VerColm; // set timestamp value
var ose = _context.ObjectStateManager.GetObjectStateEntry(currentPerson);
ose.AcceptChanges(); // pretend object is unchanged
currentPerson.Name = person.Name; // assign other data properties
_context.SaveChanges();
You can check for concurrency yourself by comparing your timestamp value to the requeried timestamp value:
var currentPerson = _context.People.Where(x => x.Id == person.Id).First();
if (currentPerson.VerColm != person.VerColm)
{
throw new OptimisticConcurrencyException();
}
currentPerson.Name = person.Name; // assign other data properties
_context.SaveChanges();
Here is another approach that is a bit more generic and fits in the data layer:
// if any timestamps have changed, throw concurrency exception
var changed = this.ChangeTracker.Entries<>()
.Any(x => !x.CurrentValues.GetValue<byte[]>("Timestamp").SequenceEqual(
x.OriginalValues.GetValue<byte[]>("Timestamp")));
if (changed) throw new OptimisticConcurrencyException();
this.SaveChanges();
It just checks to see if the TimeStamp has changed and throws concurrency exception.
If it's EF Code first, then use code similar to below code. This will change the original TimeStamp loaded from db to the one from UI and will ensure OptimisticConcurrencyEception occurs.
db.Entry(request).OriginalValues["Timestamp"] = TimeStamp;
I have modified #JarrettV solution to work with Entity Framework Core. Right now it is iterating through all modified entries in context and looking for any mismatch in property marked as concurrency token. Works for TimeStamp (RowVersion) as well:
private void ThrowIfInvalidConcurrencyToken()
{
foreach (var entry in _context.ChangeTracker.Entries())
{
if (entry.State == EntityState.Unchanged) continue;
foreach (var entryProperty in entry.Properties)
{
if (!entryProperty.IsModified || !entryProperty.Metadata.IsConcurrencyToken) continue;
if (entryProperty.OriginalValue != entryProperty.CurrentValue)
{
throw new DbUpdateConcurrencyException(
$"Entity {entry.Metadata.Name} has been modified by another process",
new List<IUpdateEntry>()
{
entry.GetInfrastructure()
});
}
}
}
}
And we need only to invoke this method before we save changes in EF context:
public async Task SaveChangesAsync(CancellationToken cancellationToken)
{
ThrowIfInvalidConcurrencyToken();
await _context.SaveChangesAsync(cancellationToken);
}