Warning: file_get_contents(/data/phpspider/zhask/data//catemap/2/csharp/282.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
C# 乐观并发:IsConcurrencyToken和RowVersion_C#_Sql Server_Entity Framework_Concurrency_Ef Code First - Fatal编程技术网

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
  • 更新记录时,其值不会自动递增
另一方面

  • 数据类型为
    rowversion
    (在Sql Server中,或在早期版本中为
    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;