如何加速 DbSet.Add()?

问题描述 投票:0回答:6

我必须将大约 30k 行从 CSV 文件导入到我的 SQL 数据库,这很遗憾地需要 20 分钟。

使用探查器进行故障排除显示 DbSet.Add 花费的时间最多,但为什么呢?

我有这些实体框架代码优先类:

public class Article
{
    // About 20 properties, each property doesn't store excessive amounts of data
}

public class Database : DbContext
{
    public DbSet<Article> Articles { get; set; }
}

对于我的 for 循环中的每个项目,我都这样做:

db.Articles.Add(article);

在 for 循环之外我做的:

db.SaveChanges();

它与我本地的 SQLExpress 服务器连接,但我想在调用 SaveChanges 之前没有任何内容,所以我想服务器不会是问题....

c# performance entity-framework sql-server-2008
6个回答
47
投票

根据 Kevin Ramen 的评论(3 月 29 日) 我可以确认设置

db.Configuration.AutoDetectChangesEnabled = false
在速度上有很大的不同

默认情况下在 2324 个项目上运行

Add()
在我的机器上运行 3 分 15 秒,禁用自动检测导致操作在 0.5 秒内完成。

http://blog.larud.net/archive/2011/07/12/bulk-load-items-to-a-ef-4-1-code-first-aspx


22
投票

我要补充 Kervin Ramen 的评论,如果您只进行插入(没有更新或删除),那么通常可以在对上下文进行任何插入之前安全地设置以下属性:

DbContext.Configuration.AutoDetectChangesEnabled = false;
DbContext.Configuration.ValidateOnSaveEnabled = false;

我在工作中遇到一次性批量导入的问题。如果不设置上述属性,将大约 7500 个复杂对象添加到上下文需要 30 多分钟。设置上述属性(因此禁用 EF 检查和更改跟踪)将导入减少到几秒钟。

但是,我再次强调,只有在插入时才使用它。如果您需要混合插入与更新/删除,您可以将代码拆分为两个路径并禁用插入部分的 EF 检查,然后重新启用更新/删除路径的检查。我已经成功地使用这种方法来绕过缓慢的

DbSet.Add()
行为。


10
投票

工作单元中的每个项目都有开销,因为它必须检查(和更新)身份管理器、添加到各种集合等。

我要尝试的第一件事是批处理,比如说,以 500 为一组(更改该数字以适应),每次都从一个新的(新的)对象上下文开始——否则您可以合理地期望伸缩性能。将其分成批次还可以防止巨石交易导致一切停止。

除此之外; SqlBulkCopy。它专为以最少的开销进行大量导入而设计。虽然它不是 EF。


5
投票

这里有一个非常容易使用和非常快速的扩展: https://efbulkinsert.codeplex.com/

它被称为“实体框架批量插入”。

Extension 本身位于命名空间 EntityFramework.BulkInsert.Extensions 中。所以要显示扩展方法,请使用

using EntityFramework.BulkInsert.Extensions;

然后你可以这样做

context.BulkInsert(entities);

顺便说一句——如果你出于某种原因不想使用这个扩展,你也可以尝试而不是为每篇文章运行 db.Articles.Add(article),每次创建一个包含几篇文章的列表,然后使用 AddRange(新在 EF 版本 6 中,连同 RemoveRange) 将它们一起添加到 dbcontext。


1
投票

我还没有真正尝试过这个,但我的逻辑是坚持使用 ODBC 驱动程序将文件加载到数据表中,然后使用 sql 存储过程将表传递给过程。

对于第一部分,尝试: http://www.c-sharpcorner.com/UploadFile/mahesh/AccessTextDb12052005071306AM/AccessTextDb.aspx

对于第二部分,尝试使用 SQL 过程: http://www.builderau.com.au/program/sqlserver/soa/Passing-table-valued-parameters-in-SQL-Server-2008/0,339028455,339282577,00.htm

并在 C# 中创建 SqlCommnand 对象并添加到其参数集合 SqlParameter 即 SqlDbType.Structured

好吧,希望对您有所帮助。


0
投票

另一个选择是批量添加文章,而不是在循环的每次迭代中添加。有点像

var articlesToAdd = new List<Article>();
foreach(var foo in foos){
    // more logic here...
    article = new Article()
    articlesToAdd.Add(article)
}

db.Articles.AddRange(articlesToAdd);

这将每次迭代的数据库往返时间减少到只有一次。

© www.soinside.com 2019 - 2024. All rights reserved.