【问题标题】:entity framework 6 - check if record exists before insert and concurrency实体框架 6 - 在插入和并发之前检查记录是否存在
【发布时间】:2014-11-18 22:22:03
【问题描述】:

我有以下场景:

使用 ef6 的业务逻辑函数检查记录是否已存在。如果记录不存在,则将其插入到数据库中。

类似的东西

if (!product.Skus.Any(s => s.SkuCodeGroup == productInfo.SkuCodeGroup && s.SkuCode == productInfo.SkuCode))
    AddProductSku();

我有多个线程调用这个业务逻辑函数。会发生什么:

如果使用相同的参数同时调用函数,则两个实例都会检查记录是否存在 - 并且不存在。所以两个实例都插入了相同的记录。

在第一个实例上调用 context.SaveChanges() 时,一切正常。但是第二个 SaveChanges() 会抛出异常,因为记录已经存在(数据库上有唯一索引)。

如何实现这一点以避免异常?

我通过使用锁 {} 解决了这个问题,但它造成了我不想要的瓶颈。

谢谢。

【问题讨论】:

    标签: .net multithreading entity-framework entity-framework-6


    【解决方案1】:

    我们不得不处理同样的问题。如果您不想在代码中实现锁定,则确实没有好的解决方法。您还必须确保没有或将来不会有多种方式让新行进入数据库。

    我们所做的是评估异常消息,如果它是重复键错误,我们只是吃掉异常。事实上,我们甚至不首先检查该行是否存在。 SQL Server 无论如何都会为我们做这件事。因此,每次我们进行插入时,它都会节省一次查找。这种方法适用于我们和我们的应用程序。它可能适用于所有情况,也可能不适用,具体取决于您在插入后要执行的操作。

    【讨论】:

    • 这种方法的问题是一个SaveChanges()(一个事务)中有很多操作。因此,如果一个插入生成重复键,则所有插入都会失败(事务回滚)。我希望其他插入工作。
    • 如果你只是吃掉异常,它不会被抛出,因此你的事务不应该被回滚。
    • @RandyMinder 你介意给你 2 美分买这个吗? stackoverflow.com/questions/54111440/…
    • @pelican_george - 那是 4 年前的事了,我退出了软件开发业务。
    • @RandyMinder 明智的决定 =)
    【解决方案2】:

    您实际上可以捕获UpdateException 并处理响应。

    首先,以下代码将显示我们感兴趣的 SQL 错误:

    SELECT error, description
    FROM master..sysmessages
    WHERE msglangid == 1033 /* eng */
      AND description LIKE '%insert%duplicate%key%'
    ORDER BY error
    

    这显示了以下输出:

    2601  Cannot insert duplicate key row in object '%.*ls' with unique index '%.*ls'. The duplicate key value is %ls.
    2627  Violation of %ls constraint '%.*ls'. Cannot insert duplicate key in object '%.*ls'. The duplicate key value is %ls.
    

    所以,由此我们可以看出,我们需要捕获SqlException 值2601 和2627。

    try {
        using(var db = new DatabaseContext()){
            //save
            db.SaveChanges(); //Boom, error
        }
    }
    catch(UpdateException ex) {
        var sqlException = ex.InnerException as SqlException;
        if(sqlException != null && sqlException.Errors.OfType<SqlError>()
             .Any(se => se.Number == 2601 || se.Number == 2627))  /* PK or UKC violation */
        {
            // it's a dupe... do something about it, 
            //depending on business rules, maybe discard new insert and attach to existing item
        }
        else {
            // it's some other error, throw back exception
            throw;
        }
    }
    

    【讨论】:

    • 那么为什么要使用 ORM 来抽象数据库,然后再去获取该数据库的特定异常?您最好不要使用 ORM,而是使用数据库引擎的所有潜力进行直接查询。请认真一点!
    【解决方案3】:

    我会利用 Entity Framework 内置的并发处理,而不是编写自己的逻辑。

    您将并发令牌添加到数据库。通常这是一个RowVersion 字段:

    使用 FluentAPI:

    modelBuilder.Entity<OfficeAssignment>() 
        .Property(t => t.Timestamp) 
        .IsRowVersion(); 
    

    使用数据注释:

     [Timestamp]
     public byte[] RowVersion { get; set; }
    

    通常你会在出现问题时处理DbUpdateConcurrencyException:

            using (var context = new SchoolDBEntities())
            {
                try
                {
                    context.Entry(student1WithUser2).State = EntityState.Modified;
                    context.SaveChanges();
                }
                catch (DbUpdateConcurrencyException ex)
                {
                    Console.WriteLine("Optimistic Concurrency exception occured");
                }
            }
    

    参考资料:

    Configuring a concurrency token

    Handling concurrency in Entity Framework

    Optimistic concurrency patterns using Entity Framework

    编辑 刚刚意识到我误读了你的问题。正如您的问题标题所暗示的那样,您在这里并没有真正谈论并发。您只想确保记录的自然键是唯一的。方法在这里:https://stackoverflow.com/a/18736484/150342

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2015-01-07
      • 2012-03-06
      • 1970-01-01
      • 1970-01-01
      • 2014-07-02
      • 2016-10-31
      • 1970-01-01
      相关资源
      最近更新 更多