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

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还比较新,希望确保我正确地使用它。

感谢您能提供任何帮助。


你的代码当前存在哪些“低效”的问题? - user47589
2
也许什么都没有。我不知道自己不知道什么。 - Nugs
3个回答

7

EF 的设计者们已经为使用它的开发人员留下了处理断开实体的工作。因此,没有“正确”的方式——一切都取决于使用情况和实体模型。

由于您似乎正在强制更新现有记录(通过将 DateModified 设置为 DateTime.UtcNow),因此无需将现有数据加载到上下文中。只需使用单个数据库查询获取现有实体 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.Accounts.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移出循环,应该可以为此场景提供最佳性能。


很好!谢谢你的回复,伊万! - Nugs
小注:由于您已经设置了UTC日期,因此ToUniversalTime()是多余的。 - Orry

2

我们可以使用upserting(更新或插入)一次调用中的所有帐户来使Ivan的答案表现得更好。

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

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

Ivan的代码只需一个upsert调用即可处理所有帐户:

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

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

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


免责声明: 我是项目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;
});

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接