Skip to content

Embed URL

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
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
Something went wrong with that request. Please try again.