Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Extract strongly-typed data context instance from arbitrary query

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) {
    // ???
}
like image 608
Zev Spitz Avatar asked Sep 29 '16 13:09

Zev Spitz


People also ask

What is DbSet in Entity Framework Core?

In Entity Framework Core, the DbSet represents the set of entities. In a database, a group of similar entities is called an Entity Set. The DbSet enables the user to perform various operations like add, remove, update, etc. on the entity set.

How do I select specific columns in Entity Framework?

We can do that simply by using the “new” operator and selecting the properties from the object that we need. In this case, we only want to retrieve the Id and Title columns. There.

How do I run a raw query in EF core?

Entity Framework Core provides the DbSet. FromSql() method to execute raw SQL queries for the underlying database and get the results as entity objects. The following example demonstrates executing a raw SQL query to MS SQL Server database. var context = new SchoolContext(); var students = context.


1 Answers

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;
}
like image 133
Ivan Stoev Avatar answered Nov 28 '22 18:11

Ivan Stoev