c# - first - entity framework教程




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

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

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


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

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

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

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

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

免责声明 :我是Entity Framework Extensions的所有者

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

  • 批量SaveChanges
  • 大容量插入
  • 批量删除
  • 批量更新
  • 批量合并

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

[POSTGRESQL的新解决方案]嗨,我知道这是一个很旧的帖子,但我最近遇到类似的问题,但我们使用Postgresql。 我想使用有效的bulkinsert,结果相当困难。 我还没有找到任何适当的免费图书馆这样做在这个数据库。 我只找到了这个帮助器: https://bytefish.de/blog/postgresql_bulk_insert/ ://bytefish.de/blog/postgresql_bulk_insert/,它也在Nuget上。 我写了一个小的映射器,它以Entity Framework的方式自动映射属性:

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

I use it the following way (I had entity named Undertaking):

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

I showed an example with transaction, but it can also be done with normal connection retrieved from context. undertakingsToAdd is enumerable of normal entity records, which I want to bulkInsert into DB.

This solution, to which I've got after few hours of research and trying, is as you could expect much faster and finally easy to use and free! I really advice you to use this solution, not only for the reasons mentioned above, but also because it's the only one with which I had no problems with Postgresql itself, many other solutions work flawlessly for example with SqlServer.


你应该看看使用System.Data.SqlClient.SqlBulkCopySystem.Data.SqlClient.SqlBulkCopy这一点。 这里有documentation ,当然还有很多在线的教程。

对不起,我知道你正在寻找一个简单的答案来让EF做你想做的事,但批量操作并不是真正的ORM。


你有没有试过插入后台工作人员或任务?

在我的情况下,即时插入7760个寄存器,分布在具有外键关系的182个不同表中(通过NavigationProperties)。

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

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


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

从你的C#代码传入插入数据为XML。

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

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

另一种选择是使用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();
}

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


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


您可以使用批量包库。 批量插入1.0.0版本用于Entity框架> = 6.0.0的项目中。

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


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

    }
}

我已经对上面的@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();
    }
}

我知道这是一个非常古老的问题,但这里有一个人说开发了一个使用EF插件的扩展方法,当我检查时,我发现今天的图书馆(一个开发人员)花费599美元。 也许这对整个库是有意义的,但是对于大容量插入而言,这太多了。

这是我做的一个非常简单的扩展方法。 我首先在数据库上使用它(不要先用代码进行测试,但我认为它的工作原理是一样的)。 使用上下文的名称更改YourEntities

public partial class YourEntities : DbContext
{
    public async Task BulkInsertAllAsync<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;
            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);

据我所知, EntityFramework no BulkInsert来增加巨大插入的性能。

在这种情况下,您可以使用documentation中的documentation来解决您的问题


正如其他人所说,如果您想要非常好的插入性能,SqlBulkCopy就是实现它的方法。

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

您需要的唯一代码是:

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

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

如果你需要插入相关的数据,一个问题是当然的。 这可以通过上面的方法有效地进入sql server,但它需要你有一个Id生成策略,可以让你在父代的应用程序代码中生成id,这样你就可以设置外键。 这可以使用GUID或类似HiLo id代的方式完成。


秘诀在于插入相同的空白登台表。 插入物快速闪烁。 然后从中将单个插入到主大表中。 然后截断准备下一批的临时表。

即。

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

这种组合提高了速度。

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

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

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

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

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





entity-framework