web-dev-qa-db-ja.com

楽観的同時実行性:IsConcurrencyTokenおよびRowVersion

アプリケーションで使用するデフォルトの同時実行戦略を作成しています。

私は楽観的な戦略を決めました。

私のエンティティはすべてTable per Type (TPT)としてマップされます(継承を使用)。 Entity Frameworkで継承されたRowVersion型の列を使用すると、問題があることがすぐにわかりました。

_Product

Id INT IDENTITY PRIMARY KEY
RowVersion ROWVERSION

Car (inherits Product records)

Color TYNIINT NOT NULL,
AnotherProperty....   
_

Carテーブルのレコードを更新すると、ProductテーブルのRowVersion列は更新されません。

Productでタイプdatetime2 (7)の列を使用し、このテーブルを継承するテーブルのレコードが変更された場合は手動で更新する予定です。

私は車輪の再発明をしていると思います。

EntityFrameworkで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();
_
13

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)であるため、
  • その値がnullになることはなく、
  • レコードが更新されると、その値は常に自動インクリメントされます。
  • また、プロパティを楽観的同時実行トークンとして自動的に構成します。

Carを更新すると、次の2つの更新ステートメントが表示されます。

_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 ...
_

最初のステートメントは何も更新しませんが、行バージョンをインクリメントし、行バージョンが途中で変更された場合は同時実行例外をスローします。

_[System.ComponentModel.DataAnnotations.Schema.Timestamp]_属性は、IsRowVersion()と同等のデータ注釈です。

_[Timestamp]
public byte[] RowVersion { get; set; }
_
21
Gert Arnold

少し調べた後、Entity Framework6の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は、特別な処理が必要な行バージョンを持つエンティティを識別するために使用されます。

流暢な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!!

                }

            }
        }
    }

ほとんどの人が遭遇する問題は、エンティティの値を設定した後、OriginalValueが変更されているため、常にUpdateDBConcurrencyExceptionが発生することです...変更されていない場合でも!

その理由は、現在の値を単独で設定すると、byte []の元の値とcurrentValueの両方が変化するためです(??奇妙で予期しない動作)。

したがって、rowversionを更新する前に、OriginalValueを元の値に再度設定しました...また、同じバイト配列を参照しないように配列をコピーします。

重要:ここでは、増分アプローチを使用して行バージョンを変更しています。独自の戦略を使用して、この値を自由に入力できます。 (ランダムまたは時間ベース)

3
Schwarzie2478

問題は、セットアップ方法ではありません。何が起こっているのかというと、OriginalValueエントリのRowVersionは、コンテキストから引き出すとすぐに新しい値に設定されます。

 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;
0
Chris Lamothe