.net SqlCommand.ExecuteOnQuery中受影响行的EF eqivalent

.net SqlCommand.ExecuteOnQuery中受影响行的EF eqivalent,.net,entity-framework-4,.net,Entity Framework 4,在审批工作流中,我希望确保提醒电子邮件只发送一次 使用SqlCommand.ExecuteOnQuery,我可以通过测试返回值来确保这一点。 使用EF的推荐解决方案是什么? 根据文档ObjectContext.SaveChanges不会返回等效值 SqlCommand示例: (TransactionScope用于在SendMail失败时回滚数据库更新。) 事实上,可以通过调用ObjectContext.SaveChanges()推断出受影响行的确切数量。 如果您查看,您将看到: public

在审批工作流中,我希望确保提醒电子邮件只发送一次

使用SqlCommand.ExecuteOnQuery,我可以通过测试返回值来确保这一点。 使用EF的推荐解决方案是什么? 根据文档ObjectContext.SaveChanges不会返回等效值

SqlCommand示例: (TransactionScope用于在SendMail失败时回滚数据库更新。)


事实上,可以通过调用ObjectContext.SaveChanges()推断出受影响行的确切数量。

如果您查看,您将看到:

public int SaveChanges()
  • 返回值:调用SaveChanges时处于添加、修改或删除状态的对象数
  • “SaveChanges在事务中运行。如果任何脏ObjectStateEntry对象无法持久化,SaveChanges将回滚该事务并引发异常。”
  • (1) (2)基本上意味着,如果对SaveChanges()的调用已成功完成,并且没有任何异常,那么EF保证返回值准确反映已修改的对象数。

    因此,您需要做的就是:
    试试看{
    //尝试保存更改,这可能会导致冲突。
    int num=context.SaveChanges();
    SendMail();
    }
    捕获(优化并发异常){
    //使用ClientWins刷新实体
    Refresh(RefreshMode.ClientWins,yourentyobject);
    //再次保存更改;
    SaveChanges();
    }
    
    调用Refresh with ClientWins时,它执行查询以检索当前 数据库中此实体的值,包括新的时间戳。因此,所有原始字段值都已更新,以反映最新的数据库值,因此我们可以再次安全地尝试SaveChanges()。

    更新您的问题:

    任务是:只有当我能够将状态从“已创建”更改为“已提醒”时,才发送电子邮件。因此,在处理OptimisticConcurrencyException时强制执行SaveChanges是没有意义的。如果更改状态导致异常,则错误处理程序应退出,否则重试整个任务(读取和保存)。如果乐观concurreny是通过RowVersion列启用的,而不是仅通过状态启用的,我该如何做到这一点?

    好的,每次修改一行时,rowversion字段都会自动更新,因此在您的情况下,最好关闭rowversion上的并发模式,并为State属性打开它,这样您的代码将非常简单:
    试试看{
    SaveChanges();
    SendMail();
    }
    捕获(优化并发异常){
    //RowVersion并发模式=已修复
    //状态并发模式=无
    //如果它到达这里,意味着状态已经改变;
    //所以除了抛出异常,你什么都不做
    投掷;
    }
    

    但是,如果您只想为rowversion属性(如您所提到的)设置Concurrency Mode=Fixed,则这意味着您可能会在任何字段(包括状态)上获得OptimisticConcurrency Exception for change,因此执行此任务需要做更多的工作:
    试试看{
    ctx.SaveChanges();
    发送邮件;
    }
    捕获(优化并发异常){
    //RowVersion并发模式=已修复
    //状态并发模式=无
    //如果记录在此处,则表示任何/所有字段都已更改
    //所以我们需要看看它是否是州政府:
    ctx.Refresh(RefreshMode.ClientWins,批准);
    ObjectStateEntry ose=ctx.ObjectStateManager.GetObjectStateEntry(批准);
    字符串stateValue=ose.OriginalValues[“State”].ToString();
    //如果该值仍然是“创建的”,那么其他内容应该已经更改,
    //并导致异常,因此我们可以继续保存:
    如果(stateValue==“已创建”){
    ctx.SaveChanges();
    发送邮件;
    }
    否则{
    //不,它是state,所以我们向调用方抛出异常:
    投掷;
    }
    

    与Morteza讨论后,我回答我的问题如下

    SaveRechange返回对象要更新的对象数,而不是它在商店中更新的数量。因此,它必须与OptimisticConcurrencyException一起使用,以确定更改是否成功。必须考虑的是,与所要更改的属性相比,其他属性可能会导致一个OpjyTracCurrCurryExcExcor。

    读取实体并在同一TransactionScope中更新它会导致死锁

    对于我的任务“仅当我能够将状态从“已创建”更改为“已提醒”时才发送电子邮件”,我使用以下解决方案:

    使用1:1关联将ApprovalRequest实体一分为二,在OptimisticConcurrencyException上退出,在TransactionScope中发送邮件并保存更改

    
    ApprovalRequests
      ID (PK)
      RequestedBy
      ...
      RowVersion (ConcurrencyMode=Fixed)
    
    ApprovalRequestStates
      ApprovalRequest_ID (PK, FK)
      State (ConcurrencyMode=Fixed)
    
    当心!当通过父实体引用子实体时,更新子实体也会导致父实体的DB更新-在这种情况下,会引发不必要的OptimisticConcurrencyException。
    因此,我没有使用:ApprovalRequests.ApprovalRequestStates.State=“embled”

    对我来说,文档说SaveChanges返回它打算更新的对象数,而不是它在存储中更新的对象数。我通过在SaveChanges()设置断点来验证这一点,更改SQL Server中的值,然后恢复。完全正确。如果您可以毫无例外地调用SaveChanges(),则EF保证要更新的对象数在存储区中实际得到更新,否则您将收到异常。抱歉,我没有正确阅读您的第一条注释(即使你忽略了重要的部分)。我的初始测试用例没有使用乐观并发,但在我的实际场景中,我将在大多数情况下启用它。这就留下了为什么TransactionScope会抛出死锁错误的问题。没问题。我将更新我的代码,以展示如何从并发异常中恢复,这可能会帮助您克服
    public int SaveChanges()
    try { // Try to save changes, which may cause a conflict. int num = context.SaveChanges(); SendMail(); } catch (OptimisticConcurrencyException) { //Refresh the entity, using ClientWins context.Refresh(RefreshMode.ClientWins, yourEntityObject); //SaveChanges again; context.SaveChanges(); } try { context.SaveChanges(); SendMail(); } catch (OptimisticConcurrencyException) { // RowVersion Concurrency Mode = Fixed // State Concurrency Mode = None // If it reaches here it means that the State has been changed; // so you do nothing except than throwing the exception throw; } try { ctx.SaveChanges(); SendMail; } catch (OptimisticConcurrencyException) { // RowVersion Concurrency Mode = Fixed // State Concurrency Mode = None // If it reches here it means that ANY/All field(s) has changed // So we need to see if it was State: ctx.Refresh(RefreshMode.ClientWins, approval); ObjectStateEntry ose = ctx.ObjectStateManager.GetObjectStateEntry(approval); string stateValue = ose.OriginalValues["State"].ToString(); // If the value is still "Created" then something else should have changed, // And caused the exception, so we can proceed with the Save: if (stateValue == "Created") { ctx.SaveChanges(); SendMail; } else { // Nope, it was state, so we throw the exception to the caller: throw; }
    
    ApprovalRequests
      ID (PK)
      RequestedBy
      ...
      RowVersion (ConcurrencyMode=Fixed)
    
    ApprovalRequestStates
      ApprovalRequest_ID (PK, FK)
      State (ConcurrencyMode=Fixed)
    
    
    Using ctx As New ApprovalEntities
        Dim approval = cxt.ApprovalRequests.Where ...
        Dim state = ctx.ApprovalRequestStates.
            Where(Function(r) r.ApprovalRequest_ID = approval.ID And r.State = "Created"
            ).FirstOrDefault()
        If state Is Nothing Then Exit Sub
        state.State = "Reminded"
        Threading.Thread.Sleep(3000) 
        Using scope As New TransactionScope
            Try
                ctx.SaveChanges()
                SendMail()
                scope.Complete()
            Catch ex As OptimisticConcurrencyException
                Exit Try
            End Try
        End Using
    End Using