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