Masking types in an IQueryable to avoid eager evaluation in Entity Framework

Tags: Linq2Rest, Entity Framework, IQueryable

A frequent request I get for Linq2Rest is to add better support for Entity Framework. By better support, they mean more deep integration with Entity Framework queries. I have until now resisted this, because Linq2Rest is not intended to be a front for Entity Framework, or any other specific data source. On the other hand I have understood the reason for the request.

At one level Linq2Rest is intended as a query expression parser. This means that it builds up an expression tree based on an OData style query. The built expression tree represents a predicate to filter a collection and return and untyped enumerable (IEnumerable<object>). Why does it return an enumerable of objects and not the original type (IEnumerable<T>)? Because the OData standard supports projections, so it is not possible to know in advance what the return type will be.

This is where the whole problem starts when it comes to supporting Entity Framework (and potentially other data sources that require detailed type information). Entity Framework requires knowledge of the entity type that you are loading. If you simply specify your query as a query over objects, it will throw a NotSupportedException, telling you that "LINQ to Entities only supports casting EDM primitive or enumeration types". This also happens if you attempt to use a CastIterator or an OfTypeIterator (by using the Cast<object> or OfType<object> methods, respectively) over a typed collection. This is for example the case if you attempt to lazily evaluate the query, so as to allow an extension on the OData query.

So since we must support projections, the API must return a untyped result. However we also want to support adding additional query conditions on to the externally defined ones. As described above this is not possible with the existing methods, since returning a common type means returning objects, which leads the Entity Framework query provider to throw an exception.

Since this seemed like a definite obstacle I had no choice but to stick with an untyped result set, and force the user to materialize the results before performing additional filtering. This was not an ideal solution, especially when it came to operations such as counting, where materializing all the entities simply to count them, meant a significant overhead. But what to do?

It struck me today that it could be possible to mask a queryable as an IQueryable<object> while at the same time maintaining the typed query underneath. It you look at the IQueryable<T> interface, then it looks like this:

C#

public interface IQueryable<out T> : IEnumerable<T>, IQueryable, IEnumerable
{
}

public interface IQueryable : IEnumerable
{
	Expression Expression { get; }

	Type ElementType { get; }

	IQueryProvider Provider { get; }
}

The interesting thing to note here, is that only the non-generic interface defines the properties. Of interest is the Provider property, which returns a non-generic IQueryProvider. Since it is non-generic, there is no requirement for it to match the generic argument of the IQueryable<T> that exposes it. This means that if the query provider given to us from Entity Framework is exposed from an IQueryable<object> then the resulting signature is the same as for the projected query.

To achieve this I created a facade class, which ensures that the external interface is consistent, while at the same time exposing the original query provider and underlying type information in the IQueryable interface.

C#

internal class UntypedQueryable<T> : IQueryable<object>
{
	private readonly IQueryable _source;

	public UntypedQueryable(IQueryable<T> source, Expression<Func<T, object>> projection)
	{
		_source = projection == null
					  ? (IQueryable)source
					  : source.Select(projection);
	}

	public Expression Expression
	{
		get { return _source.Expression; }
	}

	public Type ElementType
	{
		get { return typeof(T); }
	}

	public IQueryProvider Provider
	{
		get { return _source.Provider; }
	}

	public IEnumerator<object> GetEnumerator()
	{
		return Enumerable.Cast<object>(_source).GetEnumerator();
	}

	IEnumerator IEnumerable.GetEnumerator()
	{
		return GetEnumerator();
	}
}

Yet again another layer of indirection saves the day. The UntypedQueryable will allow Entity Framework to continue using its query provider and allow optimized queries without requiring specific information about the source type.

Give it a try using the latest Nuget package.

Latest Tweets