Welcome to ShenZhenJia Knowledge Sharing Community for programmer and developer-Open, Learning and Share
menu search
person
Welcome To Ask or Share your Answers For Others

Categories

In my application it is sometimes necessary to save 10,000 or more rows to the database in one operation. I've found that simply iterating and adding each item one at a time can take upwards of half an hour.

However, if I disable AutoDetectChangesEnabled it takes ~ 5 seconds (which is exactly what I want)

I'm trying to make an extension method called "AddRange" to DbSet which will disable AutoDetectChangesEnabled and then re-enable it upon completion.

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

So, my question is: Is there a way to get the DbContext from a DbSet? I don't like making it a parameter - It feels like it should be unnecessary.

See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
thumb_up_alt 0 like thumb_down_alt 0 dislike
244 views
Welcome To Ask or Share your Answers For Others

1 Answer

Yes, you can get the DbContext from a DbSet<TEntity>, but the solution is reflection heavy. I have provided an example of how to do this below.

I tested the following code and it was able to successfully retrieve the DbContext instance from which the DbSet was generated. Please note that, although it does answer your question, there is almost certainly a better solution to your problem.

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

Example usage:

using(var originalContextReference = new MyContext())
{
   DbSet<MyObject> set = originalContextReference.Set<MyObject>();
   DbContext retrievedContextReference = set.GetContext();
   Debug.Assert(ReferenceEquals(retrievedContextReference,originalContextReference));
}

Explanation:

According to Reflector, DbSet<TEntity> has a private field _internalSet of type InternalSet<TEntity>. The type is internal to the EntityFramework dll. It inherits from InternalQuery<TElement> (where TEntity : TElement). InternalQuery<TElement> is also internal to the EntityFramework dll. It has a private field _internalContext of type InternalContext. InternalContext is also internal to EntityFramework. However, InternalContext exposes a public DbContext property called Owner. So, if you have a DbSet<TEntity>, you can get a reference to the DbContext owner, by accessing each of those properties reflectively and casting the final result to DbContext.

Update from @LoneyPixel

In EF7 there is a private field _context directly in the class the implements DbSet. It's not hard to expose this field publicly


与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
thumb_up_alt 0 like thumb_down_alt 0 dislike
Welcome to ShenZhenJia Knowledge Sharing Community for programmer and developer-Open, Learning and Share
...