并发问题实体框架继承类

Posted

技术标签:

【中文标题】并发问题实体框架继承类【英文标题】:Concurrency issue entity framework inherited class 【发布时间】:2015-01-19 23:16:37 【问题描述】:

考虑以下课程

 [Table("Base")]
 public class Base
    
       public int Id  get; set; 
       public BaseTypes BaseTypeId get; set; 
    


 [Table("Derived")]
 public class Derived : Base
    
        public string Description  get; set; 
        public DateTime CreatedDate  get; set; 
        public byte[] Timestamp  get; set; 
    

如您所知,我们无法在派生类上添加时间戳/并发检查。所以检查并发性的建议解决方案是使用存储过程并检查过程内的并发性。您可以通过配置实体使用存储过程而不是 EF 自动生成的 SQL。像这样的

modelBuilder.Entity<Base>().MapToStoredProcedures();
modelBuilder.Entity<Derived>().MapToStoredProcedures();

在你有这样的场景之前效果很好

 var context = dbContextFactory.Create(null);
 var derived = new Derived()
     
          BaseTypeId = BaseTypes.PropertySale,
          CreatedDate = DateTime.Now.AddDays(-1),
          Description = "test "
     ;
  context.Deriveds.Add(derived);
  context.SaveChanges();

// 稍后(A)

 var  savedEntity=context.Derived.Where(x=>x.Id==derived)
  // in this place timestamp is not populated(but Id gets populated)
  savedEntity.Description = "changed";
  context.SaveChanges();

它抛出异常,因为它 savedEntity.timestamp 为空,但在数据库中它不为空(时间戳列自动填充)。 看起来插入发生后,实体框架只更新具有 DatabaseGenerated 属性的属性。

我尝试将 DatabaseGenerated(DatabaseGeneratedOption.Computed) 放在时间戳列上。它通过了实体框架模型验证。通过这样做,在插入 Timestamp 属性后会填充但问题是因为这个属性是计算列,因此它不会被发送到更新查询/过程。所以你不能测试过程中的并发性。

我还尝试为 DBUpdateCommandTree / DBFunctionCommandTree 开发一个拦截器,用于手动将时间戳属性添加到命令中,我成功地为命令添加了参数,但问题在于拦截器内部我无法访问实体本身来读取时间戳值并将其传递给过程。

仅供参考:使用这样的拦截器可以拦截数据库命令

   public class HistoryQueryInterceptor : IDbCommandTreeInterceptor
    
        public void TreeCreated(DbCommandTreeInterceptionContext interceptionContext)
        
            if (interceptionContext.OriginalResult.DataSpace == DataSpace.SSpace)
            

                var queryCommand = interceptionContext.Result as DbQueryCommandTree;
                if (queryCommand != null)
                
                    var dbContexts = interceptionContext.DbContexts;
                    var snapshotDate = DateTime.Now; //replace with SystemTime
                    var snapshotSupportDbContext =
                        dbContexts.FirstOrDefault(context => context is ISupportSnapshotViewDbContext);
                    if (snapshotSupportDbContext != null)
                    
                        snapshotDate = (snapshotSupportDbContext as ISupportSnapshotViewDbContext).SnapShotDate;
                    

                    var newQuery = queryCommand.Query.Accept(new HistoryRecordQueryVisitor(snapshotDate));
                    interceptionContext.Result = new DbQueryCommandTree(
                                                                        queryCommand.MetadataWorkspace,
                                                                        queryCommand.DataSpace,
                                                                        newQuery);
                

                var updateCommand = interceptionContext.OriginalResult as DbUpdateCommandTree;
                if (updateCommand != null)
                

                    var binding = updateCommand.Target;

                    var revisedPredicate = DbExpressionBuilder.And(updateCommand.Predicate,
                                                                    DbExpressionBuilder.Equal(
                                                                    DbExpressionBuilder.Property(DbExpressionBuilder.Variable(binding.VariableType, binding.VariableName), "Timestamp"),
                                                                    DbExpressionBuilder.Parameter(TypeUsage.CreateBinaryTypeUsage(PrimitiveType.GetEdmPrimitiveType(PrimitiveTypeKind.Binary), true), "Timestamp")));

                    interceptionContext.Result = new DbUpdateCommandTree(updateCommand.MetadataWorkspace,
                                                                       updateCommand.DataSpace,
                                                                       updateCommand.Target,
                                                                       revisedPredicate,
                                                                       new ReadOnlyCollection<DbModificationClause>(updateCommand.SetClauses),
                                                                       updateCommand.Returning);
                


            
        
    

有什么帮助吗?

【问题讨论】:

【参考方案1】:

伴侣,

这个案子比你想象的要复杂一点。 这里的问题是您无法在派生类上添加时间戳/并发检查(通过注释或 Fluent Api 或...)。当您将时间戳属性添加到派生类的属性然后建模时,它会因模型验证而失败

Type 'Derived' 定义了新的并发要求,这些要求不允许用于基本 EntitySet 类型的子类型。 这正是错误所说的意思。实体框架不支持派生类型中的并发检查。如果您添加简单的并发检查而不是时间戳,您将看到相同的错误:

由于一个很好的技术原因,我们不想将时间戳属性放在基类上。从我们的数据库的角度来看,每个表都应该有自己的时间戳。

【讨论】:

【参考方案2】:

我可能在这里错过了一些东西,但解决方案比您选择的路径更容易。如果您希望实体框架使用 Timestamp 列进行并发检查,那么您必须进行适当的配置。这意味着要么使用[Timestamp] 属性装饰属性,要么通过我更喜欢的流畅配置来完成

modelBuilder.Entity<Derived >().Property(p => p.Timestamp ).IsConcurrencyToken();

这是一个足够的解决方案吗?

【讨论】:

可能你没看可能评论,不管你如何通过attribute([Timestamp],[ConcurrencyToken])或者Fluent Api(IsTimestamp,IsConcurrencyToken)配置时间戳,都没有'不工作。这不是错误,而是设计使然(查看 Codeplex 的实体框架的来源)。当我刚开始这项任务时,我很早就经历了所有这些方式/技巧。如果您认为那行神奇的代码可以解决问题。就试一试吧。你会得到完全相同的错误。如果您查看书籍(Julia Lerman 的编程实体框架)和 MSDN 文档。它清楚地表明您必须使用存储过程【参考方案3】:

可能你没看可能评论,不管你如何通过属性([Timestamp],[ConcurrencyToken])或者Fluent Api(IsTimestamp,IsConcurrencyToken)配置时间戳,都行不通。这不是错误,它是设计使然(查看 Codeplex 的 Entity framework 的源代码)。

当我刚开始这项任务时,我在早期经历了所有这些方式/技巧。如果您认为那行神奇的代码可以解决问题。就试一试吧。你会得到完全相同的错误。

如果您查看书籍(Julia Lerman 的编程实体框架)和 MSDN 文档。它清楚地表明您必须使用存储过程。

无论如何,感谢您的宝贵时间 问候

【讨论】:

【参考方案4】:

我确实阅读了您的回答并理解为什么我在之前的回答中提出的解决方案不适用。您要在命令树拦截器中实现的目标是手动添加时间戳列的值,因为这不是由实体框架发送的,对吧?在这种情况下,您不必更改谓词,只需添加另一个 set 子句。

【讨论】:

谢谢。您可能同意,我们不能使用由 EF 自动生成的动态 SQL(因为当我们的属性没有 Timestamp 属性时它不会检查并发性)。所以我们应该添加新参数(时间戳)及其值。我认为通过修改 DbFunctionCommand 并向其添加时间戳参数,正确的位置是在拦截器内部。我可以做到这一点,问题是获取那里无法访问的实体的时间戳值。您可以访问拦截上下文,但不能访问实体实例。

以上是关于并发问题实体框架继承类的主要内容,如果未能解决你的问题,请参考以下文章

实体框架并发令牌 DateTime 类型的问题

实体框架的悲观并发

使用实体框架和存储过程进行并发检查

[Java并发编程] 并发容器框架的简单介绍

并发编程常见问答

并发编程常见问答