实体框架更新/插入多个实体

16

我想简单介绍一下我的目标。我们在应用程序中保留了远程数据库(第三方)的本地副本。为了下载信息,我们使用一个API。目前,我们会按计划下载信息,然后将新记录插入到本地数据库中或更新现有记录。以下是目前的工作方式:

public void ProcessApiData(List<Account> apiData)
{
     // get the existing accounts from the local database
     List<Account> existingAccounts = _accountRepository.GetAllList();

     foreach(account in apiData)
     {
         // check if it already exists in the local database
         var existingAccount = existingAccounts.SingleOrDefault(a => a.AccountId == account.AccountId);

         // if its null then its a new record
         if(existingAccount == null)
         {
             _accountRepository.Insert(account);
             continue;
         }

         // else its a new record so it needs updating
         existingAccount.AccountName = account.AccountName;

         // ... continue updating the rest of the properties
     }

     CurrentUnitOfWork.SaveChanges();
}

这个方法能够正常工作,但感觉还有改进的空间。

  1. 每个实体都有一个这样的方法,它们都做同样的事情(只是更新不同的属性)或插入不同的实体。有没有办法使这更加通用?
  2. 这似乎涉及到很多数据库调用,有没有一种“批量”处理的方式?我看了一下这个包,也在其他帖子中见过它 https://github.com/loresoft/EntityFramework.Extended 但它似乎只关注于使用相同值批量更新单个属性,至少我目前是这么认为的。

如果您有任何建议可以帮助我改进这个方法,我将不胜感激。我对C#还比较新,仍在寻找最佳实践。

我正在使用 .net 4.5.2 和 Entity Framework 6.1.3,后端数据库是 MSSQL 2014

3个回答

11

8
  1. Assuming that the classes in apiData are the same as your entities, you should be able to use Attach(newAccount, originalAccount) to update an existing entity.
  2. For bulk inserts I use AddRange(listOfNewEntitities). If you have a lot of entities to insert it is advisable to batch them. Also you may want to dispose and recreate the DbContext on each batch so that it's not using too much memory.

    var accounts = new List<Account>();
    var context = new YourDbContext();
    context.Configuration.AutoDetectChangesEnabled = false;
    
    foreach (var account in apiData)
    {
        accounts.Add(account);
        if (accounts.Count % 1000 == 0) 
        // Play with this number to see what works best
        {
            context.Set<Account>().AddRange(accounts);
            accounts = new List<Account>();
            context.ChangeTracker.DetectChanges();
            context.SaveChanges();
            context?.Dispose();
            context = new YourDbContext();
        }
    }
    
    context.Set<Account>().AddRange(accounts);
    context.ChangeTracker.DetectChanges();
    context.SaveChanges();
    context?.Dispose();
    

对于批量更新,在LINQ to SQL中没有内置的功能。但是有一些库和解决方案可以解决这个问题。例如,可以使用表达式树来实现批量更新,请参见此处


2
AddRange不执行“批量插入”,而只是调用一次DetectChanges,这可以提高性能而不是添加。 - Jonathan Magnan
感谢@Hintham。我将其标记为已接受的答案,因为它帮助我很多,让我得到了最终的解决方案。 - tjackadams
没问题,很高兴能帮到你。 - Hintham

4

列表 vs. 字典

每次在列表中检查实体是否存在,这是不好的。相反,您应该创建一个字典来提高性能。

var existingAccounts = _accountRepository.GetAllList().ToDictionary(x => x.AccountID);

Account existingAccount;

if(existingAccounts.TryGetValue(account.AccountId, out existingAccount))
{
    // ...code....
}

Add vs. AddRange

当你添加多个记录时,你应该注意Add与AddRange的性能区别。

  • Add:在每个记录添加后调用DetectChanges方法
  • AddRange:在所有记录添加后调用DetectChanges方法

enter image description here

因此,在10,000个实体中,Add方法需要花费875倍的时间来简单地将实体添加到上下文中。

解决方法:

  1. 创建一个列表
  2. 将实体添加到列表中
  3. 使用AddRange和列表
  4. 保存更改
  5. 完成!

在您的情况下,您需要向存储库创建一个InsertRange方法。

EF Extended

你是对的。这个库会更新所有数据为相同的值。那不是你想要的。

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

如果你想要显著提高性能,这个库可能完美适合你的企业。

你可以轻松执行:

  • BulkSaveChanges
  • BulkInsert
  • BulkUpdate
  • BulkDelete
  • BulkMerge

例如:

public void ProcessApiData(List<Account> apiData)
{
    // Insert or Update using the primary key (AccountID)
    CurrentUnitOfWork.BulkMerge(apiData);
}

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