使用EF6执行UPSERT的更有效方法

时间:2017-07-13 16:57:53

标签: c# entity-framework-6

我有以下代码块,它基本上检查数据库中实体的存在并将其加载到上下文以进行更新,或者如果它不存在则添加新实体。

using (var db = new Entities.DB.DConn())
{
    //...
    foreach (Account account in accounts)
    {
        bool isNewRecord = false;
        Entities.DB.Account dlAccount = new Entities.DB.Account();
        Entities.DB.Account exisitngAcct = db.Accounts.Where(x => x.GId == dlG.Id).FirstOrDefault(); //x.GId is NOT ad primary key
        if (exisitngAcct != null)
        {
            dlAccount = exisitngAcct;
            isNewRecord = true;
        }

        dlAccount.GId = dlG.Id;
        dlAccount.AccountName = account.NameAtFI;
        dlAccount.AccountNumber = account.AcctNumber;
        dlAccount.AcctType = account.AcctType;
        dlAccount.AsOfDate = account.DateCreated;
        dlAccount.IsDeleted = false;
        dlAccount.DateModified = DateTime.UtcNow.ToUniversalTime();

        if (isNewRecord)
        {
            dldb.Accounts.Add(dlAccount);
        }

        db.SaveChanges();
    }
}

我一直在做关于将实体附加到上下文和使用EntityState的大量研究,但我只是不知道如何在我的示例中编写代码。

有人可以帮助向我展示一种更有效的方法来执行与上述相同的操作吗?我对EF很新,并希望确保我正确使用它。

感谢您提供的任何帮助。

3 个答案:

答案 0 :(得分:3)

EF的设计人员已经为使用它的开发人员留下了断开连接的实体。因此,没有“正确”的方式 - 一切都取决于用例和实体模型。

由于您似乎正在强制更新现有记录(通过设置DateModified),因此无需将现有数据加载到上下文中。只需将单个数据库访问的现有实体PK用作添加或更新标准就足够了:

using (var db = new Entities.DB.DConn())
{
    //...
    var accountIds = accounts.Select(x => x.GId); // variable required by EF6 Contains translation
    var existingAccountIds = new HashSet<GId_Type>(
        db.Accouns.Where(x => accountIds.Contains(x.GId).Select(x => x.GId));
    foreach (Account account in accounts)
    {
        var dlAccount = new Entities.DB.Account();
        dlAccount.GId = account.GId;
        dlAccount.AccountName = account.NameAtFI;
        dlAccount.AccountNumber = account.AcctNumber;
        dlAccount.AcctType = account.AcctType;
        dlAccount.AsOfDate = account.DateCreated;
        dlAccount.IsDeleted = false;
        dlAccount.DateModified = DateTime.UtcNow.ToUniversalTime();    
        if (existingAccountIds.Contains(dlAccount.GId))
            db.Entry(dlAccount).State = EntityState.Modified; // update
        else
            db.Accounts.Add(dlAccount); // insert
    }
    db.SaveChanges();
}

(将GId_Type替换为GId的类型,例如intGuid等。

这一点,以及在循环外部移动SaveChanges,可以为这种情况提供最佳性能。

答案 1 :(得分:1)

使用一次呼叫中的所有帐户更新(更新或插入),我们可以使Ivan的回答更加出色。

我正在使用名为EFCore.BulkExtensions的免费软件包,其中包括BulkInsertOrUpdate(list)方法:https://github.com/borisdj/EFCore.BulkExtensions

(此软件包在Microsoft ef扩展页面中列出:https://docs.microsoft.com/en-us/ef/core/extensions/

对所有帐户进行一次upsert调用的Ivans代码:

using (var db = new Entities.DB.DConn())
{
    //...
    var accountIds = accounts.Select(x => x.GId); // variable required by EF6 Contains translation
    var existingAccountIds = new HashSet<GId_Type>(
        db.Accouns.Where(x => accountIds.Contains(x.GId).Select(x => x.GId));

    var dlAccounts = new List<Entities.DB.Account>();   

    foreach (Account account in accounts)
    {
        var dlAccount = new Entities.DB.Account();
        dlAccount.GId = account.GId;
        dlAccount.AccountName = account.NameAtFI;
        dlAccount.AccountNumber = account.AcctNumber;
        dlAccount.AcctType = account.AcctType;
        dlAccount.AsOfDate = account.DateCreated;
        dlAccount.IsDeleted = false;
        dlAccount.DateModified = DateTime.UtcNow.ToUniversalTime();   

        //Add the updated account to a list
        dlAccounts.Add(dlAccount);          

    }

    //upsert dlAccounts in ONE call
    db.BulkInsertOrUpdate(dlAccounts);

    db.SaveChanges();
}

注意:BulkInsertOrUpdate假定该帐户具有主键集。 在上面的示例中,它可能是AccountNumber。

答案 2 :(得分:0)

有一些方法可以提高性能。例如,使用@Ivan解决方案一次检索多个帐户以减少数据库往返次数

但是,SaveChanges方法仍然会为需要添加或更新的每个实体进行一次数据库往返,这是 INSANELY 慢。

免责声明:我是该项目的所有者Entity Framework Extensions

此库不是免费的。但它是执行upsert操作的最有效方法。

除了批量合并(Upsert)之外,此库还允许您执行所有批量操作:

  • BulkSaveChanges
  • BulkInsert
  • BulkUpdate
  • BulkDelete
  • BulkMerge
  • BulkSynchronize

示例:

// Easy to use
context.BulkSaveChanges();

// Easy to customize
context.BulkSaveChanges(bulk => bulk.BatchSize = 100);

// Perform Bulk Operations
context.BulkDelete(customers);
context.BulkInsert(customers);
context.BulkUpdate(customers);

// Customize Primary Key
context.BulkMerge(customers, operation => {
   operation.ColumnPrimaryKeyExpression = 
        customer => customer.Code;
});