Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Can you get the DbContext from a DbSet?

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.

like image 978
berkeleybross Avatar asked Jul 17 '13 21:07

berkeleybross


People also ask

Is DbSet part of DbContext?

Definition. A DbSet represents the collection of all entities in the context, or that can be queried from the database, of a given type. DbSet objects are created from a DbContext using the DbContext.

What is the difference between DbSet and DbContext?

Intuitively, a DbContext corresponds to your database (or a collection of tables and views in your database) whereas a DbSet corresponds to a table or view in your database. So it makes perfect sense that you will get a combination of both!

What are DbSet and DbContext classes in Entity Framework?

The DbSet class represents an entity set that can be used for create, read, update, and delete operations. The context class (derived from DbContext ) must include the DbSet type properties for the entities which map to database tables and views. Adds the given entity to the context with the Added state.

What is DbSet in Entity Framework Core?

In Entity Framework Core, the DbSet represents the set of entities. In a database, a group of similar entities is called an Entity Set. The DbSet enables the user to perform various operations like add, remove, update, etc. on the entity set.


3 Answers

With Entity Framework Core (tested with Version 2.1) you can get the current context using

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

How to get a DbContext from a DbSet in EntityFramework Core 2.0

like image 84
Christoph Lütjen Avatar answered Sep 24 '22 13:09

Christoph Lütjen


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

like image 21
smartcaveman Avatar answered Sep 23 '22 13:09

smartcaveman


Why are you doing this on the DbSet? Try doing it on the DbContext instead:

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

Then using it is as simple as:

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();
}
like image 40
Timothy Walters Avatar answered Sep 21 '22 13:09

Timothy Walters