从任意查询中提取强类型数据上下文实例

Zev*_*itz 8 entity-framework entity-framework-6

背景

我们有一个类库,它有一个带有刷新功能的网格(继承自WPF DataGrid).网格有一个IQueryable Query属性,可以刷新.每个网格的查询不是在类库中定义的,而是在引用的end-project中定义的:

var dg = new RefreshableDataGrid();
dg.Query = () => new ProjectDbContext().Persons;
Run Code Online (Sandbox Code Playgroud)

每个网格还有一个文本框用于文本过滤.在筛选器中输入文本时,将生成一个表达式,该表达式检查是否有任何字符串属性或字符串可转换属性(使用SqlFunctions.StringConvert)包含筛选字符串.然后将表达式作为参数附加到原始查询Where,因此仅返回包含匹配字符串的记录.

//within the class library
//pseudo-code -- this is actually done via reflection, because at compile time the
//actual type of the grid is not known, and there is no generic placeholder
this.ItemsSource = this.Query.Where(filterExpression)
Run Code Online (Sandbox Code Playgroud)

在某些情况下,过滤器逻辑在实体类型的最终项目中定义.例如:

public interface IFilterable {
    public Expression<Func<String, Boolean>> TextSearchExpression();
}

public class Email {
    public int ID {get;set;}
    public int PersonID {get;set;}
    public string Address {get;set;}
}

public class Person : IFilterable
    public int ID {get;set;}
    public string LastName {get;set;}
    public string FirstName {get;set;}
    public Expression<Func<String, Boolean>> TextSearchExpression() {
        Dim ctx = new ProjectDbContext();
        return phrase => LastName.Contains(phrase) || FirstName.Contains(phrase) || 
            ctx.Emails.Where(x => x.PersonID = ID && x.Address.Contains(prase).Any();
    }
}
Run Code Online (Sandbox Code Playgroud)

此表达式树使用项目特定上下文的实例,该实例与原始查询的实例不同.查询不能使用来自多个上下文的组件(至少不在实体框架中).我可以重写表达式树以使用特定的实例,但我需要从查询中提取原始实例.

很明显,查询包含对上下文实例的一些引用,否则查询将无法返回结果.

我不想将上下文实例传递给类库.

因此:

给定查询,如何获取DbContext用于创建查询的强类型实例?

换句话说,这种方法的主体是什么:

DbContext GetDbContext<TSource>(IQueryable<TSource> qry) {
    // ???
}
Run Code Online (Sandbox Code Playgroud)

Iva*_*oev 2

显然,查询保留了对上下文实例的一些引用,否则查询将无法返回结果。

确实如此,但它是特定于实现的细节,并且在 EF 中封装在内部成员/类/接口中。

另外考虑到DbContext是建立在 之上的ObjectContext,持有对 的引用DbContext并不是非常必要的。幸运的是事实并非如此:)

以下使用最新 EF6.1.3 的反射和实现细节(如果您不使用某些第三方扩展(例如LinqKit和类似的替换查询提供程序的扩展),则经过测试和工作):

public static DbContext GetDbContext<TSource>(this IQueryable<TSource> query)
{
    const BindingFlags flags = BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public;
    var provider = query.Provider;
    var internalContextProperty = provider.GetType().GetProperty("InternalContext", flags);
    if (internalContextProperty == null) return null;
    var internalContext = internalContextProperty.GetValue(provider, null);
    if (internalContext == null) return null;
    var ownerProperty = internalContext.GetType().GetProperty("Owner", flags);
    if (ownerProperty == null) return null;
    var dbContext = (DbContext)ownerProperty.GetValue(internalContext, null);
    return dbContext;
}
Run Code Online (Sandbox Code Playgroud)