diff --git a/PoweredSoft.DynamicLinq.ConsoleApp/Program.cs b/PoweredSoft.DynamicLinq.ConsoleApp/Program.cs index 5f3beaa..017d6df 100644 --- a/PoweredSoft.DynamicLinq.ConsoleApp/Program.cs +++ b/PoweredSoft.DynamicLinq.ConsoleApp/Program.cs @@ -16,10 +16,58 @@ namespace PoweredSoft.DynamicLinq.ConsoleApp { var type = typeof(Dal.Pocos.Author); var param = Expression.Parameter(type); - var path = "Posts.Comments.Id"; + var parts = ExpressionPathPart.Split(param, "Posts.Comments.Id"); - var parts = ExpressionPathPart.Break(param, "Posts.Comments.Id"); + // add the last part. + var parent = parts.Last(); + var toListType = parent.GetToListType(); + parts.Add(new ExpressionPathPart + { + ParentExpression = parent.PartExpression, + PartExpression = Expression.Call(typeof(Enumerable), "ToList", new[] { toListType }, parent.ParentExpression) + }); + var result = CreateSelectExpressionFromParts(parts, SelectCollectionHandling.Flatten, SelectNullHandling.New); + } + + public static Expression CreateSelectExpressionFromParts(List parts, + SelectCollectionHandling selectCollectionHandling = SelectCollectionHandling.LeaveAsIs, + SelectNullHandling nullChecking = SelectNullHandling.LeaveAsIs) + { + Type nullHandlingRightType = null; + Expression nullHandlingNullValue = null; + + if (nullChecking != SelectNullHandling.LeaveAsIs) + { + nullHandlingRightType = parts.Last().PartExpression.Type; + nullHandlingNullValue = nullChecking == SelectNullHandling.Default + ? Expression.Default(nullHandlingRightType) as Expression + : Expression.New(nullHandlingRightType) as Expression; + } + + // reversed :) + var reversedCopy = parts.Select(t => t).ToList(); + reversedCopy.Reverse(); + + Expression ret = null; + reversedCopy.ForEach(t => + { + if (t.IsParentGenericEnumerable()) + { + var lambda = t.GetLambdaExpression(); + var parentGenericType = t.ParentGenericEnumerableType(); + if (selectCollectionHandling == SelectCollectionHandling.LeaveAsIs || !t.IsGenericEnumerable()) + ret = Expression.Call(typeof(Enumerable), "Select", new Type[] { parentGenericType, t.PartExpression.Type }, t.ParentExpression, lambda); + else + ret = Expression.Call(typeof(Enumerable), "SelectMany", new Type[] { parentGenericType, t.GenericEnumerableType() }, t.ParentExpression, lambda); + } + else + { + ret = t.PartExpression; + } + }); + + return ret; } diff --git a/PoweredSoft.DynamicLinq/Constants.cs b/PoweredSoft.DynamicLinq/Constants.cs index e023863..6187aa0 100644 --- a/PoweredSoft.DynamicLinq/Constants.cs +++ b/PoweredSoft.DynamicLinq/Constants.cs @@ -59,6 +59,13 @@ namespace PoweredSoft.DynamicLinq Flatten } + public enum SelectNullHandling + { + LeaveAsIs, + Default, + New + } + internal static class Constants { internal static readonly MethodInfo GroupByMethod = typeof(Queryable).GetMethods().First(t => t.Name == "GroupBy"); diff --git a/PoweredSoft.DynamicLinq/ExpressionPathPart.cs b/PoweredSoft.DynamicLinq/ExpressionPathPart.cs index 6283421..c19c6a9 100644 --- a/PoweredSoft.DynamicLinq/ExpressionPathPart.cs +++ b/PoweredSoft.DynamicLinq/ExpressionPathPart.cs @@ -8,13 +8,18 @@ namespace PoweredSoft.DynamicLinq.Helpers public class ExpressionPathPart { public Expression ParentExpression { get; set; } - public Expression Expression { get; set; } - public bool IsNullable() => TypeHelpers.IsNullable(Expression.Type); - public bool IsParentParamaterExpression() => ParentExpression is ParameterExpression; - public bool IsGenericEnumerable() => QueryableHelpers.IsGenericEnumerable(Expression); - public Type GenericEnumerableType() => Expression.Type.GenericTypeArguments.First(); + public ParameterExpression ParameterExpression { get; set; } + public Expression PartExpression { get; set; } - public static List Break(ParameterExpression param, string path) + public bool IsNullable() => TypeHelpers.IsNullable(PartExpression.Type); + + public bool IsGenericEnumerable() => QueryableHelpers.IsGenericEnumerable(PartExpression); + public Type GenericEnumerableType() => PartExpression.Type.GenericTypeArguments.First(); + public bool IsCallingMethod() => PartExpression is MethodCallExpression; + public Type GetToListType() => IsGenericEnumerable() ? GenericEnumerableType() : PartExpression.Type; + public bool IsParentGenericEnumerable() => QueryableHelpers.IsGenericEnumerable(ParentExpression); + + public static List Split(ParameterExpression param, string path) { var ret = new List(); @@ -23,14 +28,31 @@ namespace PoweredSoft.DynamicLinq.Helpers parts.ForEach(part => { var p = new ExpressionPathPart(); - p.Expression = Expression.PropertyOrField(parent, part); + p.PartExpression = Expression.PropertyOrField(parent, part); p.ParentExpression = parent; + p.ParameterExpression = param; ret.Add(p); - parent = p.IsGenericEnumerable() ? Expression.Parameter(p.GenericEnumerableType()) : p.Expression; + if (p.IsGenericEnumerable()) + { + param = Expression.Parameter(p.GenericEnumerableType()); + parent = param; + } + else + { + parent = p.PartExpression; + } }); return ret; } + + public LambdaExpression GetLambdaExpression() + { + var lambda = Expression.Lambda(PartExpression, ParameterExpression); + return lambda; + } + + public Type ParentGenericEnumerableType() => ParentExpression.Type.GenericTypeArguments.First(); } } diff --git a/PoweredSoft.DynamicLinq/Helpers/QueryableHelpers.cs b/PoweredSoft.DynamicLinq/Helpers/QueryableHelpers.cs index 64cdbb1..ea35363 100644 --- a/PoweredSoft.DynamicLinq/Helpers/QueryableHelpers.cs +++ b/PoweredSoft.DynamicLinq/Helpers/QueryableHelpers.cs @@ -296,11 +296,11 @@ namespace PoweredSoft.DynamicLinq.Helpers /// public static Expression ResolvePathForExpression(ParameterExpression param, string path) { - var parts = ExpressionPathPart.Break(param, path); + var parts = ExpressionPathPart.Split(param, path); if (parts.Any(t => t.IsGenericEnumerable())) throw new Exception("this method does not support collection handling"); - return parts.Last().Expression; + return parts.Last().PartExpression; } public static ConstantExpression GetConstantSameAsLeftOperator(Expression member, object value)