Skip to content

Instantly share code, notes, and snippets.

@AndyHitchman
Created February 13, 2011 10:57
Show Gist options
  • Star 3 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save AndyHitchman/824585 to your computer and use it in GitHub Desktop.
Save AndyHitchman/824585 to your computer and use it in GitHub Desktop.
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
Copy link
Author

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