当我使用我的 xxxContext 对象并向表发出多个添加时,SaveChanges() 实体框架如何将其解析为 SQL?它会循环插入到 xxx中还是如果有数百行,是否足够聪明地发出批量插入命令?
奖励问题:如果它不发出批量插入,有没有办法强制它,所以我的数据库性能不会被单独的插入杀死?或者批量到临时表,然后像 Upsert 一样合并到原始表?
当我使用我的 xxxContext 对象并向表发出多个添加时,SaveChanges() 实体框架如何将其解析为 SQL?它会循环插入到 xxx中还是如果有数百行,是否足够聪明地发出批量插入命令?
奖励问题:如果它不发出批量插入,有没有办法强制它,所以我的数据库性能不会被单独的插入杀死?或者批量到临时表,然后像 Upsert 一样合并到原始表?
任何 ORM 工具的缺点是它很“健谈”。大多数时候这已经足够了。有时不是。
最简洁的答案是不”。
这就是为什么我有时仍然会选择 IDataReader 而不是 EF 或 NHibernate 等。对于批量插入操作,我将 xml 发送到存储过程,然后将其切碎并从那里批量插入/更新或合并。
因此,即使我使用 ORM,我也会创建一个不依赖于 EF(或 NHibernate)的域库......所以我有一个“安全阀”可以在某些情况下绕过 ORM。
Entity Framework 有几个改进的机会:
放:
yourContext.Configuration.AutoDetectChangesEnabled = false;
yourContext.Configuration.ValidateOnSaveEnabled = false;
以 100 个插入的包装执行SaveChanges()
...尝试 1000 并查看更改。
由于在所有这些插入过程中,上下文是相同的,因此您可以每 1000 次插入重建上下文对象。var yourContext = new YourContext();
在我的导入数据过程中进行此改进,将其从 7 分钟缩短到 6 秒。
实际数字...在您的情况下不可能是 100 或 1000... 尝试并调整它。
如果您的插入查询是 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);
}
我必须承认我没有测试过它,但这应该是一种快速而肮脏的方法来绕过一些批量插入的 EF,直到批量插入成为核心的一部分。
更新
只是一个快速的想法。您是否尝试过 Migrations 命名空间中的 ... 方法?也许这个做批量插入,还没有研究过,但值得一试:
private void BatchInsert(IEnumerable<Person> persons)
{
context.Persons.AddOrUpdate(persons);
}
我知道如果你定义一个 Key 列,这种方法可能会很慢,AddOrUpdate(p => p.Firstname, persons)
但我猜如果没有指定它,那应该是所有插入(不能保证)
您可以使用批量插入扩展
用法:
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();
}
}
恐怕 EF 不支持批量插入或更新。正如您所说,目前 EF 将生成一堆插入命令并分别执行它们(但全部包装在一个事务中)。有一些计划实施批处理,不确定最近是否有一些进展。希望在 EF6 中,但我有点怀疑。
您可以在此讨论中阅读更多内容。
从存储库插入 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();
}