我正在寻找插入实体框架的最快方法。

我之所以问这个问题,是因为您有一个活动的TransactionScope,并且插入量很大(4000+)。它可能会持续超过10分钟(事务的默认超时),这将导致事务不完整。


当前回答

对于您在问题评论中的评论:

“…保存更改(每个记录)。。。"

这是你能做的最糟糕的事情!对每个记录调用SaveChanges()会大大降低批量插入的速度。我会做一些简单的测试,这很可能会提高性能:

在所有记录后调用SaveChanges()一次。例如,在100条记录之后调用SaveChanges()。例如,在100条记录之后调用SaveChanges(),并释放上下文并创建新的上下文。禁用更改检测

对于批量插入,我正在使用这样的模式进行工作和试验:

using (TransactionScope scope = new TransactionScope())
{
    MyDbContext context = null;
    try
    {
        context = new MyDbContext();
        context.Configuration.AutoDetectChangesEnabled = false;

        int count = 0;            
        foreach (var entityToInsert in someCollectionOfEntitiesToInsert)
        {
            ++count;
            context = AddToContext(context, entityToInsert, count, 100, true);
        }

        context.SaveChanges();
    }
    finally
    {
        if (context != null)
            context.Dispose();
    }

    scope.Complete();
}

private MyDbContext AddToContext(MyDbContext context,
    Entity entity, int count, int commitCount, bool recreateContext)
{
    context.Set<Entity>().Add(entity);

    if (count % commitCount == 0)
    {
        context.SaveChanges();
        if (recreateContext)
        {
            context.Dispose();
            context = new MyDbContext();
            context.Configuration.AutoDetectChangesEnabled = false;
        }
    }

    return context;
}

我有一个测试程序,它将560.000个实体(9个标量财产,没有导航财产)插入数据库。使用此代码,它只需不到3分钟即可工作。

为了提高性能,在“许多”记录之后调用SaveChanges()非常重要(“许多”约为100或1000)。它还提高了在SaveChanges之后释放上下文并创建新上下文的性能。这将从所有实体中清除上下文,SaveChanges不会这样做,实体仍然以状态Unchanged附加到上下文。正是由于上下文中附加实体的大小不断增加,才导致插入过程一步步变慢。因此,在一段时间后清除它是有帮助的。

以下是我的560000个实体的一些测量值:

commitCount=1,recreateContext=false:许多小时(这是您当前的过程)commitCount=100,recreateContext=false:超过20分钟commitCount=1000,recreateContext=false:242秒commitCount=10000,recreateContext=false:202秒commitCount=100000,recreateContext=false:199秒commitCount=1000000,recreateContext=false:内存不足异常commitCount=1,recreateContext=true:超过10分钟commitCount=10,recreateContext=true:241秒commitCount=100,recreateContext=true:164秒commitCount=1000,recreateContext=true:191秒

上述第一个测试中的行为是,性能非常非线性,并且随着时间的推移会大大降低。(“很多小时”是一个估计,我从未完成过这项测试,20分钟后我停在了50000个实体。)这种非线性行为在所有其他测试中都不那么重要。

其他回答

据我所知,EntityFramework中没有BulkInsert来提高大型插件的性能。

在这种情况下,您可以使用ADO.net中的SqlBulkCopy来解决问题

是的,SqlBulkUpdate确实是这类任务最快的工具。我想在.NETCore中找到“最省力”的通用方法,所以我最终使用了MarcGravell的优秀库FastMember,并为实体框架DB上下文编写了一个小小的扩展方法。工作速度极快:

using System.Collections.Generic;
using System.Linq;
using FastMember;
using Microsoft.Data.SqlClient;
using Microsoft.EntityFrameworkCore;

namespace Services.Extensions
{
    public static class DbContextExtensions
    {
        public static void BulkCopyToServer<T>(this DbContext db, IEnumerable<T> collection)
        {
            var messageEntityType = db.Model.FindEntityType(typeof(T));

            var tableName = messageEntityType.GetSchema() + "." + messageEntityType.GetTableName();
            var tableColumnMappings = messageEntityType.GetProperties()
                .ToDictionary(p => p.PropertyInfo.Name, p => p.GetColumnName());

            using (var connection = new SqlConnection(db.Database.GetDbConnection().ConnectionString))
            using (var bulkCopy = new SqlBulkCopy(connection))
            {
                foreach (var (field, column) in tableColumnMappings)
                {
                    bulkCopy.ColumnMappings.Add(field, column);
                }

                using (var reader = ObjectReader.Create(collection, tableColumnMappings.Keys.ToArray()))
                {
                    bulkCopy.DestinationTableName = tableName;
                    connection.Open();
                    bulkCopy.WriteToServer(reader);
                    connection.Close();
                }
            }
        }
    }
}

但是,对于超过(+4000)个插入,我建议使用存储过程。附上经过的时间。我确实插入了20英寸的11.788行

这就是代码

 public void InsertDataBase(MyEntity entity)
    {
        repository.Database.ExecuteSqlCommand("sp_mystored " +
                "@param1, @param2"
                 new SqlParameter("@param1", entity.property1),
                 new SqlParameter("@param2", entity.property2));
    }

下面是使用实体框架和使用SqlBulkCopy类之间的性能比较,具体示例为:如何将复杂对象批量插入SQL Server数据库

正如其他人已经强调的,ORM不应用于批量操作。它们提供了灵活性、关注点分离和其他好处,但批量操作(批量读取除外)不是其中之一。

正如其他人所说,如果您想要真正好的插入性能,SqlBulkCopy是一种实现方法。

它的实现有点麻烦,但有一些库可以帮助您实现它。有一些库,但这次我将无耻地使用我自己的库:https://github.com/MikaelEliasson/EntityFramework.Utilities#batch-插入实体

您需要的唯一代码是:

 using (var db = new YourDbContext())
 {
     EFBatchOperation.For(db, db.BlogPosts).InsertAll(list);
 }

那么它快多少?很难说,因为这取决于许多因素,计算机性能、网络、对象大小等。我所做的性能测试表明,如果您像其他答案中提到的那样优化EF配置,则可以在10秒左右以标准方式在本地主机上插入25k个实体。使用EFUtilities,大约需要300毫秒。更有趣的是,我使用这种方法在不到15秒内保存了大约300万个实体,平均每秒大约200万个实体。

当然,一个问题是若需要插入相关数据。这可以使用上述方法在sql server中高效地完成,但它需要您有一个Id生成策略,允许您在应用程序代码中为父级生成Id,以便您可以设置外键。这可以使用GUID或类似HiLo id生成的方法来完成。