实体框架6 - 在插入和并发之前检查记录是否存在

时间:2014-11-18 22:22:03

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

我有以下情况:

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

像这样的东西

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

我有多个线程调用此业务逻辑功能。会发生什么:

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

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

如何实现此目的以避免异常?

我使用锁{}解决了它,但它造成了我不想要的瓶颈。

谢谢。

3 个答案:

答案 0 :(得分:5)

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

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

答案 1 :(得分: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.

因此,我们可以看到我们需要捕获SqlException26012627

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;
    }
}

答案 2 :(得分:0)

我会利用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