Reusing parts of LINQ queries is easily done by chaining methods returning IQueryable. Unfortunately that doesn’t cover some senarios like reusing member initialization lists. To handle such a situation I wrote a Mergeextension method for select expressions.
For this post we’ll load the data required for a list of cars and a detail view for one car. When loading the details, more fields are required than when just loading the list. With the merging helper method we can reuse the query for the basic list and just extend it with the added properties.
The basic info for the list view is held in a CarBasicInfoDTO class. The data for the detail view is held in a CarExtendedInfo class which is derived from the CarBasicInfo class. A helper method contains the Select() call to map Car entities to the basic DTO. Now the Merge extension method can be used to merge an expression with the initialization of the additional fields for the extended DTO with the existing helper method for the basic fields.
private Expression<Func<Car, CarBasicInfo>> basicSelect =
c =>new CarBasicInfo
CarId = c.CarId,
RegistrationNumber = c.RegistrationNumber};var car = ctx.Cars.Select(basicSelect.Merge(c =>new CarExtendedInfo
Color = c.Color,
BrandName = c.Brand.Name})).Single(c => c.RegistrationNumber=="ABC123");
The RegistrationNumber field is not mentioned in the init list – it is populated by the basic select in the SelectBasicInfo helper method. To accomplish this some (non trivial) rewriting of expression trees is in the Merge method.
The Expression<T> data type which was introduced together with LINQ is interesting. When a Func<TResult> is wrapped in an Expression<T> the code is no longer completely compiled, instead it is preserved as an expression tree that can be traversed during runtime. This is what the method ExpressionHelper.GetExpressionText() that I used in my last post Type Safe SelectList Factory utilizes. In that post I used a simple lambda to select a property of a class.
IEnumerable = GetPeople();
SelectList selectList =
people.ToSelectList(people, p => p.Id, p => p.FullName);
To check how this works under the hood, I inspected the lambda object in a debugger and made a graph overview with the excellent dot tool.