web-dev-qa-db-ja.com

DbSetからDbContextを取得できますか?

私のアプリケーションでは、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を取得する方法はありますか?それをパラメータにするのは好きではありません-それは不要なはずだと感じています。

23
berkeleybross

はい、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所有者への参照を取得できます。

@ LoneyPixel から更新

EF7では、DbSetを実装するクラスに直接プライベートフィールド_contextがあります。このフィールドを公開することは難しくありません

22
smartcaveman

なぜ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();
}
14
Timothy Walters

Entity Framework Core(バージョン2.1でテスト済み)を使用すると、を使用して現在のコンテキストを取得できます

// DbSet<MyModel> myDbSet
var context = myDbSet.GetService<ICurrentDbContext>().Context;

EntityFramework Core 2.0のDbSetからDbContextを取得する方法

14

これを無効にするヘルパーを作成して、AddRangeメソッド内からヘルパーを呼び出すことができるかもしれません。

0
Jay