私のアプリケーションでは、1回の操作で10,000行以上をデータベースに保存する必要がある場合があります。各アイテムを一度に1つずつ繰り返して追加するだけで、30分以上かかる場合があることがわかりました。
ただし、AutoDetectChangesEnabledを無効にすると、約5秒かかります(これはまさに私が望むものです)
AutoDetectChangesEnabledを無効にし、完了時に再度有効にする「AddRange」という拡張メソッドをDbSetに作成しようとしています。
public static void AddRange<TEntity>(this DbSet<TEntity> set, DbContext con, IEnumerable<TEntity> items) where TEntity : class
{
// Disable auto detect changes for speed
var detectChanges = con.Configuration.AutoDetectChangesEnabled;
try
{
con.Configuration.AutoDetectChangesEnabled = false;
foreach (var item in items)
{
set.Add(item);
}
}
finally
{
con.Configuration.AutoDetectChangesEnabled = detectChanges;
}
}
だから、私の質問は:DbSetからDbContextを取得する方法はありますか?それをパラメータにするのは好きではありません-それは不要なはずだと感じています。
はい、DbSet<TEntity>
からDbContext
を取得できますが、解決策は反射が重いです。これを行う方法の例を以下に示します。
次のコードをテストしたところ、DbContext
が生成されたDbSet
インスタンスを正常に取得できました。それはあなたの質問に答えますが、あなたの問題に対するより良い解決策がほぼ確実にあることに注意してください。
public static class HackyDbSetGetContextTrick
{
public static DbContext GetContext<TEntity>(this DbSet<TEntity> dbSet)
where TEntity: class
{
object internalSet = dbSet
.GetType()
.GetField("_internalSet",BindingFlags.NonPublic|BindingFlags.Instance)
.GetValue(dbSet);
object internalContext = internalSet
.GetType()
.BaseType
.GetField("_internalContext",BindingFlags.NonPublic|BindingFlags.Instance)
.GetValue(internalSet);
return (DbContext)internalContext
.GetType()
.GetProperty("Owner",BindingFlags.Instance|BindingFlags.Public)
.GetValue(internalContext,null);
}
}
使用例:
using(var originalContextReference = new MyContext())
{
DbSet<MyObject> set = originalContextReference.Set<MyObject>();
DbContext retrievedContextReference = set.GetContext();
Debug.Assert(ReferenceEquals(retrievedContextReference,originalContextReference));
}
説明:
Reflectorによると、DbSet<TEntity>
にはタイプ_internalSet
のプライベートフィールドInternalSet<TEntity>
があります。タイプはEntityFrameworkdllの内部です。 InternalQuery<TElement>
(ここでTEntity : TElement
)から継承します。 InternalQuery<TElement>
もEntityFrameworkdllの内部にあります。タイプInternalContext
のプライベートフィールド_internalContext
があります。 InternalContext
もEntityFrameworkの内部にあります。ただし、InternalContext
は、DbContext
と呼ばれるパブリックOwner
プロパティを公開します。したがって、DbSet<TEntity>
がある場合は、これらの各プロパティに反射的にアクセスし、最終結果をDbContext
にキャストすることで、DbContext
所有者への参照を取得できます。
EF7では、DbSetを実装するクラスに直接プライベートフィールド_contextがあります。このフィールドを公開することは難しくありません
なぜDbSetでこれを行うのですか?代わりに、DbContextで実行してみてください。
public static void AddRangeFast<T>(this DbContext context, IEnumerable<T> items) where T : class
{
var detectChanges = context.Configuration.AutoDetectChangesEnabled;
try
{
context.Configuration.AutoDetectChangesEnabled = false;
var set = context.Set<T>();
foreach (var item in items)
{
set.Add(item);
}
}
finally
{
context.Configuration.AutoDetectChangesEnabled = detectChanges;
}
}
次に、それを使用するのは次のように簡単です。
using (var db = new MyContext())
{
// slow add
db.MyObjects.Add(new MyObject { MyProperty = "My Value 1" });
// fast add
db.AddRangeFast(new[] {
new MyObject { MyProperty = "My Value 2" },
new MyObject { MyProperty = "My Value 3" },
});
db.SaveChanges();
}
Entity Framework Core(バージョン2.1でテスト済み)を使用すると、を使用して現在のコンテキストを取得できます
// DbSet<MyModel> myDbSet
var context = myDbSet.GetService<ICurrentDbContext>().Context;
これを無効にするヘルパーを作成して、AddRangeメソッド内からヘルパーを呼び出すことができるかもしれません。