【问题标题】:.Net EF Core 2.1 Get DbContext from IQueryable argument.Net EF Core 2.1 从 IQueryable 参数获取 DbContext
【发布时间】:2018-11-07 21:49:52
【问题描述】:

我有一个 IQueryable 扩展方法:

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

我想知道是否有某种方法可以从查询中获取 DbContext,以便可以删除 DbContext 参数,只留下:

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

我尝试过这样的事情 Access DataContext behind IQueryable 但它不起作用,得到零字段。

还有办法从 DbSet
Can you get the DbContext from a DbSet?
myDbSet.GetService().Context;
但这不是我需要的。我想从 Query 中获取它?

这是查询:
var q = context.Items.Where(a => a.StatusId = 1);

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

【问题讨论】:

  • 这不适用于任何 IQueryable 实现,因此我们必须依赖底层类型。 query.GetType().FullName 的值对于您要在其上使用的 IQueryable 是什么?
  • 对于这种查询类型的全名( 是“项目”),我得到:“Microsoft.EntityFrameworkCore.Query.Internal.EntityQueryable`1[[EFCore.MyExtensions.Tests.Item, EFCore.MyExtensions.Tests,版本=1.0.0.0,文化=中性,PublicKeyToken=null]]"
  • 这是一件很奇怪的事情,也许你可以解释一下为什么需要这个?
  • 对于 BatchDelete 作为查询的扩展。
  • 您遇到了与我目前面临的完全相同的问题!那么您最终是否决定将上下文作为参数传递,或者以下解决方案之一是否适合您?我不想将上下文与 IQueryable 捆绑在一起,但我也不太热衷于每次更新 EFCore 时都会中断的解决方案......

标签: entity-framework-core asp.net-core-2.0 dbcontext


【解决方案1】:

我找到了办法

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

对于 EFCore 3 而不是

.GetProperty("Dependencies", bindingFlags)

使用

.GetField("_dependencies", bindingFlags)

【讨论】:

  • 小心!虽然这可行,但它依赖于可能会在每次更新 EFCore 时发生变化的内部私有属性。即使是补丁版本也可能会破坏这一点。
  • 刚刚在 EF Core 6 上执行此操作,它是“依赖项”!大声笑
【解决方案2】:

试试这个

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

【讨论】:

    【解决方案3】:

    听起来您想在实体框架中实现 ActiveRecord。许多人都尝试过...我能建议的最好的方法是让你的 context.Items 属性类似于 LINQ 的东西,它会盗用上下文,例如:

    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
    }
    

    那么你的扩展方法不在IQueryable&lt;T&gt;上,而是在QueryableWithContext&lt;T&gt;上,可以访问Context属性。

    【讨论】:

    • 好主意,但每个集合都需要单独的 QueryableWithContext,所以不理想。我可能会坚持发送 Context 作为参数。
    • 是的,我建议这样做
    猜你喜欢
    • 2017-04-24
    • 2019-07-15
    • 2022-01-16
    • 2020-07-27
    • 1970-01-01
    • 2019-04-30
    • 2020-05-02
    • 2019-04-28
    • 1970-01-01
    相关资源
    最近更新 更多