【问题标题】:.NET Entity Framework Insert vs Bulk Insert.NET 实体框架插入与批量插入
【发布时间】:2017-04-19 22:44:08
【问题描述】:

当我使用我的 xxxContext 对象并向表发出多个添加时,SaveChanges() 实体框架如何将其解析为 SQL?它会循环执行 insert into xxx 还是如果有数百行,它是否足够聪明地发出 Bulk insert 命令?

额外问题:如果它不发出批量插入,有没有办法强制它这样做,这样我的数据库性能就不会被单独的插入杀死?或者批量到临时表,然后像 Upsert 一样合并到原始表?

【问题讨论】:

标签: c# .net entity-framework


【解决方案1】:

任何 ORM 工具的缺点是它“健谈”。大多数时候这已经足够了。有时不是。

简短的回答是“不”。

这就是为什么我有时仍然选择 IDataReader 而不是 EF 或 NHibernate 等。 对于批量插入操作,我将 xml 发送到存储过程,然后将其切碎并从那里进行批量插入/更新或合并。

所以即使我使用 ORM,我也会创建一个不依赖于 EF(或 NHibernate)的域库......所以我有一个“安全阀”可以在某些情况下绕过 ORM。

【讨论】:

  • 谢谢,我的想法。令人惊讶的是,没有任何开源解决方案可以解决这个问题。我的项目一次将数千条记录写入数据库,并且每 15 秒发生一次,所以我想我会坚持使用普通 sql,这样我可以更好地控制它。
  • 这里是关键。重新创建的索引是放缓。当您插入 RBAR(逐行排列)时,您必须在每次插入后重新创建索引。所以你做一些“基于集合”的事情,比如传递 xml,粉碎它,然后插入到 dbo.MyTable 中选择(一堆行)。现在,您可以在插入暂存表的地方做一些巫术,然后发出“从暂存到真实的移动”(以批量方式),但它不是很 EF'ish。但同样,经验法则(或主要法则之一)是“当我这样做时,我的索引是如何重建的”。又名,被遗忘的物品
  • 我所做的大部分工作基本上都是一个 upsert。 (更新或插入)我需要它们快速并且我更新的表有数十亿(几乎数万亿)行。我发现最快的方法是插入临时表然后进行合并。我在这里记录了它:jarloo.com/c-bulk-upsert-to-sql-server-tutorial
【解决方案2】:

Entity Framework 有一些改进的机会:

设置:

yourContext.Configuration.AutoDetectChangesEnabled = false;
yourContext.Configuration.ValidateOnSaveEnabled = false;

SaveChanges() 在 100 个插入的包装中...尝试 1000 并查看更改。

由于在所有这些插入过程中,上下文是相同的,您可以每插入 1000 次就重建上下文对象。 var yourContext = new YourContext();

在我的导入数据过程中进行此改进,将其从 7 分钟缩短到 6 秒。

实际数字...在您的情况下不可能是 100 或 1000...试试看吧。

【讨论】:

  • 我有一个相当复杂的迁移,我也在使用 UoW/Repo 层,这为我节省了大量的迁移时间。不确定我是否能够使用 EntityFramework.BulkInsert 在我的控制台应用程序中获取和保存数据的流程。
【解决方案3】:

如果您的插入查询是 ANSI SQL,或者您不关心使用您的代码库支持多数据库,您仍然有后门可以从 EF 创建 ADO.NET 提供程序并执行一些原始 SQL 调用

https://stackoverflow.com/a/1579220/98491

我会做这样的事情

private void BulkInsert(IEnumerable<Person> Persons)
{

    // use the information in the link to get your connection
    DbConnection conn = ...
    using (DbCommand cmd = conn.CreateCommand())
    {

       var sb = new StringBuilder();
       sb.Append("INSERT INTO person (firstname, lastname) VALUES ");
       var count = 0;
       foreach(var person in persons)
       {
           if (count !=0) sb.Append(",");
           sb.Append(GetInsertCommand(person, count++, cmd));
       }

       if (count > 0)
       {
           cmd.CommandText = sb.ToString();
           cmd.ExecuteNonQuery();
       }
    }



   if (sb.Length > 0)
       ExecuteNonQuery(sb.ToString());
}

private string GetInsertCommand(Person person, int count, DbCommand cmd)
{
    var firstname = "@firstname" + count.ToString();
    var lastname = "@lastname" + count.ToString();
    cmd.Parameters.Add(firstname, person.Firstname);
    cmd.Parameters.Add(lastname, person.Firstname);
    return String.Format("({0},{1})", firstname, lastname);
}

我必须承认我没有测试过它,但这应该是一种快速而肮脏的方法,可以绕过某些 Bulk Inserts 的 EF,直到 Bulk inserts 成为核心的一部分。

更新

只是一个快速的想法。您是否尝试过 Migrations 命名空间中的 ... 方法? 也许这个做批量插入,还没有研究过,但值得一试:

private void BatchInsert(IEnumerable<Person> persons)
{
    context.Persons.AddOrUpdate(persons);
}

我知道如果您定义像 AddOrUpdate(p =&gt; p.Firstname, persons) 这样的 Key 列,此方法可能会很慢,但我猜如果不指定它,那应该是所有插入(不保证)

【讨论】:

  • AddOrUpdate不带lambda参数使用key属性查询,所以与AddOrUpdate(p =&gt; p.Id, persons)基本一致。在插入之前,集合中的每个人仍将是一个查询(按键为SingleOrDefault)。
【解决方案4】:

你可以使用bulk insert extension

用法:

using EntityFramework.BulkInsert.Extensions;

context.BulkInsert(myEntities);

使用 DbContext:

using (var ctx = GetContext())
{
    using (var transactionScope = new TransactionScope())
    {
        // some stuff in dbcontext    
        ctx.BulkInsert(entities);    
        ctx.SaveChanges();
        transactionScope.Complete();
    }
}

【讨论】:

    【解决方案5】:

    恐怕 EF 不支持批量插入或更新。正如您所说,目前 EF 将生成一堆插入命令并分别执行它们(但都包含在一个事务中)。有一些计划实施批处理,不确定最近是否有一些进展。希望在 EF6 中,但我有点怀疑。

    您可以在discussion 中阅读更多内容。

    【讨论】:

      【解决方案6】:

      从存储库插入 ASP .NET Core 版本的快速方法。

      public virtual void AddRangeFastAndCommit(IEnumerable<T> entities)
      {
          MyDbContext localContext = new MyDbContext(_context.Options);
          localContext.ChangeTracker.AutoDetectChangesEnabled = false;
      
          foreach (var entity in entities)
          {
              localContext.Add(entity);
          }
      
          localContext.SaveChanges();
          localContext.Dispose();
      }
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2016-12-07
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多