簡體   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