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


681

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

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


1
您目前的状况如何?
达斯汀·莱恩

创建TransactionScope,实例化DBContext,打开连接,并在for-each语句中执行插入和SavingChanges(针对每个记录),注意:TransactionScope和DBContext在using语句中,而我在最后关闭连接街区
Bongo Sharp,


2
插入SQL数据库的最快方法不涉及EF。AFAIK的BCP然后是TVP +合并/插入。
StingyJack

1
对于那些会读评论的人:最适用的现代答案在这里。
Tanveer Badar

Answers:


985

在对您的问题的评论中发表评论:

“ ... SavingChanges(针对每个记录)...”

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

  • SaveChanges()在所有记录后调用一次。
  • 调用SaveChanges()例如100条记录。
  • SaveChanges()例如,调用100条记录并处理上下文并创建一个新的。
  • 禁用变更检测

对于批量插入,我正在尝试以下模式:

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实体的上下文,但不会这样做,实体仍然以state附加到上下文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分钟后我停止在50.000个实体上。)这种非线性行为在所有其他测试中都不那么重要。


89
@Bongo Sharp:不要忘记AutoDetectChangesEnabled = false;在DbContext上进行设置。它也有一个很大的额外性能的影响:stackoverflow.com/questions/5943394/...
Slauma

6
是的,问题在于我使用的是Entity Framework 4,而AutoDetectChangesEnabled是4.1的一部分,尽管如此,我进行了性能测试并且得到了惊人的结果,它从00:12:00变为00:00:22 SavinChanges每个实体都在做olverload ...非常感谢您的回答!这就是我想要的
Bongo Sharp,

10
谢谢你的上下文。Configuration.AutoDetectChangesEnabled = false; 提示,它有很大的不同。
道格拉斯

1
@ dahacker89:您是否使用了正确版本EF> = 4.1 DbContext,NOT ObjectContext
Slauma

3
@ dahacker89:我建议您为您的问题创建一个单独的问题,也许还需要更多详细信息。我无法在这里找出问题所在。
Slauma 2012年

176

这种组合足以提高速度。

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

46
不要盲目地禁用ValidateOnSaveEnabled,这可能取决于该行为,并且在为时已晚之前不要意识到这一点。再一次,您可能正在代码的其他地方执行验证,而再次让EF验证完全没有必要。
杰里米·库克

1
在我的测试中,保存的20.000行从101秒减少到88秒。数量不多,含义如何。
啊。

27
@JeremyCook我认为,如果它解释了从默认值更改这些属性(除了提高性能)可能带来的影响,那么您想得到的答案会更好。我同意。

1
这对我有用,尽管如果要在上下文中更新记录,则需要显式调用DetectChanges()
hillstuk 2014年

2
可以禁用这些,然后使用try-finally块重新启用:msdn.microsoft.com/en-us/data/jj556205.aspx
yellavon 2015年

98

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

注意:这是商业产品,并非免费

它使用SqlBulkCopy和自定义数据读取器来获得最佳性能。结果,它比使用常规插入或AddRange快20倍以上 EntityFramework.BulkInsert与EF AddRange

用法非常简单

context.BulkInsert(hugeAmountOfEntities);

10
快速,但仅在层次结构的顶层执行。
CAD bloke'Apr

65
它不是免费的。
阿米尔·萨尼扬

72
广告变得越来越聪明……这是付费产品,对于自由职业者来说非常昂贵。被警告!
JulioQc

35
600美元的1年支持和升级费用?你是不是疯了?
卡米洛·泰雷文托

7
我不再是产品的所有者
maxlego

83

您应该看看System.Data.SqlClient.SqlBulkCopy为此使用。这是文档,当然在线上有很多教程。

抱歉,我知道您正在寻找一个简单的答案,以使EF能够执行所需的操作,但是批量操作并不是ORM真正的目的。


1
在研究此问题时,我已经碰到了几次SqlBulkCopy,但它似乎更适合于表到表的插入,可悲的是,我并没有期望简单的解决方案,而希望获得性能提示,例如管理状态表。手动进行连接,让EF为您服务
Bongo Sharp,

7
我已经使用SqlBulkCopy从我的应用程序中插入大量数据。基本上,您必须创建一个DataTable,将其填充,然后将传递给BulkCopy。还有为你设置你的DataTable(其中大部分我忘了,可悲的是)几个陷阱,但它应该只是罚款
亚当Rackis

2
我做了概念证明,顺便说一句,它确实非常快,但是我之所以使用EF的原因之一是因为关系数据的插入更加容易,例如,如果我要插入已经包含关系数据的实体,它也会插入它,您是否遇到过这种情况?谢谢!
Bongo Sharp,

2
不幸的是,将对象网络插入DBMS并不是BulkCopy真正要做的。这就是像EF这样的ORM的好处,其代价是无法有效地执行数百个相似的对象图。
亚当·拉基斯

2
如果您需要原始速度,或者如果您将重新运行此插入,则绝对可以使用SqlBulkCopy。我之前已经插入了几百万条记录,而且速度非常快。也就是说,除非您需要重新运行此插入,否则仅使用EF可能会更容易。
尼尔

49

我同意亚当·拉基斯(Adam Rackis)的观点。SqlBulkCopy是将批量记录从一个数据源传输到另一个数据源的最快方法。我用它来复制2万条记录,花费了不到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();
        }

    }
}

1
我尝试了本文中提供的许多解决方案,而SqlBulkCopy是迄今为止最快的。Pure EF花费了15分钟,但是结合使用解决方案和SqlBulkCopy,我可以减少到1.5分钟!这是200万条记录!没有任何数据库索引优化。
jonas 2015年

列表比DataTable容易。有一个AsDataReader()扩展方法,在此答案中说明:stackoverflow.com/a/36817205/1507899
RJB

但它仅适用于顶级实体,而不是关系型实体
Zahid Mustafa

1
@ZahidMustafa:是的。它是在执行BulkInsert,而不是进行批量分析和关系跟踪对象图。需要,您将获得快速定制的定制解决方案。或者,您可以依靠EF来做到这一点,无需任何工作,但运行时速度较慢。
quetzalcoatl

23

我将推荐这篇有关如何使用EF进行批量插入的文章。

实体框架和慢速批量INSERT

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

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

21

因为这里从来没有提到过,所以我想在这里推荐EFCore.BulkExtensions

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

1
我赞成这个建议。在尝试了多种自制解决方案后,这将我的插入内容从50秒钟缩短到了1秒。而且,它的MIT许可证非常易于合并。
SouthShoreAK '19

这对EF 6.x
有用吗

如果实体超过10个,则仅比使用AddRange更具性能
Jackal

5
10000次插入从9分钟缩短到12秒。这值得更多的注意!
callisto

2
如果有任何方法可以更改已接受的答案,那么现在应该是现代的已接受答案。我希望EF团队提供了开箱即用的功能。
Tanveer Badar

18

我已经研究了Slauma的答案(这太了不起了,谢谢这个主意的人),并且我减小了批量大小,直到达到最佳速度为止。查看Slauma的结果:

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

可以看出,从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是最佳值,也找不到任何合理的解释。


2
我发现与Postrges和纯SQL相同(取决于SQL而不是EF),30最佳。
卡米尔·加列耶夫

我的经验是,对于不同的连接速度和行大小,最佳值有所不同。对于快速连接和小排,最佳甚至可以大于200行。

18

就像其他人说的那样,如果要真正好的插入性能,则可以使用SqlBulkCopy。

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

您唯一需要的代码是:

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

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

当然,一个问题是是否需要插入相关数据。可以使用上述方法有效地将其导入sql server,但它要求您具有ID生成策略,该策略可以让您在父代的应用代码中生成ID,以便您可以设置外键。可以使用GUID或类似HiLo id生成的方法来完成。


效果很好。语法有点冗长。认为最好EFBatchOperation有一个传递DbContext给您的构造函数,而不是传递给每个静态方法。InsertAllUpdateAll可以自动找到集合的通用版本(类似于DbContext.Set<T>)也不错。
kjbartel

请快速发表评论以表示感谢!此代码使我可以在1.5秒内保存170k条记录!彻底吹掉我尝试过的其他任何方法。
汤姆·格伦

@Mikael一个问题是处理身份字段。您是否有启用身份插入的方法?
乔·菲利普斯

1
与EntityFramework.BulkInsert相比,该库保持免费。+1
Rudey '19

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()如果需要的话,在这里不显示它们以保持代码干净


1
使用Entity Framework 6.0减慢了插入(AddRange)的速度。插入20.000行从大约101秒增加到118秒。
啊。

1
@Stephen Ho:我也在努力避免处理我的上下文。我可以理解这比重新创建上下文要慢,但是我想知道您是否比不重新创建上下文但设置​​了commitCount足够快。
学习者

@Learner:我认为这比重新创建上下文要快。但是我真的不记得现在因为我最终切换到使用SqlBulkCopy。
何鸿Stephen

我最终不得不使用此技术,因为出于某些奇怪的原因,即使我将所有内容都包装在using语句中,甚至在DbContext上将其称为Dispose(),也都会在while循环的第二遍中发生一些剩余的跟踪。当我将上下文添加到第二遍时,上下文集计数将跳到6,而不仅仅是1。任意添加的其他项已通过while循环插入到第一遍中,因此对SaveChanges的调用将在第二遍中失败(出于明显的原因)。
Hallmanac 2014年

9

我知道这是一个非常老的问题,但是这里的一个人说,开发了一种扩展方法来将批量插入与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);

请完成您的示例代码。bulkCopy在哪里
Seabizkit

1
它已经在这里:await bulkCopy.WriteToServerAsync(table);
Guilherme

也许我不清楚,在您写的文章中,您建议您进行扩展……这意味着不需要第3部分库,而实际上这两种方法都使用SqlBulkCopy库。这完全取决于SqlBulkCopy,当我问为什么bulkCopy来自哪里时,它是一个扩展库,您在上面编写了一个扩展库。在这里说我是如何使用SqlBulkCopy lib才更有意义。
Seabizkit

应该在异步版本中使用conn.OpenAsync
罗伯特·

6

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


9
如果您不想将数据存储为XML,则不需要将数据作为XML传递。在SQL 2008中,可以使用表值参数。
Ladislav Mrnka,2011年

我没有弄清楚这一点,但我还需要支持SQL 2005
Bongo Sharp,

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

4

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

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

  • Z.EntityFramework.Extensions(推荐
  • 效用
  • EntityFramework.BulkInsert

请参阅:实体框架批量插入库

选择批量插入库时请小心。只有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;
});

19
这是一个很好的扩展,但不是免费的
Okan Kocyigit'4

2
这个答案非常好,并且EntityFramework.BulkInsert在1.5秒内执行了15K行的批量插入,对于Windows Service之类的内部进程来说效果很好。
科尔特斯牧师

4
是的,批量插入价格为600美元。完全值得。
eocron

1
@eocron Yeat如果您将其用于商业用途,则值得。我认为600美元的东西没什么问题,我自己不必花费数小时自行构建它,这将使我花费600美元以上。是的,它花钱,但按我的小时工资来算,这笔钱花得值!
乔迪·范·艾克

3

用途SqlBulkCopy

void BulkInsert(GpsReceiverTrack[] gpsReceiverTracks)
{
    if (gpsReceiverTracks == null)
    {
        throw new ArgumentNullException(nameof(gpsReceiverTracks));
    }

    DataTable dataTable = new DataTable("GpsReceiverTracks");
    dataTable.Columns.Add("ID", typeof(int));
    dataTable.Columns.Add("DownloadedTrackID", typeof(int));
    dataTable.Columns.Add("Time", typeof(TimeSpan));
    dataTable.Columns.Add("Latitude", typeof(double));
    dataTable.Columns.Add("Longitude", typeof(double));
    dataTable.Columns.Add("Altitude", typeof(double));

    for (int i = 0; i < gpsReceiverTracks.Length; i++)
    {
        dataTable.Rows.Add
        (
            new object[]
            {
                    gpsReceiverTracks[i].ID,
                    gpsReceiverTracks[i].DownloadedTrackID,
                    gpsReceiverTracks[i].Time,
                    gpsReceiverTracks[i].Latitude,
                    gpsReceiverTracks[i].Longitude,
                    gpsReceiverTracks[i].Altitude
            }
        );
    }

    string connectionString = (new TeamTrackerEntities()).Database.Connection.ConnectionString;
    using (var connection = new SqlConnection(connectionString))
    {
        connection.Open();
        using (var 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();
        }
    }

    return;
}

3

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

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

AutoDetectChangesEnabled =假

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

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

我可以使用您的示例代码进行批量更新吗?
AminGolmahalle '18

4
Z库不是免费的
-SHADOW.NET,

3

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

3

[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


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

有关更多示例和高级用法,请参阅文档。免责声明:我是该库的作者,任何观点都是我个人的观点。


2
该项目已从NuGet和GitHub中删除。
0xced

1

据我所知no BulkInsertEntityFramework可以提高巨大刀片的性能。

在这种情况下,您可以去使用SqlBulkCopyADO.net为您解决问题


我正在看那个类,但是它似乎更适合于表到表的插入,不是吗?
Bongo Sharp,

不知道您的意思是,它有一个WriteToServer需要重载的重载DataTable
布林迪

不,您也可以从.Net对象插入SQL。您在寻找什么?
anishMarokey,2011年

一种在TransactionScope块内的数据库中插入潜在数千条记录的方法
Bongo Sharp,


1

您是否曾经尝试通过后台工作人员或任务插入?

在我的情况下,我将插入7760个寄存器,这些寄存器分布在具有外键关系的182个不同表中(按NavigationProperties)。

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

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


2
我很确定上下文不是线程安全的。您是否进行测试以确保所有实体都已保存?
Danny Varod 2013年

我知道整个实体框架根本不是线程安全的,但是我只是将对象添加到上下文中并保存到最后……在这里可以完美地工作。
拉斐尔AMS

因此,您在主线程中调用DbContext.SaveChanges(),但是将实体添加到上下文是在后台线程中执行的,对吗?
Prokurors,2014年

1
是的,在线程内添加数据;等待一切完成;和保存主线程中的更改
Rafael AMS 2014年

尽管我认为这种方式很危险并且容易出错,但是我发现它很有趣。
学习者2014年

1

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

例如,如果您到数据库的往返行程为50毫秒,则插入所需的时间为记录数x 50毫秒。

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

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



1

[Postgresql的新解决方案]嗨,我知道这是一篇很老的文章,但是我最近遇到了类似的问题,但是我们使用的是Postgresql。我想使用有效的bulkinsert,事实证明这很困难。我尚未在该数据库上找到任何合适的免费库来这样做。我只找到了这个帮助程序: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是普通实体记录的枚举,我想将其批量插入数据库。

经过数小时的研究和尝试后,我获得了这个解决方案,正如您所期望的那样,它更快,最终易于使用且免费!我真的建议您使用此解决方案,这不仅是因为上述原因,而且还因为它是我对Postgresql本身没有问题的唯一解决方案,许多其他解决方案都可以完美使用,例如与SqlServer一起使用。


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

使用EF,将所有记录添加到一个空的登台表中。然后使用SQL插入到主(大和慢)表中一个单一的 SQL指令。然后清空登台表。这是将大量数据插入到已经很大的表中的非常快速的方法。
西蒙·休斯

13
当您说使用EF时,将记录添加到登台表中,您是否真的使用EF尝试过此操作?由于EF每次插入都会对数据库发出一个单独的调用,因此我怀疑您会看到OP试图避免的相同性能影响。登台表如何避免此问题?
Jim Wooley 2013年

-1

但是,对于超过(+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));
    }

-1

使用以XML形式获取输入数据的存储过程来插入数据。

从您的C#代码中将插入数据作为xml传递。

例如在C#中,语法将如下所示:

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

-7

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

存储过程的代码:

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

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

接下来,遍历所有4000条记录并添加执行存储的Entity Framework代码

程序每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 = "";
    }
}

这可能是有效的,但等效于不使用实体框架。OP的问题是如何在实体框架的背景下最大化效率
kall2sollies
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.