简体   繁体   English

乐观并发:IsConcurrencyToken和RowVersion

[英]Optimistic concurrency: IsConcurrencyToken and RowVersion

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 Table per Type (TPT) (using inheritance). 我的所有实体都被映射为Table per Type (TPT) (使用继承)。 I soon learned that there is a problem when using columns of type RowVersion with inheritance on Entity Framework: 我很快就了解到在实体框架上使用RowVersion类型的列并继承时存在问题:

Product

Id INT IDENTITY PRIMARY KEY
RowVersion ROWVERSION

Car (inherits Product records)

Color TYNIINT NOT NULL,
AnotherProperty....   

If I update a record of the Car table the RowVersion column from Product table will not be updated. 如果我更新Car表的记录,则不会更新Product表中的RowVersion列。

I plan to use a column of type datetime2 (7) in Product and update it manually if any records of the tables that inherit this table are modified. 我计划在Product使用datetime2 (7)类型的列,并且如果修改了继承此表的表的任何记录,则手动更新它。

I think I'm reinventing the wheel. 我想我正在重新发明轮子。

Is there another way to use the optimistic concurrency strategy with ROWVERSION when using Table per Type (TPT) in Entity Framework? 在Entity Framework中使用Table per Type (TPT) ,是否有另一种方法可以将乐观并发策略与ROWVERSION使用?

Edit 编辑

My mapping: 我的映射:

class Product
{
    int Id { get; set; }
    string Name { get; set; }
    byte[] RowVersion { get; set; }
}

class Car : Product
{
    int Color { get; set; }
}

CodeFirst conventions. CodeFirst约定。

Only the RowVersion property on Product entity has custom definitions: 只有Product实体上的RowVersion属性具有自定义定义:

modelBuilder.Entity<Product>() 
    .Property(t => t.RowVersion) 
    .IsConcurrencyToken();

Both in EF6 and EF-core, when working with Sql Server, you have to use this mapping: 在EF6和EF-core中,使用Sql Server时,必须使用此映射:

modelBuilder.Entity<Product>() 
.Property(t => t.RowVersion) 
.IsRowVersion(); // Not: IsConcurrencyToken

IsConcurrencyToken does configure a property as concurrency token, but (when using it for a byte[] property) IsConcurrencyToken确实将属性配置为并发令牌,但(将其用于byte[]属性时)

  • the data type is varbinary(max) 数据类型是varbinary(max)
  • its value is always null if you don't initialize it 如果不初始化,则其值始终为null
  • its value is not auto-incremented when a record is updated. 更新记录时,其值不会自动递增。

IsRowVersion on the other hand, 另一方面, IsRowVersion

  • has datatype rowversion (in Sql Server, or timestamp in earlier versions), so 有数据类型rowversion (在Sql Server中,或早期版本中的timestamp ),所以
  • its value is never null, and 它的值永远不会为空,并且
  • its value is always auto-incremented when a record is updated. 更新记录时,其值始终自动递增。
  • and it automatically configures the property to be an optimistic concurrency token. 它会自动将属性配置为乐观并发令牌。

Now when you update a Car you'll see two update statements: 现在,当您更新Car您将看到两个更新声明:

DECLARE @p int
UPDATE [dbo].[Product]
SET @p = 0
WHERE (([Id] = @0) AND ([Rowversion] = @1))
SELECT [Rowversion]
FROM [dbo].[Product]
WHERE @@ROWCOUNT > 0 AND [Id] = @0

UPDATE [dbo].[Car]
SET ...

The first statement doesn't update anything, but it increments the rowversion, and it will throw a concurrency exception if the rowversion was changed in-between. 第一个语句不会更新任何内容,但会增加rowversion,如果在其间更改了rowversion,它将引发并发异常。

The [System.ComponentModel.DataAnnotations.Schema.Timestamp] attribute is the data annotations equivalent of IsRowVersion() : [System.ComponentModel.DataAnnotations.Schema.Timestamp]属性是等效于IsRowVersion()的数据注释:

[Timestamp]
public byte[] RowVersion { get; set; }

After a bit of investigating I was able to use IsConcurrencyToken on a byte[8] column called RowVersion in Entity Framework 6. 经过一些调查后,我能够在Entity Framework 6中名为RowVersion的byte [8]列上使用IsConcurrencyToken。

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()! 因为我们想在DB2中使用相同的数据类型(数据库本身没有rowversion),所以我们不能使用选项IsRowVersion()!

I investigated a little bit further how to work with IsConcurrencyToken. 我进一步研究了如何使用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<Guid>,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. IConcurrencyEnabled用于标识具有需要特殊处理的rowversion的实体。

I used fluent API to configure the modelbuilder: 我使用流畅的API来配置模型构建器:

    public class ProductConfiguration : EntityTypeConfiguration<Product>
{
    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: 最后,我在派生的DBContext类中添加了一个方法,以便在调用base.SaveChanges之前更新字段:

        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<byte[]>("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! 大多数人遇到的问题是,在设置实体的值之后,我们总是得到一个UpdateDBConcurrencyException,因为OriginalValue已经改变了......即使它没有!

The reason is that for a byte[] both original and currentValue change if you set the CurrentValue alone (?? strange and unexpected behavior). 原因是对于byte [],如果单独设置CurrentValue,则original和currentValue都会发生变化(奇怪且意外的行为)。

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! 所以我在更新rowversion之前再次将OriginalValue设置为原始值...同时我复制数组以避免引用相同的字节数组!

Attention: Here I use an incremental approach to change the rowversion, you are free to use your own strategy to fill in this value. 注意:这里我使用增量方法来更改rowversion,您可以自由地使用自己的策略来填充此值。 (Random or time-based) (随机或基于时间)

The problem is not how you are setup. 问题不在于您的设置方式。 What is happening is that the OriginalValue of your RowVersion entry is set to the new value as soon as you pull it out of the Context. 发生的事情是,只要将RowVersion条目的OriginalValue拉出Context,它就会设置为新值。

 var carInstance = dbContext.Cars.First();
 carInstance.RowVersion = carDTO.RowVerison;
 carInstance.Color = carDTO.Color ;


 var entry = dbContext.Entry(carInstance); //Can also come from ChangeTrack in override of SaveChanges (to do it automatically)     

 entry.Property(e => e.RowVersion)
                    .OriginalValue = entry.Entity.RowVersion;

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM