public
Last active

  • Download Gist
gistfile1.cs
C#
1 2 3 4 5 6 7 8 9 10 11
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();
gistfile2.cs
C#
1 2 3 4 5 6 7 8 9 10 11 12 13 14
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; }
}
gistfile3.cs
C#
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32
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);
}
gistfile4.cs
C#
1 2 3 4 5 6 7 8 9
using System.Linq;
 
public static class ProjectionExtensions
{
public static IProjectionExpression<TSource> Project<TSource>(this IQueryable<TSource> source)
{
return new ProjectionExpression<TSource>(source);
}
}
gistfile5.cs
C#
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60
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));
}
}
gistfile6.cs
C#
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33
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;
}
}
gistfile7.cs
C#
1 2 3 4 5 6 7 8 9 10 11 12 13 14
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;
}
}

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

Please sign in to comment on this gist.

Something went wrong with that request. Please try again.