web-dev-qa-db-ja.com

Entity Frameworkでの一括挿入のパフォーマンスの改善

エンティティフレームワークごとに20000レコードをテーブルに挿入したいのですが、約2分かかります。 SPを使用してパフォーマンスを改善する以外の方法はありますか。これは私のコードです:

 foreach (Employees item in sequence)
 {
   t = new Employees ();
   t.Text = item.Text;
   dataContext.Employees.AddObject(t);                  
 }
 dataContext.SaveChanges();
109
Vahid Ghadiri

いくつかの改善の機会があります(DbContextを使用している場合):

設定:

yourContext.Configuration.AutoDetectChangesEnabled = false;
yourContext.Configuration.ValidateOnSaveEnabled = false;

100挿入のパッケージでSaveChanges()を実行...または、1000アイテムのパッケージを試して、パフォーマンスの変化を確認できます。

このすべての挿入中、コンテキストは同じであり、より大きくなっているため、挿入1000回ごとにコンテキストオブジェクトを再構築します。var yourContext = new YourContext();これは大きなメリットだと思います。

私のデータのインポートプロセスでこの改善を行うには、7分から6秒かかりました。

実際の数字は...あなたの場合100または1000にはできませんでした...試してみてください。

207
Romias

この方法でEFにパフォーマンスを向上させる方法はありません。問題は、EFが各挿入をデータベースへの個別のラウンドトリップで実行することです。素晴らしいですね。データセットでさえバッチ処理をサポートしていました。回避策については、 この記事 を確認してください。別の回避策は、テーブル値パラメーターを受け入れるカスタムストアドプロシージャを使用することですが、そのためには生のADO.NETが必要です。

42
Ladislav Mrnka

bulk insert extension を使用できます

ここに小さな比較表があります

EntityFramework.BulkInsert vs EF AddRangecontext.BulkInsert(hugeAmountOfEntities);

お役に立てれば

hope this helps

33
maxlego

以下のコードを使用して、エンティティオブジェクトのコレクションを取得し、それらをデータベースに一括コピーするメソッドを使用して、部分コンテキストクラスを拡張できます。クラスの名前をMyEntitiesからエンティティクラスの名前に置き換え、正しい名前空間でプロジェクトに追加するだけです。その後、行う必要があるのは、挿入するエンティティオブジェクトを渡すBulkInsertAllメソッドを呼び出すことだけです。コンテキストクラスを再利用せずに、使用するたびに新しいインスタンスを作成してください。ここで使用されるSQLConnectionに関連付けられている認証データは、クラスを1回使用すると失われるため、少なくともEFの一部のバージョンではこれが必要です。理由はわかりません。

このバージョンはEF 5用です

public partial class MyEntities
{
    public void BulkInsertAll<T>(T[] entities) where T : class
    {
        var conn = (SqlConnection)Database.Connection;

        conn.Open();

        Type t = typeof(T);
        Set(t).ToString();
        var objectContext = ((IObjectContextAdapter)this).ObjectContext;
        var workspace = objectContext.MetadataWorkspace;
        var mappings = GetMappings(workspace, objectContext.DefaultContainerName, typeof(T).Name);

        var tableName = GetTableName<T>();
        var bulkCopy = new SqlBulkCopy(conn) { DestinationTableName = tableName };

        // Foreign key relations show up as virtual declared 
        // properties and we want to ignore these.
        var properties = t.GetProperties().Where(p => !p.GetGetMethod().IsVirtual).ToArray();
        var table = new DataTable();
        foreach (var property in properties)
        {
            Type propertyType = property.PropertyType;

            // Nullable properties need special treatment.
            if (propertyType.IsGenericType &&
                propertyType.GetGenericTypeDefinition() == typeof(Nullable<>))
            {
                propertyType = Nullable.GetUnderlyingType(propertyType);
            }

            // Since we cannot trust the CLR type properties to be in the same order as
            // the table columns we use the SqlBulkCopy column mappings.
            table.Columns.Add(new DataColumn(property.Name, propertyType));
            var clrPropertyName = property.Name;
            var tableColumnName = mappings[property.Name];
            bulkCopy.ColumnMappings.Add(new SqlBulkCopyColumnMapping(clrPropertyName, tableColumnName));
        }

        // Add all our entities to our data table
        foreach (var entity in entities)
        {
            var e = entity;
            table.Rows.Add(properties.Select(property => GetPropertyValue(property.GetValue(e, null))).ToArray());
        }

        // send it to the server for bulk execution
        bulkCopy.BulkCopyTimeout = 5 * 60;
        bulkCopy.WriteToServer(table);

        conn.Close();
    }

    private string GetTableName<T>() where T : class
    {
        var dbSet = Set<T>();
        var sql = dbSet.ToString();
        var regex = new Regex(@"FROM (?<table>.*) AS");
        var match = regex.Match(sql);
        return match.Groups["table"].Value;
    }

    private object GetPropertyValue(object o)
    {
        if (o == null)
            return DBNull.Value;
        return o;
    }

    private Dictionary<string, string> GetMappings(MetadataWorkspace workspace, string containerName, string entityName)
    {
        var mappings = new Dictionary<string, string>();
        var storageMapping = workspace.GetItem<GlobalItem>(containerName, DataSpace.CSSpace);
        dynamic entitySetMaps = storageMapping.GetType().InvokeMember(
            "EntitySetMaps",
            BindingFlags.GetProperty | BindingFlags.NonPublic | BindingFlags.Instance,
            null, storageMapping, null);

        foreach (var entitySetMap in entitySetMaps)
        {
            var typeMappings = GetArrayList("TypeMappings", entitySetMap);
            dynamic typeMapping = typeMappings[0];
            dynamic types = GetArrayList("Types", typeMapping);

            if (types[0].Name == entityName)
            {
                var fragments = GetArrayList("MappingFragments", typeMapping);
                var fragment = fragments[0];
                var properties = GetArrayList("AllProperties", fragment);
                foreach (var property in properties)
                {
                    var edmProperty = GetProperty("EdmProperty", property);
                    var columnProperty = GetProperty("ColumnProperty", property);
                    mappings.Add(edmProperty.Name, columnProperty.Name);
                }
            }
        }

        return mappings;
    }

    private ArrayList GetArrayList(string property, object instance)
    {
        var type = instance.GetType();
        var objects = (IEnumerable)type.InvokeMember(property, BindingFlags.GetProperty | BindingFlags.NonPublic | BindingFlags.Instance, null, instance, null);
        var list = new ArrayList();
        foreach (var o in objects)
        {
            list.Add(o);
        }
        return list;
    }

    private dynamic GetProperty(string property, object instance)
    {
        var type = instance.GetType();
        return type.InvokeMember(property, BindingFlags.GetProperty | BindingFlags.NonPublic | BindingFlags.Instance, null, instance, null);
    }
}

このバージョンはEF 6用です

public partial class CMLocalEntities
{
    public void BulkInsertAll<T>(T[] entities) where T : class
    {
        var conn = (SqlConnection)Database.Connection;

        conn.Open();

        Type t = typeof(T);
        Set(t).ToString();
        var objectContext = ((IObjectContextAdapter)this).ObjectContext;
        var workspace = objectContext.MetadataWorkspace;
        var mappings = GetMappings(workspace, objectContext.DefaultContainerName, typeof(T).Name);

        var tableName = GetTableName<T>();
        var bulkCopy = new SqlBulkCopy(conn) { DestinationTableName = tableName };

        // Foreign key relations show up as virtual declared 
        // properties and we want to ignore these.
        var properties = t.GetProperties().Where(p => !p.GetGetMethod().IsVirtual).ToArray();
        var table = new DataTable();
        foreach (var property in properties)
        {
            Type propertyType = property.PropertyType;

            // Nullable properties need special treatment.
            if (propertyType.IsGenericType &&
                propertyType.GetGenericTypeDefinition() == typeof(Nullable<>))
            {
                propertyType = Nullable.GetUnderlyingType(propertyType);
            }

            // Since we cannot trust the CLR type properties to be in the same order as
            // the table columns we use the SqlBulkCopy column mappings.
            table.Columns.Add(new DataColumn(property.Name, propertyType));
            var clrPropertyName = property.Name;
            var tableColumnName = mappings[property.Name];
            bulkCopy.ColumnMappings.Add(new SqlBulkCopyColumnMapping(clrPropertyName, tableColumnName));
        }

        // Add all our entities to our data table
        foreach (var entity in entities)
        {
            var e = entity;
            table.Rows.Add(properties.Select(property => GetPropertyValue(property.GetValue(e, null))).ToArray());
        }

        // send it to the server for bulk execution
        bulkCopy.BulkCopyTimeout = 5*60;
        bulkCopy.WriteToServer(table);

        conn.Close();
    }

    private string GetTableName<T>() where T : class
    {
        var dbSet = Set<T>();
        var sql = dbSet.ToString();
        var regex = new Regex(@"FROM (?<table>.*) AS");
        var match = regex.Match(sql);
        return match.Groups["table"].Value;
    }

    private object GetPropertyValue(object o)
    {
        if (o == null)
            return DBNull.Value;
        return o;
    }

    private Dictionary<string, string> GetMappings(MetadataWorkspace workspace, string containerName, string entityName)
    {
        var mappings = new Dictionary<string, string>();
        var storageMapping = workspace.GetItem<GlobalItem>(containerName, DataSpace.CSSpace);
        dynamic entitySetMaps = storageMapping.GetType().InvokeMember(
            "EntitySetMaps",
            BindingFlags.GetProperty | BindingFlags.Public | BindingFlags.Instance,
            null, storageMapping, null);

        foreach (var entitySetMap in entitySetMaps)
        {
            var typeMappings = GetArrayList("EntityTypeMappings", entitySetMap);
            dynamic typeMapping = typeMappings[0];
            dynamic types = GetArrayList("Types", typeMapping);

            if (types[0].Name == entityName)
            {
                var fragments = GetArrayList("MappingFragments", typeMapping);
                var fragment = fragments[0];
                var properties = GetArrayList("AllProperties", fragment);
                foreach (var property in properties)
                {
                    var edmProperty = GetProperty("EdmProperty", property);
                    var columnProperty = GetProperty("ColumnProperty", property);
                    mappings.Add(edmProperty.Name, columnProperty.Name);
                }
            }
        }

        return mappings;
    }

    private ArrayList GetArrayList(string property, object instance)
    {
        var type = instance.GetType();
        var objects = (IEnumerable)type.InvokeMember(
            property, 
            BindingFlags.GetProperty | BindingFlags.Public | BindingFlags.Instance, null, instance, null);
        var list = new ArrayList();
        foreach (var o in objects)
        {
            list.Add(o);
        }
        return list;
    }

    private dynamic GetProperty(string property, object instance)
    {
        var type = instance.GetType();
        return type.InvokeMember(property, BindingFlags.GetProperty | BindingFlags.Public | BindingFlags.Instance, null, instance, null);
    }

}

そして最後に、Linq-To-Sqlの愛好家にちょっとしたものを。

partial class MyDataContext
{
    partial void OnCreated()
    {
        CommandTimeout = 5 * 60;
    }

    public void BulkInsertAll<T>(IEnumerable<T> entities)
    {
        entities = entities.ToArray();

        string cs = Connection.ConnectionString;
        var conn = new SqlConnection(cs);
        conn.Open();

        Type t = typeof(T);

        var tableAttribute = (TableAttribute)t.GetCustomAttributes(
            typeof(TableAttribute), false).Single();
        var bulkCopy = new SqlBulkCopy(conn) { 
            DestinationTableName = tableAttribute.Name };

        var properties = t.GetProperties().Where(EventTypeFilter).ToArray();
        var table = new DataTable();

        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 => GetPropertyValue(
              property.GetValue(entity, null))).ToArray());
        }

        bulkCopy.WriteToServer(table);
        conn.Close();
    }

    private bool EventTypeFilter(System.Reflection.PropertyInfo p)
    {
        var attribute = Attribute.GetCustomAttribute(p, 
            typeof (AssociationAttribute)) as AssociationAttribute;

        if (attribute == null) return true;
        if (attribute.IsForeignKey == false) return true; 

        return false;
    }

    private object GetPropertyValue(object o)
    {
        if (o == null)
            return DBNull.Value;
        return o;
    }
}
25
Måns Tånneryd

たぶん、これは answer こちらが役に立ちます。コンテキストを定期的に破棄したいようです。これは、アタッチされたエンティティが大きくなるにつれてコンテキストが大きくなるためです。

8
MemeDeveloper

より良い方法は、この操作でEntity Frameworkを完全にスキップし、SqlBulkCopyクラスに依存することです。他の操作は以前と同様にEFを使用して続行できます。

これにより、ソリューションのメンテナンスコストが増加しますが、とにかく、EFを使用する場合と比較して、オブジェクトの大規模なコレクションをデータベースに挿入するのに必要な時間を1〜2桁削減できます。

親子関係を持つオブジェクトのSqlBulkCopyクラスとEFを比較する記事を次に示します(一括挿入を実装するために必要な設計の変更についても説明します): 複雑なオブジェクトをSQL Serverデータベースに一括挿入する方法

4
Zoran Horvat

インスタンスが1つある基本的なWebサイトを持つAzure環境では、forループを使用して25000レコードのうち1000レコードのバッチを一度に挿入しようとしましたが、11.5分かかりましたが、並列実行では1分未満でした。 (タスク並列ライブラリ)。

         var count = (you collection / 1000) + 1;
         Parallel.For(0, count, x =>
        {
            ApplicationDbContext db1 = new ApplicationDbContext();
            db1.Configuration.AutoDetectChangesEnabled = false;

            var records = members.Skip(x * 1000).Take(1000).ToList();
            db1.Members.AddRange(records).AsParallel();

            db1.SaveChanges();
            db1.Dispose();
        });
4
user3571683

現在、これ以上良い方法はありませんが、おそらく10個のアイテムのSaveChangesをforループ内に移動することにより、わずかな改善があるかもしれません。

int i = 0;

foreach (Employees item in sequence)
{
   t = new Employees ();
   t.Text = item.Text;
   dataContext.Employees.AddObject(t);   

   // this will add max 10 items together
   if((i % 10) == 0){
       dataContext.SaveChanges();
       // show some progress to user based on
       // value of i
   }
   i++;
}
dataContext.SaveChanges();

10を調整して、パフォーマンスを向上させることができます。速度はそれほど向上しませんが、ユーザーにある程度の進歩を見せて、ユーザーフレンドリーにすることができます。

4
Akash Kava

コードには2つの主要なパフォーマンスの問題があります。

  • Addメソッドを使用する
  • SaveChangesを使用する

Addメソッドの使用

Addメソッドは、追加する各エンティティでますます遅くなります。

参照: http://entityframework.net/improve-ef-add-performance

たとえば、次の方法で10,000エンティティを追加します。

  • 追加(最大105,000msかかります)
  • AddRange(〜120msかかります)

注:エンティティはまだデータベースに保存されていません!

問題は、Addメソッドが追加されたすべてのエンティティでDetectChangesを検出しようとするのに対して、AddRangeはすべてのエンティティがコンテキストに追加された後に1回実行することです。

一般的なソリューションは次のとおりです。

  • AddでAddRangeを使用する
  • AutoDetectChangesをfalseに設定
  • 複数のバッチでのSaveChangesの分割

SaveChangesの使用

一括操作用のEntity Frameworkは作成されていません。保存するすべてのエンティティに対して、データベースの往復が実行されます。

したがって、20,000件のレコードを挿入する場合、20,000件のデータベースラウンドトリップ非常識を実行します。

一括挿入をサポートするサードパーティライブラリがいくつかあります。

  • Z.EntityFramework.Extensions(Recommended
  • 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;
});

編集:コメントで質問に答える

作成したライブラリの各一括挿入の推奨最大サイズはありますか

高すぎず、低すぎません。行サイズ、インデックス、トリガーなどの複数の要因に依存するため、すべてのシナリオに適合する特定の値はありません。

通常、約4000にすることをお勧めします。

また、1つのトランザクションですべてを結び付け、タイムアウトを心配する方法はありますか

Entity Frameworkトランザクションを使用できます。ライブラリは、トランザクションが開始された場合にそれを使用します。ただし、時間がかかりすぎるトランザクションには、行/インデックス/テーブルのロックなどの問題も伴うことに注意してください。

4
Jonathan Magnan

一括挿入を使用してみてください...

http://code.msdn.Microsoft.com/LinqEntityDataReader

StoreEntitiesなどのエンティティのコレクションがある場合、次のようにSqlBulkCopyを使用してそれらを保存できます

        var bulkCopy = new SqlBulkCopy(connection);
        bulkCopy.DestinationTableName = TableName;
        var dataReader = storeEntities.AsDataReader();
        bulkCopy.WriteToServer(dataReader);

このコードには1つの落とし穴があります。エンティティのEntity Framework定義がテーブル定義と正確に関連付けられていることを確認し、エンティティのプロパティがSQL Serverテーブルの列とエンティティモデルで同じ順序であることを確認します。これを行わないと、例外が発生します。

3
Mick

返信が遅くなりましたが、同じ痛みに苦しんでいたので、私は答えを投稿しています。そのためだけに新しいGitHubプロジェクトを作成しました。現時点では、SqlBulkCopyを使用して、SQLサーバーの一括挿入/更新/削除を透過的にサポートしています。

https://github.com/MHanafy/EntityExtensions

他にも便利な機能がありますが、うまくいけば、さらに多くの機能を実行できるように拡張されます。

それを使用するのは簡単です

var insertsAndupdates = new List<object>();
var deletes = new List<object>();
context.BulkUpdate(insertsAndupdates, deletes);

それが役に立てば幸い!

0
Mahmoud Hanafy