【问题标题】:Implementing if-not-exists-insert using Entity Framework without race conditions使用没有竞争条件的实体框架实现 if-not-exists-insert
【发布时间】:2011-04-23 04:05:04
【问题描述】:

使用 LINQ-to-Entities 4.0,是否有正确的模式或构造来安全地实现“如果不存在则插入”?

例如,我目前有一个跟踪“用户收藏”的表格 - 用户可以在他们的收藏列表中添加或删除文章。

基础表不是真正的多对多关系,而是跟踪一些附加信息,例如添加收藏夹的日期。

CREATE TABLE UserFavorite
(
    FavoriteId int not null identity(1,1) primary key,
    UserId int not null,
    ArticleId int not null
);

CREATE UNIQUE INDEX IX_UserFavorite_1 ON UserFavorite (UserId, ArticleId);

根据需要插入具有相同用户/文章对的两个收藏夹会导致重复键错误。

我目前已经使用 C# 在数据层实现了“如果不存在则插入”逻辑:

if (!entities.FavoriteArticles.Any(
        f => f.UserId == userId && 
        f.ArticleId == articleId))
{
    FavoriteArticle favorite = new FavoriteArticle();
    favorite.UserId = userId;
    favorite.ArticleId = articleId;
    favorite.DateAdded = DateTime.Now;

    Entities.AddToFavoriteArticles(favorite);
    Entities.SaveChanges();
}

这个实现的问题是它容易受到竞争条件的影响。例如,如果用户双击“添加到收藏夹”链接,则可以向服务器发送两个请求。第一个请求成功,而第二个请求(用户看到的那个)失败,并出现 UpdateException 包装了重复键错误的 SqlException。

通过 T-SQL 存储过程,我可以使用带有锁定提示的事务来确保永远不会发生竞争条件。是否有一种干净的方法可以避免实体框架中的竞争条件,而无需求助于存储过程或盲目地吞下异常?

【问题讨论】:

  • 你看过using (var tran = new TransactionScope())吗?
  • ^ 将代码包装在 TransactionScope 中不起作用。 @ShadowChaser 你有没有找到解决这个问题的方法?

标签: entity-framework linq-to-entities insert


【解决方案1】:

您还可以编写一个使用 sql 2005+ 中的一些新技巧的存储过程

在更新语句中使用您组合的唯一 ID (userID + articleID),然后使用 @@RowCount 函数查看行数是否 > 0,如果为 1(或更多),则更新已找到与您的 userID 匹配的行和 ArticleID,如果是 0,那么你就可以插入了。

例如

更新 tablex set userID = @UserID, ArticleID = @ArticleID(你可以在这里有更多的属性,只要 where 拥有一个组合的唯一 ID)其中 userID = @UserID 和 ArticleID = @ArticleID

如果 (@@RowCount = 0) 开始 插入表格... 结束

最重要的是,这一切都在一次调用中完成,因此您不必先比较数据,然后再确定是否应该插入。当然,它会停止任何重复的插入并且不会抛出任何错误(优雅地?)

【讨论】:

  • 这仍然会导致竞争条件。假设您尝试更新并且没有更新。当您检查@@RowCount 并实际执行插入时,可能已经插入了另一行。如果你有唯一的约束,这会抛出一个错误,否则你有重复的数据。
  • @@RowCount 将仅从您的最后一条语句中获取受影响的行。它对您当前上下文中的最后一条语句有效,其他用户会话对此没有影响。
【解决方案2】:

您可以尝试将其包装在结合“著名”try/catch 模式的事务中:

using (var scope = new TransactionScope())
try
{
//...do your thing...
scope.Complete();
}
catch (UpdateException ex)
{
// here the second request ends up...
}

【讨论】:

  • 我看不出使用显式事务会带来什么。据我所知,无论有没有交易,行为都会完全相同。你能给我解释一下吗?
  • 在存储过程中使用合并显然效率较低,但这似乎是 EF 中的最佳选择,前提是事务类型设置正确。我们最近刚刚为一个比我原来的问题更复杂的项目使用了类似的解决方案 - 一个必须处理合并冲突的完整保存命令。
  • 我也看不到封闭的 TransactionScope 添加了什么。如果存在竞争条件,无论如何你都会得到一个 UpdateException,不是吗?
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-06-05
相关资源
最近更新 更多