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(' '); } } }