【问题标题】:How to handle unique constraint exception to update row after failing to insert?插入失败后如何处理唯一约束异常以更新行?
【发布时间】:2014-07-16 05:48:23
【问题描述】:

我正在尝试处理对我的实体框架应用程序的近乎同时的输入。成员(用户)可以对事物进行评分,所以我有一个他们的评分表,其中一列是成员的 ID,一列是他们正在评分的事物的 ID,一是评分,另一列是他们评分的时间.最近的评级应该覆盖早期的评级。当我收到输入时,我会检查成员是否已经对某事物进行了评分,如果有,我只需使用现有行更新评分,如果没有,我添加一个新行。我注意到,当同一用户几乎同时对同一项目进行输入时,我最终会为该用户对同一事物的两个评分。

之前我问过这个问题:How can I avoid duplicate rows from near-simultaneous SQL adds?,然后我按照建议添加了一个需要 MemberID 和 ThingID 的唯一组合的 SQL 约束,这是有道理的,但我无法让这种技术发挥作用,可能是因为我没有不知道发生异常时执行我想做的事情的语法。出现异常说违反了约束,然后我想做的是忘记尝试非法添加具有相同 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 秒的延迟,所以我很惊讶可能会报告冲突,但当我查询一行时仍然没有找到任何东西。我想这表明所有线程都失败了,因为它们同时运行并且实体框架注意到它们并且在任何成功之前都使它们失败。所以我想应该有办法通过查看所有提交并调整它们来响应异常?我不知道或看到它的语法。那么再次,有什么办法来处理呢?

更新: Paddy 在下面提出了三个很好的建议。我希望他的存储过程技术可以解决这个问题,但我仍然对这个问题的答案感兴趣。也就是说,当然应该能够通过操纵提交来响应这个异常,但是我还没有找到让它插入一行并使用最新值的语法。

【问题讨论】:

    标签: sql-server entity-framework exception-handling unique-constraint


    【解决方案1】:

    引用 Eric Lippert 的话,“如果疼,就停止做”。如果您预计会获得非常高的音量并且想要执行“插入或更新”,那么您可能需要考虑在存储过程中处理此问题,而不是使用上述方法。

    您的问题来了,因为在您调用数据库以检查是否存在与您的插入/更新之间存在一个小的差距。

    存储过程可以使用 MERGE 在表上一次执行插入或更新,确保您只会看到一个评级行,并且它将是您收到的最新更新。


    注意 - 您可以在 EF 模型中包含存储过程并使用类似的 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;
        }
    

    这可能就是您重试时似乎遇到死锁的原因,尤其是当您收到快速并发请求时。

    【讨论】:

    • 谢谢。明智的话。我已经想到了其他处理这种情况的方法,但是既然有人建议我这样做,我很好奇这样做的方法是什么。一般来说,知道如何在不中止的情况下实际处理异常似乎很有用。但是,是的,我认为使用互斥体或只允许多行并稍后清理它们对我来说会更容易。
    • 这是一个非常好的观点,所以感谢您提供的语法,但是当我按照您的建议进行测试时,我仍然会在尝试重新设置后一遍又一遍地获得唯一约束异常-提交。因此 Dispose() 似乎不会导致忘记遇到异常的提交,也不会提高函数调用级别。必须有某种方法来获取和操作挂起的更改(我已经使用 LINQ 完成了),但正如您首先所说的,取消约束并只处理代码中的额外行看起来更容易。
    • 当然,这个笑话比我大得多。 en.wikipedia.org/wiki/Smith_%26_Dale
    • @ericlippert - 正确的归属。赞一个。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2013-01-15
    • 2018-08-14
    • 2018-12-04
    • 2017-05-16
    • 2020-10-10
    • 2016-07-30
    • 1970-01-01
    相关资源
    最近更新 更多