c# - update - using system data entity




在實體框架中插入的最快方式 (16)

我正在尋找插入到實體框架中的最快方式

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

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

我正在尋找插入到實體框架中的最快方式。

我問這是因為你有一個活躍的TransactionScope的場景,插入是巨大的(4000+)。 它可能會持續超過10分鐘(事務的默認超時),並且這將導致不完整的事務。


But, for more than (+4000) inserts i recommend to use stored procedure. attached the time elapsed. I did inserted it 11.788 rows in 20"

thats it code

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


[NEW SOLUTION FOR POSTGRESQL] Hey, I know it's quite an old post, but I have recently run into similar problem, but we were using Postgresql. I wanted to use effective bulkinsert, what turned out to be pretty difficult. I haven't found any proper free library to do so on this DB. I have only found this helper: https://bytefish.de/blog/postgresql_bulk_insert/ which is also on Nuget. I have written a small mapper, which auto mapped properties the way 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() 。 (以確保數據完整性)


另一種選擇是使用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。


我同意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進行批量插入。

實體框架和慢批量插入

他探索了這些領域並比較了性能:

  1. 默認EF(57分鐘完成添加30,000條記錄)
  2. 用ADO.NET代碼代替(相同的30,000為25
  3. 上下文膨脹 - 通過為每個工作單元使用新的上下文來保持活動上下文圖小(同樣30,000個插入需要33秒)
  4. 大型列表 - 關閉AutoDetectChangesEnabled(將時間縮短至20秒)
  5. 批量(低至16秒)
  6. DbTable.AddRange() - (性能在12範圍內)

我知道這是一個非常古老的問題,但這裡有一個人說開發了一個使用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);

最快的方法是使用我開發的批量插入擴展

它使用SqlBulkCopy和自定義數據讀取器來獲得最佳性能。 因此它比使用常規插入或AddRange快20多倍

用法非常簡單

context.BulkInsert(hugeAmountOfEntities);

正如其他人所說,如果您想要非常好的插入性能,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代的方式完成。


要對你的問題發表評論:

“... SavingChanges( 對於每個記錄 )...”

這是你能做的最糟糕的事! 為每個記錄調用SaveChanges()會極大地降低批量插入的速度。 我會做幾個簡單的測試,這很可能會提高性能:

  • 在ALL記錄後調用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分鐘內就可以工作。

對於性能,重要的是在“許多”記錄(“100”或1000左右的“許多” SaveChanges()之後調用SaveChanges() )。 它還提高了在SaveChanges之後處理上下文並創建一個上下文的性能。 這會清除所有實體的上下文, 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個實體。)在所有其他測試中,這種非線性行為並不那麼重要。


這種組合提高了速度。

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




entity-framework