Reputation: 7949
I'm working on a LINQ provider so am implementing IQueryable.
What is the purpose of the Expression
property within this interface? I normally just return something like Expression.Constant(this)
from my implementations, but have no idea if that's bad.
Bizarrely the documentation states "This allows the framework to differentiate between LINQ and Entity SQL queries".
Upvotes: 4
Views: 3149
Reputation: 111890
The IQueryable.Expression
member is fed back to the IQueryProvider
by all the various Queryable.*
"operators" (.Where()
, .Select()
, .Join()
, ...), like:
public static IQueryable<TSource> Where<TSource>(this IQueryable<TSource> source, Expression<Func<TSource, bool>> predicate) {
if (source == null)
throw Error.ArgumentNull("source");
if (predicate == null)
throw Error.ArgumentNull("predicate");
return source.Provider.CreateQuery<TSource>(
Expression.Call(
null,
((MethodInfo)MethodBase.GetCurrentMethod()).MakeGenericMethod(typeof(TSource)),
new Expression[] { source.Expression, Expression.Quote(predicate) }
));
}
(taken from the referencesource)
Normally it should be the whole expression.
Clearly no one will kill you if you pass directly a whole reference to your IQueryable
class through the Expression.Constant()
, but I do think that this isn't "kosher".
The point of putting the "real" expression in Expression
(like it is done by the Enumerable.AsQueryable()
, by EF and by LINQ-to-SQL, just to name three IQueryable
providers) is that other external classes can freely analyze and manipulate the Expression
and feed it back to the provider in the same way that the Queryable.Where
does, so doing
Expression expression = source.Expression;
// here "expression" is manipulated
return source.Provider.CreateQuery<SomeType>(expression);
To give an example... There are some "query fixer"s that modify the query like https://github.com/davidfowl/QueryInterceptor (a generic modifier for queries), or https://github.com/hazzik/DelegateDecompiler, that permits to do:
var employees = (from employee in db.Employees
where employee.FullName == "Test User"
select employee).Decompile().ToList();
where FullName
isn't mapped in the DB, and is a property like:
class Employee
{
[Computed]
public string FullName
{
get { return FirstName + " " + LastName; }
}
(with FirstName
and LastName
mapped to the db). DelegateDecompiler takes the Expression
from the IQueryable
, searches for properties that have the Computed
attribute, decompiles them and places the decompiled code (converted to an expression tree) back in the IQueryable.Expression
(though the use of IQueryable.Provider.CreateQuery()
)
If you want to save additional data, you can put it in the Provider
: you can generate a new instance of the IQueryProvider
class in the CreateQuery
method. That too is fed back by the Queryable.*
operators (because the CreateQuery<>
is an instance method of source.Provider
)
Upvotes: 6