我正在尝试处理我的Entity Framework应用程序的近同步输入。会员(用户)可以对事情进行评级,因此我有一个评分表,其中一列是会员的ID,一个是他们评价的东西的ID,一个是评级,另一个是是他们评价的时间。最近的评级应该超越之前的评级。当我收到输入时,我会检查该成员是否已经评价了一个东西,如果有,我只是使用现有行更新评级,或者如果他们没有,我会添加一个新行。我注意到,当输入来自同一个用户的同一个项目几乎同时进行时,我最终为该用户提供了两个评级相同的东西。
之前我问过这个问题:How can I avoid duplicate rows from near-simultaneous SQL adds?我按照这个建议添加了一个SQL约束,要求使用MemberID和ThingID的唯一组合,这是有道理的,但是我很难让这种技术起作用,可能是因为我我不知道在发生异常时做我想做的事情的语法。出现了一个例外,说违反了约束,我想要做的就是忘记使用相同的MemberID和ThingID非法添加一行,而是获取现有的一个并简单地将值设置为稍近一点数据。但是我还没有想出一个能够做到这一点的语法。我尝试了一些事情,当我在获得异常后尝试使用SaveChanges时,我总是遇到异常 - 要么是唯一的约束仍然出现,要么我得到死锁异常。
我尝试的最新版本是这样的:
// Get the member's rating for the thing, or create it.
Member_Thing_Rating memPref = (from mip in _myEntities.Member_Thing_Rating
where mip.thingID == thingId
where mip.MemberID == memberId
select mip).FirstOrDefault();
bool RetryGet = false;
if (memPref == null)
{
using (TransactionScope txScope = new TransactionScope())
{
try
{
memPref = new Member_Thing_Rating();
memPref.MemberID = memberId;
memPref.thingID = thingId;
memPref.EffectiveDate = DateTime.Now;
_myEntities.Member_Thing_Rating.AddObject(memPref);
_myEntities.SaveChanges();
}
catch (Exception ex)
{
Thread.Sleep(750);
RetryGet = true;
}
}
if (RetryGet == true)
{
Member_Thing_Rating memPref = (from mip in _myEntities.Member_Thing_Rating
where mip.thingID == thingId
where mip.MemberID == memberId
select mip).FirstOrDefault();
}
}
在编写完上述内容后,我还尝试在函数调用中包装逻辑,因为在离开提交更改的范围时,Entity Framework似乎会清理数据库事务。因此,我没有使用TransactionScope并在与上述相同的级别管理异常,而是将整个内容包装在一个管理函数中,如下所示:
bool Succeeded = false;
while (Succeeded == false)
{
Thread.Sleep(750);
Exception Problem = AttemptToSaveMemberIngredientPreference(memberId, ingredientId, rating);
if (Problem == null)
Succeeded = true;
else
{
Exception BaseEx = Problem.GetBaseException();
}
}
但这只会导致唯一约束上的无限串异常,在更高级别的函数中永远处理。我在尝试之间有3/4秒的延迟,所以我很惊讶可能存在报告的冲突,但当我查询一行时仍然没有找到任何结果。我想这表明所有线程都失败了,因为它们同时运行,并且实体框架注意到它们全部并且在成功之前全部失败。所以我想应该有办法通过查看所有提交并调整它们来回应异常?我不知道或看到它的语法。再说一次,处理这个问题的方法是什么?
更新: 帕迪在下面提出了三条好建议。我希望他的存储过程技术可以解决这个问题,但我仍然对这个问题的答案感兴趣。也就是说,当然应该能够通过操作提交来响应此异常,但我还没有找到语法来让它插入一行并使用最新的值。
答案 0 :(得分:1)
引用Eric Lippert,"如果它疼,请停止这样做"。如果您希望获得非常高的音量,并且想要进行“插入或更新”,那么您可能需要考虑在存储过程中处理此问题而不是使用上述方法。
您的问题即将发生,因为您要求数据库检查存在与插入/更新之间存在小差距。
sproc可以使用MERGE在表格上单次传递进行插入或更新,保证您只会看到一行评级,并且它将是您收到的最新更新。
注意 - 您可以在您的EF模型中包含sproc并使用类似的EF语法调用它。
注意2 - 查看代码,在异常情况下,在休眠线程之前,不要回滚事务范围。这是一个相对较长的时间来保持交易开放,特别是当您期望非常高的交易量时。您可能希望更新代码如下:
try
{
memPref = new Member_Thing_Rating();
memPref.MemberID = memberId;
memPref.thingID = thingId;
memPref.EffectiveDate = DateTime.Now;
_myEntities.Member_Thing_Rating.AddObject(memPref);
_myEntities.SaveChanges();
txScope.Complete();
}
catch (Exception ex)
{
txScope.Dispose();
Thread.Sleep(750);
RetryGet = true;
}
这可能就是您重试时似乎遇到死锁的原因,尤其是当您收到快速并发请求时。