繁体   English   中英

乐观并发:IsConcurrencyToken和RowVersion

[英]Optimistic concurrency: IsConcurrencyToken and RowVersion

我正在创建我将在我的应用程序中使用的默认并发策略。

我决定采取乐观的策略。

我的所有实体都被映射为Table per Type (TPT) (使用继承)。 我很快就了解到在实体框架上使用RowVersion类型的列并继承时存在问题:

Product

Id INT IDENTITY PRIMARY KEY
RowVersion ROWVERSION

Car (inherits Product records)

Color TYNIINT NOT NULL,
AnotherProperty....   

如果我更新Car表的记录,则不会更新Product表中的RowVersion列。

我计划在Product使用datetime2 (7)类型的列,并且如果修改了继承此表的表的任何记录,则手动更新它。

我想我正在重新发明轮子。

在Entity Framework中使用Table per Type (TPT) ,是否有另一种方法可以将乐观并发策略与ROWVERSION使用?

编辑

我的映射:

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

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

CodeFirst约定。

只有Product实体上的RowVersion属性具有自定义定义:

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

在EF6和EF-core中,使用Sql Server时,必须使用此映射:

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

IsConcurrencyToken确实将属性配置为并发令牌,但(将其用于byte[]属性时)

  • 数据类型是varbinary(max)
  • 如果不初始化,则其值始终为null
  • 更新记录时,其值不会自动递增。

另一方面, IsRowVersion

  • 有数据类型rowversion (在Sql Server中,或早期版本中的timestamp ),所以
  • 它的值永远不会为空,并且
  • 更新记录时,其值始终自动递增。
  • 它会自动将属性配置为乐观并发令牌。

现在,当您更新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 ...

第一个语句不会更新任何内容,但会增加rowversion,如果在其间更改了rowversion,它将引发并发异常。

[System.ComponentModel.DataAnnotations.Schema.Timestamp]属性是等效于IsRowVersion()的数据注释:

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

经过一些调查后,我能够在Entity Framework 6中名为RowVersion的byte [8]列上使用IsConcurrencyToken。

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

我进一步研究了如何使用IsConcurrencyToken。

我做了以下工作来实现似乎有效的解决方案:

我的型号:

    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用于标识具有需要特殊处理的rowversion的实体。

我使用流畅的API来配置模型构建器:

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

最后,我在派生的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!!

                }

            }
        }
    }

大多数人遇到的问题是,在设置实体的值之后,我们总是得到一个UpdateDBConcurrencyException,因为OriginalValue已经改变了......即使它没有!

原因是对于byte [],如果单独设置CurrentValue,则original和currentValue都会发生变化(奇怪且意外的行为)。

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

注意:这里我使用增量方法来更改rowversion,您可以自由地使用自己的策略来填充此值。 (随机或基于时间)

问题不在于您的设置方式。 发生的事情是,只要将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