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) {
// ???
}
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.
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.
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.
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;
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With