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

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


当前回答

您可以使用Bulk包库。大容量插入1.0.0版本用于实体框架>=6.0.0的项目。

更多描述可在此处找到-Bulkoperation源代码

其他回答

[POSTGRESQL的新解决方案]嘿,我知道这是一篇很老的文章,但我最近遇到了类似的问题,但我们使用的是Postgresql。我想使用有效的膨胀剂,但结果很难。我在这个数据库上找不到合适的免费库。我只找到了这个助手:https://bytefish.de/blog/postgresql_bulk_insert/也在Nuget上。我编写了一个小映射器,它以实体框架的方式自动映射财产:

public static PostgreSQLCopyHelper<T> CreateHelper<T>(string schemaName, string tableName)
        {
            var helper = new PostgreSQLCopyHelper<T>("dbo", "\"" + tableName + "\"");
            var properties = typeof(T).GetProperties();
            foreach(var prop in properties)
            {
                var type = prop.PropertyType;
                if (Attribute.IsDefined(prop, typeof(KeyAttribute)) || Attribute.IsDefined(prop, typeof(ForeignKeyAttribute)))
                    continue;
                switch (type)
                {
                    case Type intType when intType == typeof(int) || intType == typeof(int?):
                        {
                            helper = helper.MapInteger("\"" + prop.Name + "\"",  x => (int?)typeof(T).GetProperty(prop.Name).GetValue(x, null));
                            break;
                        }
                    case Type stringType when stringType == typeof(string):
                        {
                            helper = helper.MapText("\"" + prop.Name + "\"", x => (string)typeof(T).GetProperty(prop.Name).GetValue(x, null));
                            break;
                        }
                    case Type dateType when dateType == typeof(DateTime) || dateType == typeof(DateTime?):
                        {
                            helper = helper.MapTimeStamp("\"" + prop.Name + "\"", x => (DateTime?)typeof(T).GetProperty(prop.Name).GetValue(x, null));
                            break;
                        }
                    case Type decimalType when decimalType == typeof(decimal) || decimalType == typeof(decimal?):
                        {
                            helper = helper.MapMoney("\"" + prop.Name + "\"", x => (decimal?)typeof(T).GetProperty(prop.Name).GetValue(x, null));
                            break;
                        }
                    case Type doubleType when doubleType == typeof(double) || doubleType == typeof(double?):
                        {
                            helper = helper.MapDouble("\"" + prop.Name + "\"", x => (double?)typeof(T).GetProperty(prop.Name).GetValue(x, null));
                            break;
                        }
                    case Type floatType when floatType == typeof(float) || floatType == typeof(float?):
                        {
                            helper = helper.MapReal("\"" + prop.Name + "\"", x => (float?)typeof(T).GetProperty(prop.Name).GetValue(x, null));
                            break;
                        }
                    case Type guidType when guidType == typeof(Guid):
                        {
                            helper = helper.MapUUID("\"" + prop.Name + "\"", x => (Guid)typeof(T).GetProperty(prop.Name).GetValue(x, null));
                            break;
                        }
                }
            }
            return helper;
        }

我使用它的方式如下(我有一个名为“承诺”的实体):

var undertakingHelper = BulkMapper.CreateHelper<Model.Undertaking>("dbo", nameof(Model.Undertaking));
undertakingHelper.SaveAll(transaction.UnderlyingTransaction.Connection as Npgsql.NpgsqlConnection, undertakingsToAdd));

我展示了一个事务的示例,但它也可以通过从上下文中检索到的正常连接来完成。undertakingsToAdd是普通实体记录的枚举,我想将其批量插入数据库。

这个解决方案,我经过几个小时的研究和尝试后得到的,正如你所期望的那样,速度更快,最终易于使用和免费!我真的建议你使用这个解决方案,不仅因为上面提到的原因,而且因为它是唯一一个我对Postgresql本身没有问题的解决方案,许多其他解决方案都可以完美地工作,例如SqlServer。

最快的方法是使用批量插入扩展,这是我开发的

注:这是一种商业产品,不是免费的

它使用SqlBulkCopy和自定义数据读取器来获得最大性能。因此,它比使用常规插入或AddRange快20倍以上

用法非常简单

context.BulkInsert(hugeAmountOfEntities);

使用此技术可以提高实体框架中插入记录的速度。这里我使用一个简单的存储过程来插入记录。为了执行这个存储过程,我使用实体框架的.FromSql()方法来执行Raw SQL。

存储过程代码:

CREATE PROCEDURE TestProc
@FirstParam VARCHAR(50),
@SecondParam VARCHAR(50)

AS
  Insert into SomeTable(Name, Address) values(@FirstParam, @SecondParam) 
GO

接下来,循环遍历所有4000条记录,并添加执行存储的

该过程每100次循环一次。

为此,我创建了一个字符串查询来执行这个过程,并继续将每一组记录附加到它。

然后检查循环是否以100的倍数运行,在这种情况下,使用.FromSql()执行它。

所以对于4000条记录,我只需要执行以下步骤4000/100=40次。

检查以下代码:

string execQuery = "";
var context = new MyContext();
for (int i = 0; i < 4000; i++)
{
    execQuery += "EXEC TestProc @FirstParam = 'First'" + i + "'', @SecondParam = 'Second'" + i + "''";

    if (i % 100 == 0)
    {
        context.Student.FromSql(execQuery);
        execQuery = "";
    }
}

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

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

这是你能做的最糟糕的事情!对每个记录调用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个实体。)这种非线性行为在所有其他测试中都不那么重要。

秘密是插入到相同的空白暂存表中。插件快速发光。然后在主大表中运行一个单独的插入。然后截断临时表,为下一批做好准备。

ie.

insert into some_staging_table using Entity Framework.

-- Single insert into main table (this could be a tiny stored proc call)
insert into some_main_already_large_table (columns...)
   select (columns...) from some_staging_table
truncate table some_staging_table