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

时间:2016-09-29 13:29:16

标签: entity-framework entity-framework-6

背景

我们有一个类库,它有一个带有刷新功能的网格(从WPF DataGrid继承)。网格具有IQueryable Query属性,可以进行刷新。每个网格的查询都不在类库中定义,而是在引用的最终项目中定义:

var dg = new RefreshableDataGrid();
dg.Query = () => new ProjectDbContext().Persons;

每个网格还有一个用于文本过滤的文本框。在筛选器中输入文本时,将生成一个表达式,该表达式检查是否有任何字符串属性或字符串可转换属性(使用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)

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

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

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

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

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

因此:

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

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

DbContext GetDbContext<TSource>(IQueryable<TSource> qry) {
    // ???
}

2 个答案:

答案 0 :(得分: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;
}

答案 1 :(得分:0)

我建议将MyDataContext的实例传递给查询函数

public class DACPerson
{
    public static IQueryable<Person> GetAllAsQueryable(MyDataContext db)
    {
        return db.People.AsQueryable();
    }


}

这允许您在调用函数中执行以下操作:

public List<Person> UpdateListofPeople(string term)
{
    using(DataContext db = new DataContext())
    {
        var people = DACPerson.GetAllAsQueryable(db);
        var result = people.Where(x=>x.Username.Contains(term)).

        //call other Data Access Component FUnctions here using same DB....
    }
}

即。您可以将过滤带到数据访问类上方的层。

有些人可能不喜欢这样做。您可以获得最好的建议,即将所有entityframeowrk功能保留在同一层中,然后返回DTO。我喜欢上面的方法。这取决于将来必须维护您应用程序的每个部分。

希望这至少有帮助