Autoprojecting LINQ queries
Something I’ve been looking at adding to AutoMapper was the idea of doing automatic query projection in the Select query projection in LINQ statements. One downside of AutoMapper is that projection from domain objects still forces the entire domain object to be queried and loaded. For a lot of read-only scenarios, loading up a tracked, persistent entity is a bit of a waste. And unless you’re doing CQRS with read-specific tables, you’re doing projection somehow from the write tables.
But many LINQ query providers help with this by parsing expression trees to craft specific SQL queries projecting straight down at the SQL layer. Additionally, projecting in to these DTOs skips loading persistent, tracked entities into memory. Unfortunately, we’re then forced to write our boring LHS-RHS code when we drop to this layer:
return Session.Linq<Conference>() .Select(c => new ConferenceShowModel { Name = c.Name, AttendeeCount = c.AttendeeCount, SessionCount = c.SessionCount } ) .ToArray();
It’s this pointless, repetitive mapping code that AutoMapper was intended to avoid. Underneath the covers, the Select clause is just a simple expression:
public static IQueryable<TResult> Select<TSource, TResult>( this IQueryable<TSource> source, Expression<Func<TSource, TResult>> selector)
So our Select clause from the query earlier is really built from building up an expression tree. Instead of building up an expression tree through the C# compiler, why don’t we just automatically build up one ourselves, based on the source-destination type? That’s what I set out to do, at least in a very simplified, non-optimized model. First, I created an extension method to IQueryable that lets me start to build a projection:
public static class QueryableExtensions { public static IProjectionExpression Project<TSource>( this IQueryable<TSource> source) { return new ProjectionExpression<TSource>(source); }
The IProjectionExpression objects lets me then specify a model to project to:
public interface IProjectionExpression { IQueryable<TResult> To<TResult>(); }
The idea being that I’ll be able to Project().To
public class ProjectionExpression<TSource> : IProjectionExpression { private readonly IQueryable<TSource> _source; public ProjectionExpression(IQueryable<TSource> source) { _source = source; } public IQueryable<TResult> To<TResult>() { Expression<Func<TSource, TResult>> expr = BuildExpression<TResult>(); return _source.Select(expr); } public static Expression<Func<TSource, TResult>> BuildExpression<TResult>() { var sourceMembers = typeof(TSource).GetProperties(); var destinationMembers = typeof(TResult).GetProperties(); var name = "src"; var parameterExpression = Expression.Parameter(typeof(TSource), name); return Expression.Lambda<Func<TSource, TResult>>( Expression.MemberInit( Expression.New(typeof(TResult)), destinationMembers.Select(dest => Expression.Bind(dest, Expression.Property( parameterExpression, sourceMembers.First(pi => pi.Name == dest.Name) ) )).ToArray() ), parameterExpression ); } }
It’s not very optimized, as it builds out the expression tree every time. But that’s an easy enhancement, as once the expression tree is built from TSource –> TDestination, it could be statically cached and re-used. But once I have this in place, my LINQ query becomes greatly simplified:
public ConferenceShowModel[] List() { return Session.Linq<Conference>() .Project().To<ConferenceShowModel>() .ToArray(); }
Just one Project().To() method call, and the expression for the projection statement is automatically built up, assuming that all properties match by name. This is a simplified version of what happens in AutoMapper, so you don’t see all of the things that the underlying LINQ query provider supports with projections.
But it’s a start.