代码之家  ›  专栏  ›  技术社区  ›  ShadowChaser

使用不存在竞争条件的实体框架实现如果不存在

  •  15
  • ShadowChaser  · 技术社区  · 16 年前

    使用LINQ到实体4,是否有一个正确的模式或构造来安全地实现“如果不存在,那么插入”?

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

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

    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存储过程,我可以使用带有锁提示的事务来确保永远不会发生竞争条件。有没有一种干净的方法可以避免实体框架中的竞争条件,而不诉诸于存储过程或盲目地接受异常?

    2 回复  |  直到 16 年前
        1
  •  -3
  •   Henk van Dijken    15 年前

    您可以尝试将其与“著名的”try/catch模式组合在一个事务中:

    using (var scope = new TransactionScope())
    try
    {
    //...do your thing...
    scope.Complete();
    }
    catch (UpdateException ex)
    {
    // here the second request ends up...
    }
    
        2
  •  1
  •   abend    15 年前

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

    在update语句中使用组合的唯一ID(userID+articleID),然后使用@@row count函数查看行计数是否为0如果为1(或更多),更新是否找到了与userID和articleID匹配的行,如果为0,则完全可以插入。

    例如

    更新tablex set userID=@userID,ArticleID=@ArticleID(这里可以有更多的属性,只要其中包含一个组合的惟一ID),其中userID=@userID和ArticleID=@ArticleID

    如果(@RowCount=0) 开始 插入到tablex。。。 终点

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