Ian Newson
Ian Newson

Reputation: 7949

.NET LINQ IQueryable: what is `Expression` for?

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

Answers (1)

xanatos
xanatos

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

Related Questions