With certain cases, the OptimisticConcurrencyException does not operate in Entity Framework.

asp.net c# concurrency entity-framework poco

Question

UPDATE (2010-12-21): Based on exams I've been taking, I completely reworked this question. Also, I thought my query was specifically related to POCO, but it turns out that it isn't.

I have a timestamp column in my database table that should be utilized to monitor changes for optimistic concurrency and I'm using Entity Framework. I'm receiving erratic results even though I've set the concurrency mode for this attribute in the Entity Designer to "Fixed." Here are a few simple examples showing when concurrency checking is effective and when it is not.

throws OptimisticConcurrencyException successfully:

SaveChanges will trigger an optimistic concurrency exception if a timestamp conflict occurs if I attach a detached entity:

    [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");
    }

OptimisticConcurrencyException is not thrown:

On the other hand, even if there is a timestamp conflict, I don't receive an OptimisticConcurrencyException if I fetch a fresh copy of my object from the database, make partial updates to certain fields, and then use SaveChanges().

    [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");
    }

According to SQL Profiler, it seems that Entity Framework is utilizing the VerColm that was first loaded rather than the updated VerColm (which is the timestamp attribute). It won't ever raise an OptimisticConcurrencyException as a result.


UPDATE: Additional information added at Jan's request:

While going through this example, you'll see that I have added comments to the code above to match what I see in my controller action.

Before the change, the VerColm in my DataBase had the following value: 0x0000000000000FA7

When doing the update, SQL Profiler displays the following:

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

Keep in mind that @2 should be 0x0000000000000FA6, but it is really 0x0000000000000FA7.

Here is the VerColm after the modification in my database: 0x0000000000000FA8


Does anybody have any suggestions for resolving this issue? If I edit an existing object and a timestamp conflict occurs, I want Entity Framework to raise an error.

Thanks

1
22
4/29/2014 2:52:49 PM

Accepted Answer

Explanation

The cause of your failure to get what you anticipatedOptimisticConcurrencyException EF checks concurrency in a certain way when it comes to your second code example:

EF keeps track of the value of all with when you get entities by querying your database.ConcurrencyMode.Fixed indicated properties are those that had not been changed as of the time of the query.

Then you alter a few properties (such as theFixed note the ones) and dialSaveChanges() based on your DataContext.

By contrasting the current values of all variables, EF looks for concurrent updates.Fixed designated db columns with the initial values of the original, unalteredFixed marked characteristics The important thing to remember is that EF interprets updating your timestamp property as updating a normal data property. The behavior that you see is intended.

Solution/Workaround

You have the following possibilities for a workaround:

  1. Use your first strategy: Instead of doing a new database query to find your object, attach the recreated entity to your context.

  2. In order for the EF concurrency check to utilize the given value as indicated below (see also this response on a related subject), pretend that your timestamp value is the current database value.

    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. By contrasting your timestamp value with the requeried timestamp value, you may determine concurrency on your own:

    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();
    
30
5/23/2017 12:07:18 PM

Popular Answer

Here is another strategy that fits in the data layer and is a little more general:

// 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();

Simply by examining if the TimeStamp has changed, it raises a concurrency error.



Related Questions





Related

Licensed under: CC-BY-SA with attribution
Not affiliated with Stack Overflow
Licensed under: CC-BY-SA with attribution
Not affiliated with Stack Overflow