乐观并发:IsConcurrencyToken和RowVersion
Posted
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了乐观并发:IsConcurrencyToken和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列将不会更新。
我计划在datetime2 (7)
中使用Product
类型的列,并且如果修改了继承此表的表的任何记录,则手动更新它。
我想我正在重新发明轮子。
在实体框架中使用ROWVERSION
时,是否有另一种方法可以使用Table per Type (TPT)
的乐观并发策略?
编辑
我的映射:
class Product
{
int Id { get; set; }
string Name { get; set; }
byte[] RowVersion { get; set; }
}
class Car : Product
{
int Color { get; set; }
}
Code First惯例。
只有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()
等效的数据注释:
1617739030
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,您可以自由地使用自己的策略来填充此值。 (随机或基于时间)
问题不在于您的设置方式。发生的事情是,只要将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;
以上是关于乐观并发:IsConcurrencyToken和RowVersion的主要内容,如果未能解决你的问题,请参考以下文章