并发执行C#代码会导致重复的数据库条目

时间:2017-09-13 10:20:04

标签: c# asp.net entity-framework

好吧,我有这段代码:

var companyId = 1;
var categoryId = 1;
var item = _dbContext.FirstOrDefault(i => 
     i.CompanyId == companyId && i.CategoryId == categoryId);


if (item == null) {
    var newItem = new Item() 
    {
        CategoryId = companyId,
        CompanyId = categoryId,
        // some other properties
    };
    _dbContext.Items.Add(newItem);
    _dbContext.SaveChanges();
}
else 
{
    // update some properties of the item (not CompanyId or CategoryId)
    item.xyz = "new Value"
    _dbContext.Items.Update(item);
    _dbContext.SaveChanges();
}

这是我访问Items表的唯一代码。我想确保没有为CompanyId-CategoryId组合制作重复的条目,也就是说,这种组合应该是唯一的。但即使代码在插入之前检查是否存在,我如何才能在数据库中找到重复的条目?

请忽略其他所有内容(架构,使用DB Context等,因为我刚刚编写了这个简单的代码来理解这个问题)。另外,我知道我可以在数据库级别实现这个功能并使列组合独一无二,但有没有办法在应用程序代码中执行此操作(在C#中)?我正在使用EF,如果这很重要的话。为什么会出现这个问题,多线程?

请帮助我理解这个问题或者给我一些指示,以便我在这方面应该进一步阅读。感谢。

2 个答案:

答案 0 :(得分:1)

如果您想在应用程序代码中执行此操作,那么您将不得不在某处强制执行单线程。一种方法是使用锁定来生成一段代码,一次只能输入一个线程。然后,您将重新检查记录的存在,如果仍然为,则执行您的插入。它看起来像这样:

// declared at class level
private static readonly object ItemCreationSyncLock = new object();

public void MyMethodThatCreatesAnItem()
{
    // ... setup code ...
    var item = _dbContext.Items.FirstOrDefault(itm => item.Name == criteria);
    if(item == null)
    {
        lock(ItemCreationSyncLock)
        {
            item = _dbContext.Items.FirstOfDefault(itm => item.Name == criteria);
            if(item == null)
            {
                item = new Item { Name = criteria };
                _dbContext.Items.Add(item);
                _dbContext.SaveChanges();
            }
        }
    }
}

所以我介绍了""避免应用程序代码中的重复条目,但重要的是要意识到,即使作为备份,您仍然希望在数据库级别强制执行唯一约束,如果您确实希望确保重复项不存在。通过将应用程序扩展到多个实例,无论是使用Web园/场还是扩展到Azure上的其他实例,我都能够击败我刚刚向您展示的后卫。

答案 1 :(得分:1)

只有拥有一台Web服务器才能在C#中使用锁定。如果您有一个服务器场,那么您的应用程序的每个实例都将拥有自己的ItemCreationSyncLock。

要在数据库中创建同步锁,对于SQL Server就像这样简单:

    using (var db = new Db())
    using (var tran = db.Database.BeginTransaction())
    {
        db.Database.ExecuteSqlCommand("exec sp_getapplock 'MyAppLock','exclusive';");

        //only one thread across all application instances will execute this code at one time

        tran.Commit();
    }