Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Optimistic Concurrency

I have an Entity Framework Project with several linked entities. Since it is utilized by multiple users at once I've set up a RowVersion-Field for entities which are likely to be edited by several users at once. Unfortunately I now get an OptimisticConecurrencyException every time I try to save a new entity, which is linked to an already existing entity.

Store update, insert, or delete statement affected an unexpected number of rows (0). Entities may have been modified or deleted since entities were loaded. See http://go.microsoft.com/fwlink/?LinkId=472540 for information on understanding and handling optimistic concurrency exceptions.

The problem is now that this error doesn't really give any pointers as to where the error really lies. It could either be the underlying model that is modified in the meantime, there could be a validation error on the new model or something else.

The code I use to add the new entity is as follows:

using (ctx = new DbContext())
{
    try
    {
        ctx.Samples.Add(model);
        ctx.SaveChanges();
    }
    catch (DbUpdateConcurrencyException ex)
    {
        LogManager.HandleException(ex.InnerException);
    }
}

model is the model i want to add to the database

Edit: As seen above i modified the code to ignore the update of an underlying model. Furthermore i have verified through:

ctx.Database.log = s => Debug.Write(s);

That only an insert statement is sent to the database and not an additional update statement.

INSERT [dbo].[Samples]([IDSample], [ModificationDate], [IDUser])
VALUES (@0, @1, @2) 
SELECT [RowVersion]
FROM [dbo].[Samples]
WHERE @@ROWCOUNT > 0 AND [IDSample] = @0 AND [ModificationDate] = @1

I would understand the exception if i would update an entity and the rowversion column wouldn't match, but in this case it's a completely new entity. Is there a way to see if one of the properties is malformed?

Edit2:

Instead of just trimming the milliseconds i now used DateTime.Today instead of DateTime.Now which works. Seemingly there is some problem with datetime2(4) on ModificationDate. I already made sure that ModificationDate is truncated to 4 milliseconds so there should be no parse error.

Edit3:

After switching back to DateTime.Now and trimming the milliseconds it stopped working and the entities are not longer inserted into the database. Could this be caused by the fact that the sql server has problems matching the entities based on millisecond values. I executed the EF generated SQL as seen above with some fictional values and it went through although on some occasions the query didn't return a rowversion-value. In terms of the entity framework, the client would interpret this as a return value of 0 lines and therefore call an concurrency-exception. (It should also be of note that the ModificationDate together with the IDSample is the primary key of the entity.)

Edit4:

I'm now using DateTime.Today and then add the needed precision, which works for me. This can be flagged as solved. (Altough i would have expected that EF can take care of datetime-format-conversion by itself :/)

like image 563
narain Avatar asked May 15 '15 08:05

narain


1 Answers

The question I have is where are/were you adding the DateTime? You are creating too many steps to hammer out this problem. Creating a datetime, modifying it, etc.

If you're entity is inheriting from a base class with mapped properties do your concurrency add/update in the DbContext override of SaveChanges().

Here's an example: (written without optimized syntax)

public abstract class EntityBase
{
   public int Id {get; set;}
   public DateTime CreationDate {get; set;}
   public DateTime? ModifyDate {get; set;}
   public string VersionHash {get; set;}
}
public static class EntityBaseExtensions
{
    public static void MyBaseEntityMapping<T>(this EntityTypeConfiguration<T> configuration) where T : EntityBase
    {
        configuration.HasKey(x => x.Id);
        configuration.Property(x => x.CreationDate)
                                 .IsRequired();
        configuration.Property(x => x.ModifyDate)
                                 .IsOptional();
        configuration.Property(x => x.VersionHash).IsConcurrencyToken();
    } 
}
public class MyEntity : EntityBase
{
   public string MyProperty {get; set;}
}
public class MyEntityMapping : EntityTypeConfiguration<MyEntity>
{
    public MyEntityMapping()
    {
       this.MyBaseEntityMapping();
       Property(x=>x.MyProperty).IsRequired();
    }
}

public class MyContext : DbContext
{
    ....
    public override int SaveChanges()
    {
        this.ChangeTracker.DetectChanges(); //this forces EF to compare changes to originals including references and one to many relationships, I'm in the habit of doing this.

        var context = ((IObjectContextAdapter)this).ObjectContext; //grab the underlying context
        var ostateEntries = context.ObjectStateManager.GetObjectStateEntries(EntityState.Modified | EntityState.Added); // grab the entity entries (add/remove, queried) in the current context

        var stateEntries = ostateEntries.Where(x => x.IsRelationship == false && x.Entity is EntityBase); // don't care about relationships, but has to inherit from EntityBase

        var time = DateTime.Now; //getting a date for our auditing dates

        foreach (var entry in stateEntries)
        {
            var entity = entry.Entity as EntityBase;
            if (entity != null) //redundant, but resharper still yells at you :)
            {
                if (entry.State == EntityState.Added) //could also look at Id field > 0, but this is safe enough
                {
                    entity.CreationDate = time;
                }
                entity.ModifyDate = time;
                entity.VersionHash = Guid.NewGuid().ToString().Replace("-", "").Substring(0, 10); //this an example of a simple random configuration of letters/numbers..  since the query on sql server is primarily using the primary key index, you can use whatever you want without worrying about query execution.. just don't query on the version itself!
            }
        }
        return base.SaveChanges();
    }
    ....
}
like image 176
jslat Avatar answered Oct 07 '22 15:10

jslat