using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
namespace EasyDevCore.Common
{
///
/// https://www.devtrends.co.uk/blog/stop-using-automapper-in-your-data-access-code
///
public static class QueryableExtensions
{
///
/// Projects the specified source.
/// Students.Project().To<StudentAddressDetails>();
///
/// The type of the source.
/// The source.
///
public static ProjectionExpression Project(this IQueryable source)
{
return new ProjectionExpression(source);
}
}
///
///
///
/// The type of the source.
public class ProjectionExpression
{
///
/// The expression cache
///
private static readonly Dictionary ExpressionCache = new Dictionary();
///
/// The source
///
private readonly IQueryable _source;
///
/// Initializes a new instance of the class.
///
/// The source.
public ProjectionExpression(IQueryable source)
{
_source = source;
}
///
/// To this instance.
///
/// The type of the dest.
///
public IQueryable To()
{
var queryExpression = GetCachedExpression() ?? BuildExpression();
return _source.Select(queryExpression);
}
///
/// Gets the cached expression.
///
/// The type of the dest.
///
private static Expression> GetCachedExpression()
{
var key = GetCacheKey();
return ExpressionCache.ContainsKey(key) ? ExpressionCache[key] as Expression> : null;
}
///
/// Builds the expression.
///
/// The type of the dest.
///
private static Expression> BuildExpression()
{
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>(Expression.MemberInit(Expression.New(typeof(TDest)), bindings), parameterExpression);
var key = GetCacheKey();
ExpressionCache.Add(key, expression);
return expression;
}
///
/// Builds the binding.
///
/// The parameter expression.
/// The destination property.
/// The source properties.
///
private static MemberAssignment BuildBinding(Expression parameterExpression, MemberInfo destinationProperty, IEnumerable 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;
}
///
/// Gets the cache key.
///
/// The type of the dest.
///
private static string GetCacheKey()
{
return string.Concat(typeof(TSource).FullName, typeof(TDest).FullName);
}
///
/// Splits the camel case.
///
/// The input.
///
private static string[] SplitCamelCase(string input)
{
return Regex.Replace(input, "([A-Z])", " $1", RegexOptions.Compiled).Trim().Split(' ');
}
}
}