关于c#:在实体框架中最快的插入方式

Fastest Way of Inserting in Entity Framework

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

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


你在问题评论中的评论:

"...SavingChanges (for each
record)..."

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

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

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
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;
}

我有一个测试程序,它向数据库中插入560000个实体(9个标量属性,没有导航属性)。使用此代码,它可以在3分钟内工作。

对于性能而言,重要的是在"多"记录之后调用SaveChanges()("多"在100或1000左右)。它还提高了在保存更改后释放上下文并创建新上下文的性能。这就从所有实体中清除了上下文,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个实体。)这种非线性行为在所有其他测试中都不那么重要。


这种组合可以很好地提高速度。

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


最快的方法是使用我开发的大容量插入扩展。

它使用sqlbackcopy和自定义数据阅读器来获得最大的性能。因此,它比使用常规的insert或addrange快20倍以上EntityFramework.BulkInsert vs EF AddRange

使用非常简单

1
context.BulkInsert(hugeAmountOfEntities);


您应该考虑使用System.Data.SqlClient.SqlBulkCopy来解决这个问题。这是文档,当然网上有很多教程。

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


我同意亚当拉基斯的观点。SqlBulkCopy是将大容量记录从一个数据源传输到另一个数据源的最快方法。我用这个复制了20K条记录,用了不到3秒钟。请看下面的示例。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
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();
        }

    }
}


我研究了斯劳玛的答案(这太棒了,谢谢你的创意人),我减少了批量,直到达到最佳速度。看看斯劳玛的结果:

  • 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之间的某个值时会发生什么,下面是我的结果(我使用的是不同的行内容,所以我的时间具有不同的值):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
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是最佳的,也不可能找到任何合理的解释。


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

实体框架和缓慢批量插入

他探索了这些领域并比较了表现:

  • 默认ef(57分钟完成添加30000条记录)
  • 替换为ADO.NET代码(相同30000的代码为25秒)
  • 上下文膨胀-通过为每个工作单元使用新的上下文,使活动上下文图保持较小(相同的30000次插入需要33秒)
  • 大列表-关闭自动检测更改已启用(将时间缩短到大约20秒)
  • 批处理(最短16秒)
  • dbtable.addrange()-(性能在12个范围内)

  • 正如其他人所说,如果您真的想要很好的插入性能,那么sqlblkcopy就是这样做的方法。

    实现起来有点麻烦,但是有一些库可以帮助您完成它。外面有一些,但这次我会无耻地插入我自己的库:https://github.com/mikaeleliasson/entityframework.utilities批量插入实体

    您需要的唯一代码是:

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

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

    当然,如果需要插入相关的数据,则有一个问题。这可以使用上述方法在SQL Server中有效地完成,但它要求您具有一个ID生成策略,该策略允许您在父级的应用程序代码中生成ID,以便设置外键。这可以使用guid或类似hilo id生成的东西来完成。


    如果您的实体依赖于上下文中的其他预加载实体(如导航属性),则Dispose()上下文会产生问题。

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

    但是,我只是简单地分离已经存在于SaveChanges()中的实体,而不是Dispose()的上下文并重新创建。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    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()包起来,为了保持代码的整洁而不在这里显示它们


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

    这里有一个非常简单的扩展方法。我首先将它与数据库结合使用(不要先用代码进行测试,但我认为这同样有效)。根据上下文名称更改YourEntities

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    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的任何集合,例如:

    1
    await context.BulkInsertAllAsync(items);


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


    I'm looking for the fastest way of inserting into Entity Framework

    有些第三方库支持大容量插入:

    • z.entityframework.extensions(推荐)
    • 遗赠
    • EntityFramework.BulkInsert

    请参见:实体框架大容量插入库

    选择大容量插入库时要小心。只有实体框架扩展支持所有类型的关联和继承,而且它是唯一仍受支持的。

    免责声明:我是实体框架扩展的所有者

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

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

    例子

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    // 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;
    });


    我对上面的@slauma示例进行了一般扩展;

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    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;
        }
    }

    用途:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    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();
        }
    }

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

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

    autodetectChangesEnabled=false

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

    validateOnSaveEnabled=false;

    未检测到更改跟踪程序

    你必须添加nuget

    1
    Install-Package Z.EntityFramework.Extensions

    现在您可以使用以下代码

    1
    2
    3
    4
    5
    6
    7
    var context = new MyContext();

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

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


    因为这里从来没有提到过,所以我想在这里重新推荐efcore.bulkextensions。

    1
    2
    3
    4
    5
    6
    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);


    下面是使用实体框架和使用sqlbackcopy类之间的性能比较:如何将复杂对象大容量插入到SQL Server数据库中

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


    使用SqlBulkCopy

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    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;
    }

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

    例子:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    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();
    }

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


    这里编写的所有解决方案都没有帮助,因为当您执行savechanges()时,insert语句将逐个发送到数据库,这就是实体的工作方式。

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

    您必须使用BulkInsert,链接如下:https://efbulkInsert.codeplex.com/

    我用它把插入时间从5-6分钟缩短到10-12秒。


    [PostgreSQL的新解决方案]嘿,我知道这是一篇很老的文章,但我最近遇到了类似的问题,但我们使用的是PostgreSQL。我想使用有效的Bulkinsert,结果很困难。我没有找到任何合适的免费图书馆来做这个数据库。我只找到这个助手:https://bytefish.de/blog/postgresql_批量插入/也在Nuget上。我编写了一个小的映射器,它按照实体框架的方式自动映射属性:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    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;
            }

    我使用它的方式如下(我有实体命名为承诺):

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

    我展示了一个事务示例,但也可以通过从上下文中检索到的正常连接来完成。underakingstoad是正常实体记录的可枚举项,我希望将这些记录批量插入到DB中。

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


    sqlblkcopy非常快

    这是我的实现:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    // 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;
            }
        }
    }

    据我所知,EntityFramework中有no BulkInsert,以提高大型插入件的性能。

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


    您可以使用批量包库。在实体框架大于等于6.0.0的项目中使用大容量插入1.0.0版本。

    在这里可以找到更多的描述-批量操作源代码


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

    在我的例子中,我插入了7760个寄存器,分布在182个具有外键关系的不同表中(通过navigationproperties)。

    没有这个任务,花了2分半钟。在一个任务(Task.Factory.StartNew(...)中),需要15秒。

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


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

    从C代码传递中,以XML形式插入数据。

    例如,在C中,语法如下:

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

    但是,对于超过(+4000)个插入,我建议使用存储过程。附加已用时间。我确实在20"enter image description here中插入了11.788行。

    这就是IT代码

    1
    2
    3
    4
    5
    6
    7
     public void InsertDataBase(MyEntity entity)
        {
            repository.Database.ExecuteSqlCommand("sp_mystored" +
                   "@param1, @param2"
                     new SqlParameter("@param1", entity.property1),
                     new SqlParameter("@param2", entity.property2));
        }

    秘密是插入到相同的空白临时表中。插页很快就变亮了。然后在主大表中运行一个插入。然后截断准备好下一批处理的临时表。

    IE.

    1
    2
    3
    4
    5
    6
    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


    enter image description here

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

    存储过程代码:

    1
    2
    3
    4
    5
    6
    7
    CREATE PROCEDURE TestProc
    @FirstParam VARCHAR(50),
    @SecondParam VARCHAR(50)

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

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

    每100个循环执行一次过程。

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

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

    So for 4000 records I only have to execute the procedure for only
    4000/100 = 40 times.

    检查以下代码:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    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 ="";
        }
    }