Create a gist now

Instantly share code, notes, and snippets.

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();
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; }
}
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);
}
using System.Linq;
public static class ProjectionExtensions
{
public static IProjectionExpression<TSource> Project<TSource>(this IQueryable<TSource> source)
{
return new ProjectionExpression<TSource>(source);
}
}
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));
}
}
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;
}
}
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;
}
}
@AndyHitchman

This depends on FluentNHibernate's reflection utils to get PropertyInfo from Expressions.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment