【问题标题】:LINQ SaveChanges() exceptionally slowLINQ SaveChanges() 异常缓慢
【发布时间】:2016-05-31 09:15:32
【问题描述】:

我有一个控制台行应用程序,它通过 LINQ 将大约 150,000 行保存到数据库中。这很好用(我通常希望它停止工作)。这是从 CSV 文件中读取数据后的标准保存更改调用:-

 List<Invoice> oldInvoices = db.Invoices.Where(x => !x.IsVisible).ToList();
 List<int> oldInvoiceIDs = oldInvoices.Select(s => s.InvoiceID).ToList();

 List<InvoiceProduct> allInvoiceProducts = db.InvoiceProducts.ToList();
 List<InvoiceProduct> oldInvoiceProducts = allInvoiceProducts.Where(x => oldInvoiceIDs.Contains(x.InvoiceID)).ToList();

 db.InvoiceProducts.RemoveRange(oldInvoiceProducts);
 db.Invoices.RemoveRange(oldInvoices);

 UpdateConsole.WriteLine("Switching over invoices completed. Please wait...", ConsoleColor.Black, ConsoleColor.Magenta);

该表是发票列表,其中包含针对每张发票的产品子链接表。每次得到新数据,我们写入新数据,在数据库中将其标记为不可见,然后将当前可见的数据切换为不可见,将当前不可见的数据切换为可见,产生从一个数据集立即切换到下一个数据集。刚刚被标记为不可见的数据集然后通过 LINQ 被删除。

这需要一些时间来删除,但不是不合理的时间。由于此数据来自 CSV 数据文件,我们记录了行数,以及读取文件的开始和结束日期和时间。这是存储在另一个数据库表中,要保存的代码是:-

importLog.SuccessfullyImportedRows = successfulRows;
importLog.FailedImportedRows = failedRows;
importLog.EndTime = DateTime.Now;

db.SaveChanges();

此保存需要超过 40 分钟,我不知道为什么。我唯一能想到的是,它使用的是在 Visual Studio 中生成 EDMX 时可用的相同 DBEntities 类?

有人吃过这个吗?它给人的应用程序挂起的外观,但它确实会在 40 分钟左右后继续......

【问题讨论】:

  • 感谢那里的链接。后者是关于循环内的 SaveChanges() ,而我的不是。还值得注意的是,当我保存 177,000 条记录时,我保存到本地列表,然后执行 AddRange(),然后执行 SaveChanges()。我只调用 SaveChanges() 一次来保存列表,第二次调用保存日志。没有任何循环的 SaveChanges() 是否仍然存在相同的问题?

标签: c# linq visual-studio linq-to-entities edmx


【解决方案1】:

您的方法存在多个性能问题:

  1. 从数据库中拖动不必要的数据。
  2. 大量插入大量记录。
  3. 从 Hude 记录中批量删除。

无需从数据库中拖动所有发票,然后在内存中本地过滤它们,您可以直接在数据库中查询它们并只检索您想要的列表。

你需要替换这个:

 List<InvoiceProduct> allInvoiceProducts = db.InvoiceProducts.ToList();
 List<InvoiceProduct> oldInvoiceProducts = allInvoiceProducts.Where(x => oldInvoiceIDs.Contains(x.InvoiceID)).ToList();

与:

List<InvoiceProduct> oldInvoiceProducts = db.InvoiceProducts.Where(x => oldInvoiceIDs.Contains(x.InvoiceID)).ToList();

对于批量删除更快的方法:

 String commaDelimitedIds = String.Join(",", oldInvoiceIDs);
 String query = "DELETE FROM Invoice WHERE InvoiceID IN (" + commaDelimitedIds + ")";
 db.ExecuteQuery(query);

通过Linq To SQL 插入150,000 记录不是一个好主意,这将生成150,000 Insert 语句(不提及关系对象)。

看看这个例子:SQLBulkCopy,它非常适合大型插入。

一般来说,ORM对于批量操作不是一个好主意。

【讨论】:

  • 您好,感谢您的回复。你说我从数据库中拖出不必要的数据是正确的。我之前遇到的问题是“with:”之后的第三行代码检索到一个错误,因为 .Contains() 那里的列表中有太多整数。因此,我在一个额外的行中选择了一个本地列表,然后在上面做了我的方法。非常感谢您的帮助!我会试一试的。
  • @MikeUpjohn 您可以使用 for 循环将每个 2000 ID 拆分在一起,最后将 Concat 列表拆分在一起。这是 SQL 参数限制的解决方法。
【解决方案2】:

首先,在您的查询中,我发现使用.toList() 时存在问题。 toList 表示您强制该查询立即运行并将其存储到内存中。对于小数据来说它更快,但对于超过 150,000 行,你肯定会遇到性能问题并且内存不足。您可以使用AsQueryable() 代替。

AsQueryable 只是创建一个查询,获取 列表。您可以稍后对查询进行进一步更改,例如添加 一直发送到数据库的新 Where 子句 级别。

对于 EF 6 或更高版本,RemoveRange 的性能非常快。所以我不认为RemoveRange 是这里的根本原因。但是,如果您想提高更多性能,请尝试使用此扩展程序之一。这太好了。 https://efbulkinsert.codeplex.com/

【讨论】:

    【解决方案3】:

    好的,我找到了解决方案(虽然不确定推理)。如果我不将实体项带入日志记录功能,我会执行从 EDMX 生成的实体的新实例,例如:-

    using(DBEntities db = new DBEntities()) {
        importLog.SuccessfullyImportedRows = successfulRows;
        importLog.FailedImportedRows = failedRows;
        importLog.EndTime = DateTime.Now;
    
        db.SaveChanges();
    }
    

    这可以在不到一秒钟的时间内完成。由于插入了这么多行,所以在 DBEntities 的原始实例中缓存了一些东西?

    【讨论】:

      猜你喜欢
      • 2011-05-23
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2022-01-19
      • 2014-08-31
      • 1970-01-01
      相关资源
      最近更新 更多