Extract strongly-typed data context instance from arbitrary query

我只是一个虾纸丫 提交于 2020-01-02 02:25:27

问题


Background

We have a class library which has a grid (inherits from WPF DataGrid) with refresh functionality. The grid has a IQueryable Query property, which enables the refresh. Each grid's query is defined not in the class library, but in the referencing end-project:

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

Each grid also has a textbox for text filtering. When text is entered in the filter, an expression is generated which checks if any string property or string-convertible property (using SqlFunctions.StringConvert) contains the filter string. The expression is then appended to the original query as an argument to Where, and thus only the records containing matching strings are returned.

//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)

In some cases, the filter logic is defined in end-projects, on the entity type. For example:

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

This expression tree uses an instance of the project-specific context, which is a different instance from that of the original query. Queries cannot use components from multiple contexts (at least not in Entity Framework). I can rewrite the expression tree to use a specific instance, but I need to extract the original instance from the query.

It seems obvious that the query holds some reference to the context instance, otherwise the query would not be able to return results.

I do not want to pass the context instance to the class library.

Hence:

Given a query, how can I get the strongly-typed DbContext instance used to create the query?

In other words, what goes in the body of this method:

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

回答1:


It seems obvious that the query holds some reference to the context instance, otherwise the query would not be able to return results.

That's true, but it's implementation specific detail and in EF is encapsulated inside internal members/classes/interfaces.

Also taking into account that DbContext is build on top of the ObjectContext, holding a reference to the DbContext is not strongly necessary. Fortunately that's not the case :)

The following uses reflection and implementation details of the latest EF6.1.3 (tested and working if you don't use some 3rd party extensions like LinqKit and similar that replace the query provider):

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



回答2:


I would recommend passing an instance of MyDataContext into your query function

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


}

This allows you to do the following in the calling function:

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

i.e. you can bring the filtering to the layer above the data access class.

Some people may not like to do this. You may get the advice that its best to keep all entityframeowrk functionality within the same layer and just return the DTO. I like the above approach though. It depends on who will have to maintain each part of your application in the future.

Hope this helps at least



来源:https://stackoverflow.com/questions/39771701/extract-strongly-typed-data-context-instance-from-arbitrary-query

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!