[英]Why RowVersion property is not raising optimistic concurrency exception when two concurrent changes occur?
[英]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.