123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157 |
- 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
- {
- /// <summary>
- /// https://www.devtrends.co.uk/blog/stop-using-automapper-in-your-data-access-code
- /// </summary>
- public static class QueryableExtensions
- {
- /// <summary>
- /// Projects the specified source.
- /// Students.Project().To<StudentAddressDetails>();
- /// </summary>
- /// <typeparam name="TSource">The type of the source.</typeparam>
- /// <param name="source">The source.</param>
- /// <returns></returns>
- public static ProjectionExpression<TSource> Project<TSource>(this IQueryable<TSource> source)
- {
- return new ProjectionExpression<TSource>(source);
- }
- }
- /// <summary>
- ///
- /// </summary>
- /// <typeparam name="TSource">The type of the source.</typeparam>
- public class ProjectionExpression<TSource>
- {
- /// <summary>
- /// The expression cache
- /// </summary>
- private static readonly Dictionary<string, Expression> ExpressionCache = new Dictionary<string, Expression>();
- /// <summary>
- /// The source
- /// </summary>
- private readonly IQueryable<TSource> _source;
- /// <summary>
- /// Initializes a new instance of the <see cref="ProjectionExpression{TSource}"/> class.
- /// </summary>
- /// <param name="source">The source.</param>
- public ProjectionExpression(IQueryable<TSource> source)
- {
- _source = source;
- }
- /// <summary>
- /// To this instance.
- /// </summary>
- /// <typeparam name="TDest">The type of the dest.</typeparam>
- /// <returns></returns>
- public IQueryable<TDest> To<TDest>()
- {
- var queryExpression = GetCachedExpression<TDest>() ?? BuildExpression<TDest>();
- return _source.Select(queryExpression);
- }
- /// <summary>
- /// Gets the cached expression.
- /// </summary>
- /// <typeparam name="TDest">The type of the dest.</typeparam>
- /// <returns></returns>
- private static Expression<Func<TSource, TDest>> GetCachedExpression<TDest>()
- {
- var key = GetCacheKey<TDest>();
- return ExpressionCache.ContainsKey(key) ? ExpressionCache[key] as Expression<Func<TSource, TDest>> : null;
- }
- /// <summary>
- /// Builds the expression.
- /// </summary>
- /// <typeparam name="TDest">The type of the dest.</typeparam>
- /// <returns></returns>
- 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;
- }
- /// <summary>
- /// Builds the binding.
- /// </summary>
- /// <param name="parameterExpression">The parameter expression.</param>
- /// <param name="destinationProperty">The destination property.</param>
- /// <param name="sourceProperties">The source properties.</param>
- /// <returns></returns>
- 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;
- }
- /// <summary>
- /// Gets the cache key.
- /// </summary>
- /// <typeparam name="TDest">The type of the dest.</typeparam>
- /// <returns></returns>
- private static string GetCacheKey<TDest>()
- {
- return string.Concat(typeof(TSource).FullName, typeof(TDest).FullName);
- }
- /// <summary>
- /// Splits the camel case.
- /// </summary>
- /// <param name="input">The input.</param>
- /// <returns></returns>
- private static string[] SplitCamelCase(string input)
- {
- return Regex.Replace(input, "([A-Z])", " $1", RegexOptions.Compiled).Trim().Split(' ');
- }
- }
- }
|