C# 乐观并发:IsConcurrencyToken和RowVersion
我正在创建将在应用程序中使用的默认并发策略 我决定采取乐观的策略 我的所有实体都映射为每个类型的C# 乐观并发:IsConcurrencyToken和RowVersion,c#,sql-server,entity-framework,concurrency,ef-code-first,C#,Sql Server,Entity Framework,Concurrency,Ef Code First,我正在创建将在应用程序中使用的默认并发策略 我决定采取乐观的策略 我的所有实体都映射为每个类型的表(TPT)(使用继承)。我很快了解到,在实体框架上使用具有继承的RowVersion类型的列时存在一个问题: Product Id INT IDENTITY PRIMARY KEY RowVersion ROWVERSION Car (inherits Product records) Color TYNIINT NOT NULL, AnotherProperty.... 如果我更新C
表(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)
的列,并在继承此表的表的任何记录被修改时手动更新它
我想我正在重新发明轮子
在实体框架中使用每类型表(TPT)
时,是否有另一种方法可以对ROWVERSION
使用乐观并发策略
编辑
我的映射:
class Product
{
int Id { get; set; }
string Name { get; set; }
byte[] RowVersion { get; set; }
}
class Car : Product
{
int Color { get; set; }
}
CodeFirst惯例
只有产品
实体上的RowVersion属性具有自定义定义:
modelBuilder.Entity<Product>()
.Property(t => t.RowVersion)
.IsConcurrencyToken();
modelBuilder.Entity()
.Property(t=>t.RowVersion)
.IsConcurrencyToken();
在EF6和EF core中,使用Sql Server时,必须使用以下映射:
modelBuilder.Entity()
.Property(t=>t.RowVersion)
.IsRowVersion();//不是:IsConcurrencyToken
IsConcurrencyToken确实将属性配置为并发令牌,但是(将其用于字节[]
属性时)
- 数据类型为
varbinary(max)
- 如果不初始化它,它的值总是
null
- 更新记录时,其值不会自动递增
- 数据类型为
(在Sql Server中,或在早期版本中为rowversion
),因此timestamp
- 它的值从不为null,并且
- 更新记录时,其值始终自动递增
- 它会自动将属性配置为乐观并发令牌
汽车时,您将看到两条更新语句:
DECLARE@p int
更新[dbo].[产品]
设置为@p=0
其中(([Id]=@0)和([Rowversion]=@1))
选择[行版本]
来自[dbo]。[产品]
其中@@ROWCOUNT>0和[Id]=@0
更新[dbo].[Car]
设置
第一条语句不更新任何内容,但它会增加rowversion,如果在此期间更改了rowversion,则会引发并发异常
[System.ComponentModel.DataAnnotations.Schema.Timestamp]
属性是与IsRowVersion()等价的数据注释:
经过一点调查,我能够在EntityFramework6中的一个名为RowVersion的字节[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
{
字节[]行版本{get;set;}
}
公共类产品:AuditableEntity、IPProduct、IConcurrencyEnabled
{
公共字符串名
{
获得;设置;
}
公共字符串描述
{
获得;设置;
}
专用字节[]_rowVersion=新字节[8];
公共字节[]行版本
{
得到
{
返回版本;
}
设置
{
System.Array.Copy(值,_rowVersion,8);
}
}
}
IConcurrencyEnabled用于标识具有需要特殊处理的行版本的实体
我使用fluent API配置modelbuilder:
public class ProductConfiguration : EntityTypeConfiguration<Product>
{
public ProductConfiguration()
{
Property(e => e.Id).HasDatabaseGeneratedOption(DatabaseGeneratedOption.None);
Property(e => e.RowVersion).IsFixedLength().HasMaxLength(8).IsConcurrencyToken();
}
}
公共类ProductConfiguration:EntityTypeConfiguration
{
公共产品配置()
{
属性(e=>e.Id).HasDatabaseGeneratedOption(DatabaseGeneratedOption.None);
属性(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!!
}
}
}
}
public void OnBeforeSaveChanges(DbContext-DbContext)
{
foreach(dbContext.ChangeTracker.Entries()中的变量dbEntityEntry,其中(x=>x.State==EntityState.Added | | x.State==EntityState.Modified))
{
IConcurrencyEnabled entity=dbEntityEntry。实体为IConcurrencyEnabled;
如果(实体!=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=新字节[8];
System.Array.Copy(dbEntityEntry.OriginalValues.GetValue(“RowVersion”),valueBefore,8);
var值=BitConverter.ToInt64(entity.RowVersion,0);
if(value==Int64.MaxValue)
数值=1;
else值++;
var rowversion=dbEntityEntry.Property(“rowversion”);
rowversion.CurrentValue=BitConverter.GetBytes((Int64)值);
rowversion.OriginalValue=valueBefore;//这是一条神奇的线!!
}
}
}
}
大多数人遇到的问题是,在设置实体的值之后,我们总是会得到UpdateDBConcurrencyException,因为原始值已更改。。。即使没有
原因是,对于字节[],如果单独设置currentValue,则原始值和currentValue都会更改(?奇怪且意外)
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!!
}
}
}
}
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;