OptimisticConcurrencyException Does Not Work in Entity Framework In Certain Situations

13,983

Solution 1

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:

  1. Use your first approach: Don't requery the db for your entity but Attach the recreated entity to your context.

  2. 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();
    
  3. 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();
    

Solution 2

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.

Solution 3

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;
Share:
13,983

Related videos on Youtube

Johnny Oshika
Author by

Johnny Oshika

I’m a serial entrepreneur and a passionate software architect! I co-founded 3 companies (BC Jobs, Jobcast, PropertyInsight) and got lucky and sold 2 of them. I’m always learning and upgrading my skills, so if you're innovating on a cool new project, I'd love to hear from you! Message me on LinkedIn (https://www.linkedin.com/in/johnnyoshika/) or find me on GitHub (https://github.com/johnnyoshika).

Updated on October 06, 2020

Comments

  • Johnny Oshika
    Johnny Oshika over 3 years

    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

    • Morteza Manavi
      Morteza Manavi over 13 years
      Please post the code that does the saving.
    • Jan
      Jan over 13 years
      I can't reproduce this. I get an OptimisticConcurrencyException when i try to save the loaded and modified entity with a timestamp conflict. Are you sure you have a timestamp conflict? Can you please post your profiled SQL query?
    • Johnny Oshika
      Johnny Oshika over 13 years
      Hi Jan, I added additional info above per your request.
  • Johnny Oshika
    Johnny Oshika over 13 years
    Thank you for the detailed explanation.
  • Anttu
    Anttu about 11 years
    I tried this and it seems to work fine. However, I would like to throw a similar DbUpdateConcurrencyException as EF does including the Entities collection, however, there's no setter for that collection. I tried to peek into the source with ILSpy, but that didn't reveal anything useful without wading through a lot of code (which I don't have time to do). Anyone who knows how this could be done?
  • Anttu
    Anttu about 11 years
    Note: The example code doesn't work for deleted entities. Should be updated to ...Any(x => x.State == EntityState.Modified && !x.CurrentValues.GetValue<.... otherwise an InvalidOperationException is thrown if you try to issue a delete.
  • RitchieD
    RitchieD over 8 years
    I reworked this idea to fit into my DAL, works great and is simple. Thanks.