8

I have an IQueryable extension method:

public static void SomeExt<T>(this IQueryable<T> query, DbContext context) {...}

and I would like to know if there is some way to get DbContext from query so that DbContext argument could be removed leaving only:

public static void SomeExt<T>(this IQueryable<T> query) {...}

I have tried something like this Access DataContext behind IQueryable but its not working, getting zero fields.

Also there is way to get it from DbSet
Can you get the DbContext from a DbSet?
myDbSet.GetService<'ICurrentDbContext>().Context;
but that's not what I need. I want to get it from Query?

This is the query:
var q = context.Items.Where(a => a.StatusId = 1);

q.SomeExt(context);
vs
q.SomeExt();

Vadim Ovchinnikov
  • 13,327
  • 5
  • 62
  • 90
borisdj
  • 2,201
  • 3
  • 24
  • 31
  • This won't work with just any IQueryable implementation so we have to rely on the underlying types. What is the value of `query.GetType().FullName` for the IQueryable you want to use this on? – Daniel Rothig Nov 07 '18 at 22:22
  • For FullName of this Query type ( is 'Item') I'm getting: "Microsoft.EntityFrameworkCore.Query.Internal.EntityQueryable`1[[EFCore.MyExtensions.Tests.Item, EFCore.MyExtensions.Tests, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null]]" – borisdj Nov 07 '18 at 22:30
  • This is a really peculiar thing to want to do, perhaps you could explain why you need this? – DavidG Nov 07 '18 at 22:33
  • For BatchDelete as Extension on Query. – borisdj Nov 07 '18 at 22:35
  • 1
    You had the exact same problem that I am currently facing! So did you eventually settle on passing the context as a parameter, or did one of the solutions below work out for you? I don't want to bundle the context in with the IQueryable, but I am also not super keen on a solution that could break every time EFCore is updated.... – MSD Feb 09 '21 at 21:05
  • I have used the method GetDbContext(IQueryable query). Changes are not that often and you can make a test for this – borisdj Feb 11 '21 at 08:53

4 Answers4

11

I have found a way to do this

public static DbContext GetDbContext(IQueryable query)
{
    var bindingFlags = BindingFlags.NonPublic | BindingFlags.Instance;
    var queryCompiler = typeof(EntityQueryProvider).GetField("_queryCompiler", bindingFlags).GetValue(query.Provider);
    var queryContextFactory = queryCompiler.GetType().GetField("_queryContextFactory", bindingFlags).GetValue(queryCompiler);

    var dependencies = typeof(RelationalQueryContextFactory).GetProperty("Dependencies", bindingFlags).GetValue(queryContextFactory);
    var queryContextDependencies = typeof(DbContext).Assembly.GetType(typeof(QueryContextDependencies).FullName);
    var stateManagerProperty = queryContextDependencies.GetProperty("StateManager", bindingFlags | BindingFlags.Public).GetValue(dependencies);
    var stateManager = (IStateManager)stateManagerProperty;

    return stateManager.Context;
}

For EFCore 3 instead of

.GetProperty("Dependencies", bindingFlags)

use

.GetField("_dependencies", bindingFlags)
borisdj
  • 2,201
  • 3
  • 24
  • 31
3

If anyone else comes here and is trying to do this with EF Core 7 then the context is a private field of the InternalDbSet object which is created when you do a LINQ query. To get context from IQueryable do this:

FieldInfo fi = query.GetType().GetField("_context", BindingFlags.NonPublic | BindingFlags.Instance);
DbContext context = fi.GetValue(query) as DbContext;
making
  • 408
  • 6
  • 21
  • `fi` returns `null` in EF Core 7. And when I put a breakpoint, I don't see `_context` in the debugger – Felix Aug 06 '23 at 04:43
2

Sounds like you want to implement ActiveRecord in Entity Framework. Many have tried... Best I can suggest is make your context.Items property something LINQ-like that bootlegs the context, e.g:

public class MyContext : DbContext
{
    QueryableWithContext<Item> Items {get => new QueryableWithContext<Item>(ItemsSet, this)}
    private DbSet<Item> ItemsSet {get;set;}
}

public class QueryableWithContext<T>
{
    public DbContext Context { get; }
    private IQueryable<T> inner;

    public QueryableWithContext(IQueryable<T> inner, DbContext context)
    {
        this.inner = inner;
        this.Context = context;
    }

    public QueryableWithContext<T> Where(Func<T,bool> predicate)
    {
        return new QueryableWithContext<T>(inner.Where(predicate) as IQueryable<T>, Context);
    }

    // plus lots of other LINQ-like expressions
}

Then your extension method is not on IQueryable<T> but on QueryableWithContext<T>, and can access the Context property.

Daniel Rothig
  • 696
  • 3
  • 10
2

try this

 public static DbContext GetDbContext(this IQueryable query)
    {
        var compilerField = typeof(EntityQueryProvider).GetField("_queryCompiler", BindingFlags.NonPublic | BindingFlags.Instance);
        var compiler = (QueryCompiler)compilerField.GetValue(query.Provider);

        var queryContextFactoryField = compiler.GetType().GetField("_queryContextFactory", BindingFlags.NonPublic | BindingFlags.Instance);
        var queryContextFactory = (RelationalQueryContextFactory)queryContextFactoryField.GetValue(compiler);


        object stateManagerDynamic;

        var dependenciesProperty = typeof(RelationalQueryContextFactory).GetProperty("Dependencies", BindingFlags.NonPublic | BindingFlags.Instance);
        if (dependenciesProperty != null)
        {
            // EFCore 2.x
            var dependencies = dependenciesProperty.GetValue(queryContextFactory);

            var stateManagerField = typeof(DbContext).GetTypeFromAssembly_Core("Microsoft.EntityFrameworkCore.Query.QueryContextDependencies").GetProperty("StateManager", BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance);
            stateManagerDynamic = stateManagerField.GetValue(dependencies);
        }
        else
        {
            // EFCore 1.x
            var stateManagerField = typeof(QueryContextFactory).GetProperty("StateManager", BindingFlags.NonPublic | BindingFlags.Instance);
            stateManagerDynamic = stateManagerField.GetValue(queryContextFactory);
        }

        IStateManager stateManager = stateManagerDynamic as IStateManager;

        if (stateManager == null)
        {
            Microsoft.EntityFrameworkCore.Internal.LazyRef<IStateManager> lazyStateManager = stateManagerDynamic as Microsoft.EntityFrameworkCore.Internal.LazyRef<IStateManager>;
            if (lazyStateManager != null)
            {
                stateManager = lazyStateManager.Value;
            }
        }

        if (stateManager == null)
        {
            stateManager = ((dynamic)stateManagerDynamic).Value;
        }


        return stateManager.Context;
    }
unos baghaii
  • 2,539
  • 4
  • 25
  • 42