Created
February 13, 2011 10:57
-
-
Save AndyHitchman/824585 to your computer and use it in GitHub Desktop.
Enhanced query projections with custom override [based on http://www.lostechies.com/blogs/jimmy_bogard/archive/2011/02/09/autoprojecting-linq-queries.aspx]
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
output = | |
(from i in session.Query<Input>() | |
select i) | |
.Project().To<Output>( | |
mapper => | |
mapper | |
.Map(o => o.BasicCustom, i => i.Custom) | |
.Map(o => o.WackyCustom, i => "modified " + i.Custom) | |
.Ignore(o => o.Ignored) | |
) | |
.Single(); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
public class Output | |
{ | |
public string Simple { get; set; } | |
public string BasicCustom { get; set; } | |
public string WackyCustom { get; set; } | |
public string Ignored { get; set; } | |
} | |
public class Input | |
{ | |
public virtual int Id { get; set; } | |
public virtual string Simple { get; set; } | |
public virtual string Custom { get; set; } | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using System; | |
using System.Linq; | |
public interface IProjectionExpression<TSource> | |
{ | |
/// <summary> | |
/// Project a elements of a query to a to a compatible model. | |
/// </summary> | |
/// <remarks> | |
/// Each property of <typeparamref name="TDest"/> is expected in <typeparamref name="TSource"/> with | |
/// the same name. The source property is assigned to the destination property, and hence must be of the same | |
/// type or implicitly castable. | |
/// </remarks> | |
/// <typeparam name="TDest"></typeparam> | |
/// <returns></returns> | |
IQueryable<TDest> To<TDest>(); | |
/// <summary> | |
/// Project a elements of a query to a different shape, with custom mappings. | |
/// </summary> | |
/// <typeparam name="TDest"></typeparam> | |
/// <remarks> | |
/// Each property of <typeparamref name="TDest"/> is expected in <typeparamref name="TSource"/> with | |
/// the same name. The source property is assigned to the destination property, and hence must be of the same | |
/// type or implicitly castable. | |
/// <para/> | |
/// For destination properties that are not represented by name in the source, or that are not of the same type of that | |
/// cannot be case implicitly, use the <param name="customMapper"/> to assign a value to the property. | |
/// </remarks> | |
/// <returns></returns> | |
IQueryable<TDest> To<TDest>(Action<Mapper<TSource, TDest>> customMapper); | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using System.Linq; | |
public static class ProjectionExtensions | |
{ | |
public static IProjectionExpression<TSource> Project<TSource>(this IQueryable<TSource> source) | |
{ | |
return new ProjectionExpression<TSource>(source); | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using System; | |
using System.Collections.Generic; | |
using System.Linq; | |
using System.Linq.Expressions; | |
using System.Reflection; | |
public class ProjectionExpression<TSource> : IProjectionExpression<TSource> | |
{ | |
private readonly IQueryable<TSource> source; | |
private static readonly ParameterExpression parameterExpression = Expression.Parameter(typeof(TSource), "s"); | |
public ProjectionExpression(IQueryable<TSource> source) | |
{ | |
this.source = source; | |
} | |
public IQueryable<TDest> To<TDest>() | |
{ | |
return To<TDest>(mapper => { }); | |
} | |
public IQueryable<TDest> To<TDest>(Action<Mapper<TSource,TDest>> customMapper) | |
{ | |
var customMappings = new List<Mapping>(); | |
var ignoredProperties = new List<PropertyInfo>(); | |
customMapper(new Mapper<TSource,TDest>(customMappings, ignoredProperties)); | |
var expr = buildExpression<TDest>(customMappings, ignoredProperties); | |
return source.Select(expr); | |
} | |
private static Expression<Func<TSource, TDest>> buildExpression<TDest>(IEnumerable<Mapping> customMaps, IEnumerable<PropertyInfo> ignoredProperties) | |
{ | |
var sourceMembers = typeof(TSource).GetProperties(); | |
var destinationMembers = typeof(TDest).GetProperties(); | |
return | |
Expression.Lambda<Func<TSource, TDest>>( | |
Expression.MemberInit( | |
Expression.New(typeof(TDest)), | |
destinationMembers | |
.Where(dest => !ignoredProperties.Contains(dest)) | |
.Select(dest => Expression.Bind(dest, bindCustom(dest, customMaps) ?? bindSimple(dest, sourceMembers))) | |
.ToArray()), | |
parameterExpression); | |
} | |
private static Expression bindCustom(PropertyInfo dest, IEnumerable<Mapping> customMaps) | |
{ | |
return customMaps.Where(map => map.DestPropertyInfo == dest).Select(map => map.Transform.Body).FirstOrDefault(); | |
} | |
private static MemberExpression bindSimple(PropertyInfo dest, IEnumerable<PropertyInfo> sourceMembers) | |
{ | |
return | |
Expression.Property( | |
parameterExpression, | |
sourceMembers.First(pi => pi.Name == dest.Name)); | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using System; | |
using System.Collections.Generic; | |
using System.Linq.Expressions; | |
using System.Reflection; | |
public class Mapper<TSource,TDest> | |
{ | |
private readonly List<Mapping> customMappings; | |
private readonly List<PropertyInfo> ignoreProperties; | |
public Mapper(List<Mapping> customMappings, List<PropertyInfo> ignoreProperties) | |
{ | |
this.customMappings = customMappings; | |
this.ignoreProperties = ignoreProperties; | |
} | |
public Mapper<TSource,TDest> Map<TProperty>(Expression<Func<TDest, TProperty>> property, Expression<Func<TSource, TProperty>> transform) | |
{ | |
customMappings.Add( | |
new Mapping( | |
(PropertyInfo)FluentNHibernate.Utils.Reflection.ReflectionHelper.GetMember(property).MemberInfo, | |
transform)); | |
return this; | |
} | |
public Mapper<TSource, TDest> Ignore<TProperty>(Expression<Func<TDest, TProperty>> property) | |
{ | |
ignoreProperties.Add((PropertyInfo) FluentNHibernate.Utils.Reflection.ReflectionHelper.GetMember(property).MemberInfo); | |
return this; | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using System.Linq.Expressions; | |
using System.Reflection; | |
public class Mapping | |
{ | |
public PropertyInfo DestPropertyInfo { get; private set; } | |
public LambdaExpression Transform { get; private set; } | |
public Mapping(PropertyInfo propertyInfo, LambdaExpression transform) | |
{ | |
DestPropertyInfo = propertyInfo; | |
Transform = transform; | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
This depends on FluentNHibernate's reflection utils to get PropertyInfo from Expressions.