Found this very cool code today:
http://www.devtrends.co.uk/blog/stop-using-automapper-in-your-data-access-code// This is the kind of projection code that we are replacing
//var students = from s in context.Students
// select new StudentSummary
// {
// FirstName = s.FirstName,
// LastName = s.LastName,
// TutorName = s.Tutor.Name,
// CoursesCount = s.Courses.Count
// };
// The line below performs exactly the same query as the code above.
var students = context.Students.Project().To<StudentSummary>();
Which is all down to the following code:
public static class QueryableExtensions
{
public static ProjectionExpression<TSource> Project<TSource>(this IQueryable<TSource> source)
{
return new ProjectionExpression<TSource>(source);
}
}
public class ProjectionExpression<TSource>
{
private static readonly Dictionary<string, Expression> ExpressionCache = new Dictionary<string, Expression>();
private readonly IQueryable<TSource> _source;
public ProjectionExpression(IQueryable<TSource> source)
{
_source = source;
}
public IQueryable<TDest> To<TDest>()
{
var queryExpression = GetCachedExpression<TDest>() ?? BuildExpression<TDest>();
return _source.Select(queryExpression);
}
private static Expression<Func<TSource, TDest>> GetCachedExpression<TDest>()
{
var key = GetCacheKey<TDest>();
return ExpressionCache.ContainsKey(key) ? ExpressionCache[key] as Expression<Func<TSource, TDest>> : null;
}
private static Expression<Func<TSource, TDest>> BuildExpression<TDest>()
{
var sourceProperties = typeof(TSource).GetProperties();
var destinationProperties = typeof(TDest).GetProperties().Where(dest => dest.CanWrite);
var parameterExpression = Expression.Parameter(typeof(TSource), "src");
var bindings = destinationProperties
.Select(destinationProperty => BuildBinding(parameterExpression, destinationProperty, sourceProperties))
.Where(binding => binding != null);
var expression = Expression.Lambda<Func<TSource, TDest>>(Expression.MemberInit(Expression.New(typeof(TDest)), bindings), parameterExpression);
var key = GetCacheKey<TDest>();
ExpressionCache.Add(key, expression);
return expression;
}
private static MemberAssignment BuildBinding(Expression parameterExpression, MemberInfo destinationProperty, IEnumerable<PropertyInfo> sourceProperties)
{
var sourceProperty = sourceProperties.FirstOrDefault(src => src.Name == destinationProperty.Name);
if (sourceProperty != null)
{
return Expression.Bind(destinationProperty, Expression.Property(parameterExpression, sourceProperty));
}
var propertyNames = SplitCamelCase(destinationProperty.Name);
if (propertyNames.Length == 2)
{
sourceProperty = sourceProperties.FirstOrDefault(src => src.Name == propertyNames[0]);
if (sourceProperty != null)
{
var sourceChildProperty = sourceProperty.PropertyType.GetProperties().FirstOrDefault(src => src.Name == propertyNames[1]);
if (sourceChildProperty != null)
{
return Expression.Bind(destinationProperty, Expression.Property(Expression.Property(parameterExpression, sourceProperty), sourceChildProperty));
}
}
}
return null;
}
private static string GetCacheKey<TDest>()
{
return string.Concat(typeof(TSource).FullName, typeof(TDest).FullName);
}
private static string[] SplitCamelCase(string input)
{
return Regex.Replace(input, "([A-Z])", " $1", RegexOptions.Compiled).Trim().Split(' ');
}
}
No comments:
Post a Comment