经过一番调查,我成功地在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;
}
}
}
}
大多数人遇到的问题是,在设置实体值后,我们总是会得到一个UpdateDBConcurrencyException异常,因为OriginalValue已经改变了...即使它没有改变!
原因在于对于byte[],如果仅设置CurrentValue,则OriginalValue和CurrentValue都会更改(奇怪和意外的行为)。
所以我再次将OriginalValue设置为更新rowversion之前的原始值...
此外,我复制数组以避免引用相同的字节数组!
注意:这里我使用增量方法来更改rowversion,您可以自由选择自己的策略来填充此值(随机或基于时间)。