实体框架中最快的插入方式

时间:2011-05-09 17:14:00

标签: c# sql entity-framework

我正在寻找插入Entity Framework的最快方式。

我问这个是因为你有一个活跃的TransactionScope并且插入很大(4000+)。它可能持续超过10分钟(交易的默认超时),这将导致交易不完整。

31 个答案:

答案 0 :(得分:916)

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

  

“... SavingChanges(表示每个   记录)...“

这是你能做的最糟糕的事情!为每条记录调用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之后处理上下文并创建新上下文的性能。这清除了所有entites的上下文,SaveChanges没有这样做,实体仍然附加到状态Unchanged中的上下文。在上下文中,附加实体的大小不断增加,这会逐步减慢插入速度。因此,在一段时间后清除它是有帮助的。

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

  • 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分钟后停止在50.000个实体。)这种非线性行为在所有其他测试中并不那么重要。

答案 1 :(得分:168)

这种组合可以提高速度。

context.Configuration.AutoDetectChangesEnabled = false;
context.Configuration.ValidateOnSaveEnabled = false;

答案 2 :(得分:97)

最快的方法是使用我开发的bulk insert extension

它使用SqlBulkCopy和自定义datareader来获得最大性能。因此,它比使用常规插入或AddRange快20多倍 EntityFramework.BulkInsert vs EF AddRange

使用非常简单

context.BulkInsert(hugeAmountOfEntities);

答案 3 :(得分:73)

您应该考虑使用System.Data.SqlClient.SqlBulkCopy。这是documentation,当然还有很多在线教程。

抱歉,我知道您正在寻找一个简单的答案,让EF做您想做的事,但批量操作并不是ORM的真正意义。

答案 4 :(得分:47)

我同意Adam Rackis。 SqlBulkCopy是将批量记录从一个数据源传输到另一个数据源的最快方法。我用这个来复制20K记录,花了不到3秒钟。看看下面的例子。

public static void InsertIntoMembers(DataTable dataTable)
{           
    using (var connection = new SqlConnection(@"data source=;persist security info=True;user id=;password=;initial catalog=;MultipleActiveResultSets=True;App=EntityFramework"))
    {
        SqlTransaction transaction = null;
        connection.Open();
        try
        {
            transaction = connection.BeginTransaction();
            using (var sqlBulkCopy = new SqlBulkCopy(connection, SqlBulkCopyOptions.TableLock, transaction))
            {
                sqlBulkCopy.DestinationTableName = "Members";
                sqlBulkCopy.ColumnMappings.Add("Firstname", "Firstname");
                sqlBulkCopy.ColumnMappings.Add("Lastname", "Lastname");
                sqlBulkCopy.ColumnMappings.Add("DOB", "DOB");
                sqlBulkCopy.ColumnMappings.Add("Gender", "Gender");
                sqlBulkCopy.ColumnMappings.Add("Email", "Email");

                sqlBulkCopy.ColumnMappings.Add("Address1", "Address1");
                sqlBulkCopy.ColumnMappings.Add("Address2", "Address2");
                sqlBulkCopy.ColumnMappings.Add("Address3", "Address3");
                sqlBulkCopy.ColumnMappings.Add("Address4", "Address4");
                sqlBulkCopy.ColumnMappings.Add("Postcode", "Postcode");

                sqlBulkCopy.ColumnMappings.Add("MobileNumber", "MobileNumber");
                sqlBulkCopy.ColumnMappings.Add("TelephoneNumber", "TelephoneNumber");

                sqlBulkCopy.ColumnMappings.Add("Deleted", "Deleted");

                sqlBulkCopy.WriteToServer(dataTable);
            }
            transaction.Commit();
        }
        catch (Exception)
        {
            transaction.Rollback();
        }

    }
}

答案 5 :(得分:18)

我已经调查了Slauma的答案(这很棒,感谢这个想法的人),并且我减少了批量大小,直到我达到了最佳速度。看看Slauma的结果:

  • commitCount = 1,recreateContext = true:超过10分钟
  • commitCount = 10,recreateContext = true:241 sec
  • commitCount = 100,recreateContext = true:164秒
  • commitCount = 1000,recreateContext = true:191 sec

从1到10,从10到100,但是从100到1000的插入速度再次下降时,可见速度增加。

所以我专注于将批量大小减少到10到100之间的值时发生的事情,这是我的结果(我使用不同的行内容,所以我的时间具有不同的价值):

Quantity    | Batch size    | Interval
1000    1   3
10000   1   34
100000  1   368

1000    5   1
10000   5   12
100000  5   133

1000    10  1
10000   10  11
100000  10  101

1000    20  1
10000   20  9
100000  20  92

1000    27  0
10000   27  9
100000  27  92

1000    30  0
10000   30  9
100000  30  92

1000    35  1
10000   35  9
100000  35  94

1000    50  1
10000   50  10
100000  50  106

1000    100 1
10000   100 14
100000  100 141

根据我的结果,批量大小的实际最佳值约为30。它不到10和100.问题是,我不知道为什么30是最优的,也不能找到任何合理的解释。

答案 6 :(得分:17)

我建议这篇文章介绍如何使用EF进行批量插入。

Entity Framework and slow bulk INSERTs

他探索了这些领域并比较了性能:

  1. 默认EF(完成添加30,000条记录需要57分钟)
  2. 替换为ADO.NET代码(25 代表相同的30,000)
  3. 上下文膨胀 - 通过为每个工作单元使用新的上下文(相同的30,000次插入需要33秒)来保持活动的上下文图形小。
  4. 大型列表 - 关闭AutoDetectChangesEnabled(将时间缩短至约20秒)
  5. 批量处理(最短16秒)
  6. DbTable.AddRange() - (性能在12范围内)

答案 7 :(得分:15)

正如其他人所说,如果你想要非常好的插入性能,SqlBulkCopy就是这样做的。

实现起来有点麻烦,但有些库可以帮助您实现它。有一些,但这次我会无耻地插入我自己的图书馆:https://github.com/MikaelEliasson/EntityFramework.Utilities#batch-insert-entities

您需要的唯一代码是:

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

那么它的速度有多快?很难说,因为它取决于很多因素,计算机性能,网络,对象大小等等。我所做的性能测试表明,25k实体可以在localhost上以标准方式的大约10s插入如果您优化您的EF配置,如其他答案中所述。 EFUtilities大约需要300毫秒。更有趣的是,我使用这种方法在15秒内节省了大约300万个实体,平均每秒约20万个实体。

如果您需要插入相关数据,那么问题就在于此。这可以使用上面的方法有效地进入sql server,但它要求你有一个Id生成策略,让你在父代的app-code中生成id,这样你就可以设置外键。这可以使用GUID或HiLo id生成之类的东西来完成。

答案 8 :(得分:14)

如果您Dispose()的实体依赖于上下文中的其他预加载实体(例如导航属性),

Add()上下文会产生问题

我使用类似的概念来保​​持我的上下文很小以实现相同的性能

但不是Dispose()上下文而是重新创建,我只是分离已经SaveChanges()

的实体
public void AddAndSave<TEntity>(List<TEntity> entities) where TEntity : class {

const int CommitCount = 1000; //set your own best performance number here
int currentCount = 0;

while (currentCount < entities.Count())
{
    //make sure it don't commit more than the entities you have
    int commitCount = CommitCount;
    if ((entities.Count - currentCount) < commitCount)
        commitCount = entities.Count - currentCount;

    //e.g. Add entities [ i = 0 to 999, 1000 to 1999, ... , n to n+999... ] to conext
    for (int i = currentCount; i < (currentCount + commitCount); i++)        
        _context.Entry(entities[i]).State = System.Data.EntityState.Added;
        //same as calling _context.Set<TEntity>().Add(entities[i]);       

    //commit entities[n to n+999] to database
    _context.SaveChanges();

    //detach all entities in the context that committed to database
    //so it won't overload the context
    for (int i = currentCount; i < (currentCount + commitCount); i++)
        _context.Entry(entities[i]).State = System.Data.EntityState.Detached;

    currentCount += commitCount;
} }
如果需要,

用try catch和TrasactionScope()包装它, 没有在这里显示它们以保持代码清洁

答案 9 :(得分:8)

[2019更新] EF Core 3.1

按照上述说明,在EF Core中禁用AutoDetectChangesEnabled效果很好:插入时间除以100(从几分钟到几秒钟,具有交叉表关系的10k条记录)

更新后的代码为:

  context.ChangeTracker.AutoDetectChangesEnabled = false;
            foreach (IRecord record in records) {
               //Add records to your database        
            }
            context.ChangeTracker.DetectChanges();
            context.SaveChanges();
            context.ChangeTracker.AutoDetectChangesEnabled = true; //do not forget to re-enable

答案 10 :(得分:7)

我知道这是一个非常古老的问题,但是这里的一个人说开发了一种扩展方法来使用EF的批量插入,当我检查时,我发现该库今天花费了599美元(对于一个开发人员)。也许这对整个库来说都是有意义的,但是对于大量插入来说这太过分了。

这是我制作的一个非常简单的扩展方法。我首先使用它与数据库配对(不要先用代码测试,但我认为它的工作方式相同)。使用您的上下文名称更改YourEntities

public partial class YourEntities : DbContext
{
    public async Task BulkInsertAllAsync<T>(IEnumerable<T> entities)
    {
        using (var conn = new SqlConnection(Database.Connection.ConnectionString))
        {
            await conn.OpenAsync();

            Type t = typeof(T);

            var bulkCopy = new SqlBulkCopy(conn)
            {
                DestinationTableName = GetTableName(t)
            };

            var table = new DataTable();

            var properties = t.GetProperties().Where(p => p.PropertyType.IsValueType || p.PropertyType == typeof(string));

            foreach (var property in properties)
            {
                Type propertyType = property.PropertyType;
                if (propertyType.IsGenericType &&
                    propertyType.GetGenericTypeDefinition() == typeof(Nullable<>))
                {
                    propertyType = Nullable.GetUnderlyingType(propertyType);
                }

                table.Columns.Add(new DataColumn(property.Name, propertyType));
            }

            foreach (var entity in entities)
            {
                table.Rows.Add(
                    properties.Select(property => property.GetValue(entity, null) ?? DBNull.Value).ToArray());
            }

            bulkCopy.BulkCopyTimeout = 0;
            await bulkCopy.WriteToServerAsync(table);
        }
    }

    public void BulkInsertAll<T>(IEnumerable<T> entities)
    {
        using (var conn = new SqlConnection(Database.Connection.ConnectionString))
        {
            conn.Open();

            Type t = typeof(T);

            var bulkCopy = new SqlBulkCopy(conn)
            {
                DestinationTableName = GetTableName(t)
            };

            var table = new DataTable();

            var properties = t.GetProperties().Where(p => p.PropertyType.IsValueType || p.PropertyType == typeof(string));

            foreach (var property in properties)
            {
                Type propertyType = property.PropertyType;
                if (propertyType.IsGenericType &&
                    propertyType.GetGenericTypeDefinition() == typeof(Nullable<>))
                {
                    propertyType = Nullable.GetUnderlyingType(propertyType);
                }

                table.Columns.Add(new DataColumn(property.Name, propertyType));
            }

            foreach (var entity in entities)
            {
                table.Rows.Add(
                    properties.Select(property => property.GetValue(entity, null) ?? DBNull.Value).ToArray());
            }

            bulkCopy.BulkCopyTimeout = 0;
            bulkCopy.WriteToServer(table);
        }
    }

    public string GetTableName(Type type)
    {
        var metadata = ((IObjectContextAdapter)this).ObjectContext.MetadataWorkspace;
        var objectItemCollection = ((ObjectItemCollection)metadata.GetItemCollection(DataSpace.OSpace));

        var entityType = metadata
                .GetItems<EntityType>(DataSpace.OSpace)
                .Single(e => objectItemCollection.GetClrType(e) == type);

        var entitySet = metadata
            .GetItems<EntityContainer>(DataSpace.CSpace)
            .Single()
            .EntitySets
            .Single(s => s.ElementType.Name == entityType.Name);

        var mapping = metadata.GetItems<EntityContainerMapping>(DataSpace.CSSpace)
                .Single()
                .EntitySetMappings
                .Single(s => s.EntitySet == entitySet);

        var table = mapping
            .EntityTypeMappings.Single()
            .Fragments.Single()
            .StoreEntitySet;

        return (string)table.MetadataProperties["Table"].Value ?? table.Name;
    }
}

您可以对任何继承自IEnumerable的集合使用它,例如:

await context.BulkInsertAllAsync(items);

答案 11 :(得分:5)

尝试使用存储过程,它将获取您要插入的数据的XML。

答案 12 :(得分:4)

我已经对@Slauma上面的例子进行了通用扩展;

public static class DataExtensions
{
    public static DbContext AddToContext<T>(this DbContext context, object entity, int count, int commitCount, bool recreateContext, Func<DbContext> contextCreator)
    {
        context.Set(typeof(T)).Add((T)entity);

        if (count % commitCount == 0)
        {
            context.SaveChanges();
            if (recreateContext)
            {
                context.Dispose();
                context = contextCreator.Invoke();
                context.Configuration.AutoDetectChangesEnabled = false;
            }
        }
        return context;
    }
}

用法:

public void AddEntities(List<YourEntity> entities)
{
    using (var transactionScope = new TransactionScope())
    {
        DbContext context = new YourContext();
        int count = 0;
        foreach (var entity in entities)
        {
            ++count;
            context = context.AddToContext<TenancyNote>(entity, count, 100, true,
                () => new YourContext());
        }
        context.SaveChanges();
        transactionScope.Complete();
    }
}

答案 13 :(得分:3)

  

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

有一些支持批量插入的第三方库可用:

  • Z.EntityFramework.Extensions(推荐
  • EFUtilities
  • EntityFramework.BulkInsert

请参阅:Entity Framework Bulk Insert library

选择批量插入库时要小心。只有Entity Framework Extensions支持所有类型的关联和继承,并且它是唯一仍然受支持的。

免责声明:我是Entity Framework Extensions

的所有者

此库允许您执行场景所需的所有批量操作:

  • 批量保存更改
  • 批量插入
  • 批量删除
  • 批量更新
  • 批量合并

实施例

// Easy to use
context.BulkSaveChanges();

// Easy to customize
context.BulkSaveChanges(bulk => bulk.BatchSize = 100);

// Perform Bulk Operations
context.BulkDelete(customers);
context.BulkInsert(customers);
context.BulkUpdate(customers);

// Customize Primary Key
context.BulkMerge(customers, operation => {
   operation.ColumnPrimaryKeyExpression = 
        customer => customer.Code;
});

答案 14 :(得分:3)

保存列表的最快方法之一 您必须应用以下代码

context.Configuration.AutoDetectChangesEnabled = false;
context.Configuration.ValidateOnSaveEnabled = false;

AutoDetectChangesEnabled = false

添加,添加范围和保存更改:不检测更改。

ValidateOnSaveEnabled = false;

未检测到更改跟踪器

您必须添加nuget

Install-Package Z.EntityFramework.Extensions

现在您可以使用以下代码

var context = new MyContext();

context.Configuration.AutoDetectChangesEnabled = false;
context.Configuration.ValidateOnSaveEnabled = false;

context.BulkInsert(list);
context.BulkSaveChanges();

答案 15 :(得分:2)

另一种选择是使用Nuget提供的SqlBulkTools。它非常易于使用,并具有一些强大的功能。

示例:

var bulk = new BulkOperations();
var books = GetBooks();

using (TransactionScope trans = new TransactionScope())
{
    using (SqlConnection conn = new SqlConnection(ConfigurationManager
    .ConnectionStrings["SqlBulkToolsTest"].ConnectionString))
    {
        bulk.Setup<Book>()
            .ForCollection(books)
            .WithTable("Books") 
            .AddAllColumns()
            .BulkInsert()
            .Commit(conn);
    }

    trans.Complete();
}

有关更多示例和高级用法,请参阅the documentation。免责声明:我是这个图书馆的作者,任何观点都是我自己的看法。

答案 16 :(得分:2)

使用<div class='nested-fields'> <%= f.label :order_name %> <%= f.text_field :order_name %> </div> <%= link_to_remove_association "remove order", f %>

SqlBulkCopy

答案 17 :(得分:2)

以下是在实际示例中使用Entity Framework和使用SqlBulkCopy类之间的性能比较:How to Bulk Insert Complex Objects into SQL Server Database

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

答案 18 :(得分:2)

因为这里没有提到,我想推荐EFCore.BulkExtensions here

context.BulkInsert(entitiesList);                 context.BulkInsertAsync(entitiesList);
context.BulkUpdate(entitiesList);                 context.BulkUpdateAsync(entitiesList);
context.BulkDelete(entitiesList);                 context.BulkDeleteAsync(entitiesList);
context.BulkInsertOrUpdate(entitiesList);         context.BulkInsertOrUpdateAsync(entitiesList);         // Upsert
context.BulkInsertOrUpdateOrDelete(entitiesList); context.BulkInsertOrUpdateOrDeleteAsync(entitiesList); // Sync
context.BulkRead(entitiesList);                   context.BulkReadAsync(entitiesList);

答案 19 :(得分:1)

这里写的所有解决方案都无济于事,因为当您执行SaveChanges()时,insert语句将逐个发送到数据库,这就是Entity的工作方式。

例如,如果您的数据库和返回时间为50毫秒,则插入所需的时间是记录数x 50毫秒。

您必须使用BulkInsert,这是链接:https://efbulkinsert.codeplex.com/

通过使用它,插入时间从5-6分钟减少到10-12秒。

答案 20 :(得分:1)

您是否尝试过后台工作人员或任务?

就我而言,我插入7760个寄存器,分布在182个具有外键关系的表中(通过NavigationProperties)。

没有任务,花了2分半钟。 在任务(Task.Factory.StartNew(...))内,花了15秒。

我只是在将所有实体添加到上下文后才执行SaveChanges()。 (以确保数据完整性)

答案 21 :(得分:1)

您可以使用Bulk package库。 批量插入1.0.0版本用于具有实体框架&gt; = 6.0.0。

的项目中

可在此处找到更多说明 - Bulkoperation source code

答案 22 :(得分:1)

[POSTGRESQL的新解决方案] 嘿,我知道这是一个很老的帖子,但我最近遇到了类似的问题,但我们使用的是Postgresql。我想使用有效的bulkinsert,结果很难。我没有在这个DB上找到任何适当的免费库。我只发现了这个帮手: 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;
        }

我按照以下方式使用它(我有一个名为Undertaking的实体):

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

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

这个解决方案,经过几个小时的研究和尝试,我已经得到了,你可以期待更快,最后易于使用和免费!我真的建议你使用这个解决方案,不仅仅是出于上述原因,而且因为它是唯一一个我对Postgresql本身没有任何问题的解决方案,许多其他解决方案可以完美地运行,例如使用SqlServer。

答案 23 :(得分:1)

SqlBulkCopy超级快

这是我的实现方式

// at some point in my calling code, I will call:
var myDataTable = CreateMyDataTable();
myDataTable.Rows.Add(Guid.NewGuid,tableHeaderId,theName,theValue); // e.g. - need this call for each row to insert

var efConnectionString = ConfigurationManager.ConnectionStrings["MyWebConfigEfConnection"].ConnectionString;
var efConnectionStringBuilder = new EntityConnectionStringBuilder(efConnectionString);
var connectionString = efConnectionStringBuilder.ProviderConnectionString;
BulkInsert(connectionString, myDataTable);

private DataTable CreateMyDataTable()
{
    var myDataTable = new DataTable { TableName = "MyTable"};
// this table has an identity column - don't need to specify that
    myDataTable.Columns.Add("MyTableRecordGuid", typeof(Guid));
    myDataTable.Columns.Add("MyTableHeaderId", typeof(int));
    myDataTable.Columns.Add("ColumnName", typeof(string));
    myDataTable.Columns.Add("ColumnValue", typeof(string));
    return myDataTable;
}

private void BulkInsert(string connectionString, DataTable dataTable)
{
    using (var connection = new SqlConnection(connectionString))
    {
        connection.Open();
        SqlTransaction transaction = null;
        try
        {
            transaction = connection.BeginTransaction();

            using (var sqlBulkCopy = new SqlBulkCopy(connection, SqlBulkCopyOptions.TableLock, transaction))
            {
                sqlBulkCopy.DestinationTableName = dataTable.TableName;
                foreach (DataColumn column in dataTable.Columns) {
                    sqlBulkCopy.ColumnMappings.Add(column.ColumnName, column.ColumnName);
                }

                sqlBulkCopy.WriteToServer(dataTable);
            }
            transaction.Commit();
        }
        catch (Exception)
        {
            transaction?.Rollback();
            throw;
        }
    }
}

答案 24 :(得分:1)

据我所知,no BulkInsertEntityFramework可以提高巨大插页的效果。

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

答案 25 :(得分:0)

使用以xml格式输入数据的存储过程来插入数据。

从您的c#代码传递插入数据为xml。

例如在c#中,语法将是这样的:

object id_application = db.ExecuteScalar("procSaveApplication", xml)

答案 26 :(得分:0)

是的,SqlBulkUpdate确实是用于此类任务的最快工具。我想在.NET Core中为我找到“省力”的通用方法,因此我最终使用了great library from Marc Gravell called FastMember并为实体框架数据库上下文编写了一个微小的扩展方法。快速运作:

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();
                }
            }
        }
    }
}

答案 27 :(得分:0)

TL; DR 我知道这是一个古老的职位,但是我已经实现了一个解决方案,从提出的方案之一开始,对其进行扩展并解决了一些问题;此外,我还阅读了提出的其他解决方案,并与这些解决方案进行比较,在我看来,提出一种更适合原始问题中提出的要求的解决方案。

在此解决方案中,我扩展了Slauma's approach,这对原始问题中提出的情况来说是完美的,那就是使用Entity Framework和Transaction Scope在数据库上进行昂贵的写操作。

在Slauma的解决方案中-附带的是草案,仅用于通过实施大容量插入的策略来了解EF的速度-存在以下问题:

  1. 交易超时(默认情况下为1分钟,可通过代码延长至最多10分钟);
  2. 复制第一块数据,其宽度等于在事务结束时使用的提交的大小(此问题很奇怪,可以通过一种变通方法来解决)。

我还通过报告一个示例,扩展了Slauma提出的案例研究,该示例包括在上下文中插入多个从属实体。

我能够验证的性能是将10K rec / min的性能插入db中的一个200K宽的记录块,每个记录大约1KB。速度是恒定的,性能没有下降,并且测试花费了大约20分钟才能成功运行。

详细解决方案

主持示例存储库类中插入的大容量插入操作的方法:

abstract class SomeRepository { 

    protected MyDbContext myDbContextRef;

    public void ImportData<TChild, TFather>(List<TChild> entities, TFather entityFather)
            where TChild : class, IEntityChild
            where TFather : class, IEntityFather
    {

        using (var scope = MyDbContext.CreateTransactionScope())
        {

            MyDbContext context = null;
            try
            {
                context = new MyDbContext(myDbContextRef.ConnectionString);

                context.Configuration.AutoDetectChangesEnabled = false;

                entityFather.BulkInsertResult = false;
                var fileEntity = context.Set<TFather>().Add(entityFather);
                context.SaveChanges();

                int count = 0;

                //avoids an issue with recreating context: EF duplicates the first commit block of data at the end of transaction!!
                context = MyDbContext.AddToContext<TChild>(context, null, 0, 1, true);

                foreach (var entityToInsert in entities)
                {
                    ++count;
                    entityToInsert.EntityFatherRefId = fileEntity.Id;
                    context = MyDbContext.AddToContext<TChild>(context, entityToInsert, count, 100, true);
                }

                entityFather.BulkInsertResult = true;
                context.Set<TFather>().Add(fileEntity);
                context.Entry<TFather>(fileEntity).State = EntityState.Modified;

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

            scope.Complete();
        }

    }

}

接口仅用于示例目的:

public interface IEntityChild {

    //some properties ...

    int EntityFatherRefId { get; set; }

}

public interface IEntityFather {

    int Id { get; set; }
    bool BulkInsertResult { get; set; }
}

db上下文中,我将解决方案的各个元素实现为静态方法:

public class MyDbContext : DbContext
{

    public string ConnectionString { get; set; }


    public MyDbContext(string nameOrConnectionString)
    : base(nameOrConnectionString)
    {
        Database.SetInitializer<MyDbContext>(null);
        ConnectionString = Database.Connection.ConnectionString;
    }


    /// <summary>
    /// Creates a TransactionScope raising timeout transaction to 30 minutes
    /// </summary>
    /// <param name="_isolationLevel"></param>
    /// <param name="timeout"></param>
    /// <remarks>
    /// It is possible to set isolation-level and timeout to different values. Pay close attention managing these 2 transactions working parameters.
    /// <para>Default TransactionScope values for isolation-level and timeout are the following:</para>
    /// <para>Default isolation-level is "Serializable"</para>
    /// <para>Default timeout ranges between 1 minute (default value if not specified a timeout) to max 10 minute (if not changed by code or updating max-timeout machine.config value)</para>
    /// </remarks>
    public static TransactionScope CreateTransactionScope(IsolationLevel _isolationLevel = IsolationLevel.Serializable, TimeSpan? timeout = null)
    {
        SetTransactionManagerField("_cachedMaxTimeout", true);
        SetTransactionManagerField("_maximumTimeout", timeout ?? TimeSpan.FromMinutes(30));

        var transactionOptions = new TransactionOptions();
        transactionOptions.IsolationLevel = _isolationLevel;
        transactionOptions.Timeout = TransactionManager.MaximumTimeout;
        return new TransactionScope(TransactionScopeOption.Required, transactionOptions);
    }

    private static void SetTransactionManagerField(string fieldName, object value)
    {
        typeof(TransactionManager).GetField(fieldName, BindingFlags.NonPublic | BindingFlags.Static).SetValue(null, value);
    }


    /// <summary>
    /// Adds a generic entity to a given context allowing commit on large block of data and improving performance to support db bulk-insert operations based on Entity Framework
    /// </summary>
    /// <typeparam name="T"></typeparam>
    /// <param name="context"></param>
    /// <param name="entity"></param>
    /// <param name="count"></param>
    /// <param name="commitCount">defines the block of data size</param>
    /// <param name="recreateContext"></param>
    /// <returns></returns>
    public static MyDbContext AddToContext<T>(MyDbContext context, T entity, int count, int commitCount, bool recreateContext) where T : class
    {
        if (entity != null)
            context.Set<T>().Add(entity);

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

        return context;
    }
}

答案 28 :(得分:0)

秘诀是插入相同的空白临时表。插件快速闪亮。然后从中运行单个插入到主大表中。然后截断准备好下一批的临时表。

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

答案 29 :(得分:0)

但是,对于超过(+4000)个插入,我建议使用存储过程。附上经过的时间。 我确实在20“enter image description here

中插入了11.788行

多数民众赞成代码

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

答案 30 :(得分:-1)

Configuration.LazyLoadingEnabled = false; Configuration.ProxyCreationEnabled = false;

如果没有 AutoDetectChangesEnabled = false,这些对速度影响太大;我建议使用与 dbo 不同的表头。通常我使用 nop、sop、tbl 等。