アプリケーションで使用するデフォルトの同時実行戦略を作成しています。
私は楽観的な戦略を決めました。
私のエンティティはすべて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();
_
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
を更新すると、次の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; }
_
少し調べた後、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を元の値に再度設定しました...また、同じバイト配列を参照しないように配列をコピーします。
重要:ここでは、増分アプローチを使用して行バージョンを変更しています。独自の戦略を使用して、この値を自由に入力できます。 (ランダムまたは時間ベース)
問題は、セットアップ方法ではありません。何が起こっているのかというと、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;