Optimistic concurrency: IsConcurrencyToken and RowVersion

后端 未结 3 1808
萌比男神i
萌比男神i 2020-12-01 10:56

I\'m creating the default concurrency strategy that I will use in my application.

I decided for an optimistic strategy.

All of my entities are mapped as

3条回答
  •  离开以前
    2020-12-01 11:48

    After a bit of investigating I was able to use IsConcurrencyToken on a byte[8] column called RowVersion in Entity Framework 6.

    Because we want to use the same datatype in DB2 ( which doesn't have rowversion in the database itself) we can't use the option IsRowVersion()!

    I investigated a little bit further how to work with IsConcurrencyToken.

    I did the following to achieve a solution that seems to work:

    My Model:

        public interface IConcurrencyEnabled
    {
        byte[] RowVersion { get; set; }
    }
    
      public class Product : AuditableEntity,IProduct,IConcurrencyEnabled
    {
        public string Name
        {
            get; set;
        }
        public string Description
        {
            get; set;
        }
        private byte[] _rowVersion = new byte[8];
        public byte[] RowVersion
        {
            get
            {
                return _rowVersion;
            }
    
            set
            {
                System.Array.Copy(value, _rowVersion, 8);
            }
        }
    }
    

    IConcurrencyEnabled is used to identify Entities that have a rowversion that needs special treatment.

    I used fluent API to configure the modelbuilder:

        public class ProductConfiguration : EntityTypeConfiguration
    {
        public ProductConfiguration()
        {
            Property(e => e.Id).HasDatabaseGeneratedOption(DatabaseGeneratedOption.None);
            Property(e => e.RowVersion).IsFixedLength().HasMaxLength(8).IsConcurrencyToken();
        }
    }
    

    And finally I added a method to my derived DBContext class to update the field before the base.SaveChanges is called:

            public void OnBeforeSaveChanges(DbContext dbContext)
        {
            foreach (var dbEntityEntry in dbContext.ChangeTracker.Entries().Where(x => x.State == EntityState.Added || x.State == EntityState.Modified))
            {
                IConcurrencyEnabled entity = dbEntityEntry.Entity as IConcurrencyEnabled;
                if (entity != null)
                {
    
                    if (dbEntityEntry.State == EntityState.Added)
                    {
                        var rowversion = dbEntityEntry.Property("RowVersion");
                        rowversion.CurrentValue = BitConverter.GetBytes((Int64)1);
                    }
                    else if (dbEntityEntry.State == EntityState.Modified)
                    {
                        var valueBefore = new byte[8];
                        System.Array.Copy(dbEntityEntry.OriginalValues.GetValue("RowVersion"), valueBefore, 8);
    
                        var value = BitConverter.ToInt64(entity.RowVersion, 0);
                        if (value == Int64.MaxValue)
                            value = 1;
                        else value++;
    
                        var rowversion = dbEntityEntry.Property("RowVersion");
                        rowversion.CurrentValue = BitConverter.GetBytes((Int64)value);
                        rowversion.OriginalValue = valueBefore;//This is the magic line!!
    
                    }
    
                }
            }
        }
    

    The problem most people encounter is that after setting the value of the entity, we always get a UpdateDBConcurrencyException, because the OriginalValue has changed... even if it hasn't!

    The reason is that for a byte[] both original and currentValue change if you set the CurrentValue alone (?? strange and unexpected behavior).

    So I set the OriginalValue again to the original Value before I updated the rowversion... Also I copy the array to avoid referencing the same byte-array!

    Attention: Here I use an incremental approach to change the rowversion, you are free to use your own strategy to fill in this value. (Random or time-based)

提交回复
热议问题