From 0e7aca052ca58fd14afcf49cbdd0a2964963efc9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20Leb=C3=A9e?= Date: Tue, 10 Apr 2018 20:08:27 -0500 Subject: [PATCH] omg finally. --- .../BetterParserProto1.cs | 231 ------------------ .../PoweredSoft.DynamicLinq.ConsoleApp.csproj | 2 - PoweredSoft.DynamicLinq.ConsoleApp/Program.cs | 3 +- PoweredSoft.DynamicLinq.Test/SelectTests.cs | 4 +- .../Helpers/QueryableHelpers.cs | 12 +- .../Parser/ExpressionParser.cs | 4 + .../Resolver/PathExpressionResolver.cs | 127 +++------- 7 files changed, 50 insertions(+), 333 deletions(-) delete mode 100644 PoweredSoft.DynamicLinq.ConsoleApp/BetterParserProto1.cs rename PoweredSoft.DynamicLinq.ConsoleApp/BetterProto2.cs => PoweredSoft.DynamicLinq/Resolver/PathExpressionResolver.cs (53%) diff --git a/PoweredSoft.DynamicLinq.ConsoleApp/BetterParserProto1.cs b/PoweredSoft.DynamicLinq.ConsoleApp/BetterParserProto1.cs deleted file mode 100644 index 84e2a7b..0000000 --- a/PoweredSoft.DynamicLinq.ConsoleApp/BetterParserProto1.cs +++ /dev/null @@ -1,231 +0,0 @@ -using PoweredSoft.DynamicLinq.Dal.Pocos; -using PoweredSoft.DynamicLinq.Helpers; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Linq.Expressions; -using System.Text; -using System.Threading.Tasks; - -#if false - -namespace PoweredSoft.DynamicLinq.ConsoleApp -{ - public class StaticProto1 - { - public static void Run() - { - var q = new List().AsQueryable(); - q.Select(t => new - { - Ids = t.Posts.SelectMany(t2 => t2.Comments.Select(t3 => t3.CommentLikes)) - }); - - var expressionParser = new ExpressionParser(typeof(Author), "Posts.Comments.CommentLikes"); - expressionParser.Parse(); - - var finalExpression = CreateSelectExpressionFromParsed(expressionParser, SelectCollectionHandling.Flatten, SelectNullHandling.New); - } - - public static Expression CreateSelectExpressionFromParsed(ExpressionParser expressionParser, - SelectCollectionHandling selectCollectionHandling = SelectCollectionHandling.LeaveAsIs, - SelectNullHandling nullChecking = SelectNullHandling.LeaveAsIs) - { - Type nullHandlingRightType = null; - Expression nullHandlingNullValue = null; - - if (nullChecking != SelectNullHandling.LeaveAsIs) - { - var withoutNullCheckResult = CreateSelectExpressionFromParsed(expressionParser, SelectCollectionHandling.Flatten, SelectNullHandling.LeaveAsIs); - if (QueryableHelpers.IsGenericEnumerable(withoutNullCheckResult.Type)) - nullHandlingRightType = typeof(List<>).MakeGenericType(withoutNullCheckResult.Type.GenericTypeArguments.First()); - else - nullHandlingRightType = withoutNullCheckResult.Type; - - nullHandlingNullValue = nullChecking == SelectNullHandling.Default - ? Expression.Default(nullHandlingRightType) as Expression - : Expression.New(nullHandlingRightType) as Expression; - } - - // reversed :) - var reversedCopy = expressionParser.Groups.Select(t => t).ToList(); - reversedCopy.Reverse(); - - MethodCallExpression lastSelectExpression = null; - Expression ret = null; - - foreach (var t in reversedCopy) - { - if (true == t.Parent?.LastPiece().IsGenericEnumerable()) - { - if (lastSelectExpression == null) - lastSelectExpression = RegroupSelectExpressions(t, selectCollectionHandling); - else - lastSelectExpression = RegroupSelectExpressions(t, lastSelectExpression, selectCollectionHandling); - } - } - - ret = lastSelectExpression; - return ret; - } - - public static MethodCallExpression RegroupSelectExpressions(ExpressionParameterGroup group, SelectCollectionHandling selectCollectionHandling) - { - MethodCallExpression ret = null; - var lambda = group.CreateLambda(); - var parentExpression = group.Parent.LastExpression(); - var isParentExpressionGenericEnumerable = QueryableHelpers.IsGenericEnumerable(parentExpression); - var parentExpressionGenericEnumerableType = isParentExpressionGenericEnumerable ? parentExpression.Type.GenericTypeArguments.First() : null; - var lastPiece = group.LastPiece(); - - if (selectCollectionHandling == SelectCollectionHandling.LeaveAsIs || !lastPiece.IsGenericEnumerable()) - ret = Expression.Call(typeof(Enumerable), "Select", new Type[] { parentExpressionGenericEnumerableType, lastPiece.Expression.Type }, parentExpression, lambda); - else - ret = Expression.Call(typeof(Enumerable), "SelectMany", new Type[] { parentExpressionGenericEnumerableType, lastPiece.GetGenericEnumerableType() }, parentExpression, lambda); - - return ret; - } - - public static MethodCallExpression RegroupSelectExpressions(ExpressionParameterGroup group, MethodCallExpression innerSelect, SelectCollectionHandling selectCollectionHandling) - { - var parent = group.Parent; - var parentLastPiece = parent.LastPiece(); - - MethodCallExpression ret = null; - var lambda = Expression.Lambda(innerSelect, group.Parameter); - if (selectCollectionHandling == SelectCollectionHandling.LeaveAsIs) - ret = Expression.Call(typeof(Enumerable), "Select", new Type[] { parentLastPiece.GetGenericEnumerableType(), innerSelect.Type }, parentLastPiece.Expression, lambda); - else - ret = Expression.Call(typeof(Enumerable), "SelectMany", new Type[] { parentLastPiece.GetGenericEnumerableType(), innerSelect.Type.GenericTypeArguments.First() }, parentLastPiece.Expression, lambda); - - return ret; - } - } - - - public class ExpressionParameterGroup - { - public ExpressionParameterGroup Parent { get; set; } - public ParameterExpression Parameter { get; set; } - public List Pieces { get; set; } = new List(); - - public ExpressionParameterGroup(ParameterExpression parameter) - { - Parameter = parameter; - } - - public void AddSubPart(ExpressionPiece expressionPart) - { - Pieces.Add(expressionPart); - } - - public Expression LastExpression() => LastPiece().Expression; - - public ExpressionPiece LastPiece() => Pieces.Last(); - - public LambdaExpression CreateLambda() - { - var lastExpression = LastPiece().Expression; - var lambda = Expression.Lambda(lastExpression, Parameter); - return lambda; - } - } - - public class ExpressionPiece - { - public ExpressionParameterGroup Parameter { get; set; } - public ExpressionPiece Parent { get; set; } - public Expression Expression { get; set; } - - public ExpressionPiece(ExpressionParameterGroup parameter, ExpressionPiece parent = null) - { - Parameter = parameter; - Parent = parent; - } - - public void Resolve(string piece) - { - Expression = Expression.PropertyOrField(Parent?.Expression ?? Parameter.Parameter, piece); - } - - public bool IsGenericEnumerable() => QueryableHelpers.IsGenericEnumerable(Expression); - public Type GetGenericEnumerableType() => Expression.Type.GenericTypeArguments.First(); - } - - public class ExpressionParser - { - public ParameterExpression Parameter { get; protected set; } - public string Path { get; protected set; } - public List Groups { get; set; } = new List(); - - public ExpressionParser(Type type, string path) : this(Expression.Parameter(type), path) - { - - } - - public ExpressionParser(ParameterExpression parameter, string path) - { - if (parameter == null) - throw new ArgumentNullException("parameter"); - - if (string.IsNullOrEmpty(path)) - throw new ArgumentNullException("path"); - - Parameter = parameter; - Path = path; - } - - public void Parse() - { - Groups = new List(); - var pieces = Path.Split('.').ToList(); - - // create the current parameter. - var currentGroup = CreateAndAddParameterGroup(Parameter); - ExpressionPiece parentPiece = null; - - int indexOfPiece = -1; - pieces.ForEach(piece => - { - ++indexOfPiece; - bool isLast = indexOfPiece == pieces.Count - 1; - - var expressionPiece = new ExpressionPiece(currentGroup, parentPiece); - expressionPiece.Resolve(piece); - currentGroup.AddSubPart(expressionPiece); - - // rest is only if its not the last piece. - if (isLast) return; - - if (expressionPiece.IsGenericEnumerable()) - { - var param = Expression.Parameter(expressionPiece.GetGenericEnumerableType()); - currentGroup = CreateAndAddParameterGroup(param, currentGroup); - parentPiece = null; - } - else - { - parentPiece = expressionPiece; - } - }); - } - - public ExpressionParameterGroup CreateAndAddParameterGroup(ParameterExpression parameter, ExpressionParameterGroup parent = null) - { - var group = new ExpressionParameterGroup(parameter); - - if (parent != null) - group.Parent = parent; - - Groups.Add(group); - return group; - } - - public ExpressionParameterGroup LastGroup() - { - return this.Groups.Last(); - } - } - - -#endif \ No newline at end of file diff --git a/PoweredSoft.DynamicLinq.ConsoleApp/PoweredSoft.DynamicLinq.ConsoleApp.csproj b/PoweredSoft.DynamicLinq.ConsoleApp/PoweredSoft.DynamicLinq.ConsoleApp.csproj index 1f995c4..f60bcbc 100644 --- a/PoweredSoft.DynamicLinq.ConsoleApp/PoweredSoft.DynamicLinq.ConsoleApp.csproj +++ b/PoweredSoft.DynamicLinq.ConsoleApp/PoweredSoft.DynamicLinq.ConsoleApp.csproj @@ -42,8 +42,6 @@ - - diff --git a/PoweredSoft.DynamicLinq.ConsoleApp/Program.cs b/PoweredSoft.DynamicLinq.ConsoleApp/Program.cs index 6b06876..a933f2d 100644 --- a/PoweredSoft.DynamicLinq.ConsoleApp/Program.cs +++ b/PoweredSoft.DynamicLinq.ConsoleApp/Program.cs @@ -14,7 +14,8 @@ namespace PoweredSoft.DynamicLinq.ConsoleApp { static void Main(string[] args) { - BetterProto2.Run(); + var selectTests = new SelectTests(); + selectTests.SelectNullChecking(); } } diff --git a/PoweredSoft.DynamicLinq.Test/SelectTests.cs b/PoweredSoft.DynamicLinq.Test/SelectTests.cs index 70c7c8f..34b9155 100644 --- a/PoweredSoft.DynamicLinq.Test/SelectTests.cs +++ b/PoweredSoft.DynamicLinq.Test/SelectTests.cs @@ -94,9 +94,11 @@ namespace PoweredSoft.DynamicLinq.Test var querySelect = query.Select(t => { t.NullChecking(true); - t.PathToList("Posts.Comments", selectCollectionHandling: SelectCollectionHandling.Flatten); + t.PathToList("Posts.Comments.CommentLikes", selectCollectionHandling: SelectCollectionHandling.Flatten); }); + var abc = querySelect.ToObjectList(); + var list = querySelect.ToDynamicClassList(); } } diff --git a/PoweredSoft.DynamicLinq/Helpers/QueryableHelpers.cs b/PoweredSoft.DynamicLinq/Helpers/QueryableHelpers.cs index 78ff18d..feb985d 100644 --- a/PoweredSoft.DynamicLinq/Helpers/QueryableHelpers.cs +++ b/PoweredSoft.DynamicLinq/Helpers/QueryableHelpers.cs @@ -1,4 +1,6 @@ using PoweredSoft.DynamicLinq.DynamicType; +using PoweredSoft.DynamicLinq.Parser; +using PoweredSoft.DynamicLinq.Resolver; using System; using System.Collections; using System.Collections.Generic; @@ -201,7 +203,8 @@ namespace PoweredSoft.DynamicLinq.Helpers return CreateSelectExpressionForGrouping(query, parameter, selectType, path, selectCollectionHandling, nullChecking); } - private static Expression CreateSelectExpressionRegular(IQueryable query, ParameterExpression parameter, SelectTypes selectType, string path, SelectCollectionHandling selectCollectionHandling, bool nullChecking) + private static Expression CreateSelectExpressionRegular(IQueryable query, ParameterExpression parameter, SelectTypes selectType, string path + , SelectCollectionHandling selectCollectionHandling, bool nullChecking) { if (selectType == SelectTypes.Path) { @@ -209,7 +212,12 @@ namespace PoweredSoft.DynamicLinq.Helpers } else if (selectType == SelectTypes.PathToList) { - var expr = ResolvePathForExpression(parameter, path); + var parser = new ExpressionParser(parameter, path); + var resolver = new PathExpressionResolver(parser); + resolver.NullChecking = nullChecking; + resolver.CollectionHandling = selectCollectionHandling; + resolver.Resolve(); + var expr = (resolver.Result as LambdaExpression).Body; var notGroupedType = expr.Type.GenericTypeArguments.FirstOrDefault(); if (notGroupedType == null) throw new Exception($"Path must be a Enumerable but its a {expr.Type}"); diff --git a/PoweredSoft.DynamicLinq/Parser/ExpressionParser.cs b/PoweredSoft.DynamicLinq/Parser/ExpressionParser.cs index 013d03e..2e2a8e9 100644 --- a/PoweredSoft.DynamicLinq/Parser/ExpressionParser.cs +++ b/PoweredSoft.DynamicLinq/Parser/ExpressionParser.cs @@ -83,6 +83,10 @@ namespace PoweredSoft.DynamicLinq.Parser group = CreateAndAddGroup(groups, Expression.Parameter(piece.EnumerableType), group); }); + // if the last piece is empty. + if (group.Pieces.Count == 0) + groups.Remove(group); + return groups; } } diff --git a/PoweredSoft.DynamicLinq.ConsoleApp/BetterProto2.cs b/PoweredSoft.DynamicLinq/Resolver/PathExpressionResolver.cs similarity index 53% rename from PoweredSoft.DynamicLinq.ConsoleApp/BetterProto2.cs rename to PoweredSoft.DynamicLinq/Resolver/PathExpressionResolver.cs index 0437b65..7d8d9d7 100644 --- a/PoweredSoft.DynamicLinq.ConsoleApp/BetterProto2.cs +++ b/PoweredSoft.DynamicLinq/Resolver/PathExpressionResolver.cs @@ -1,83 +1,16 @@ -using PoweredSoft.DynamicLinq.Dal.Pocos; -using PoweredSoft.DynamicLinq.Helpers; +using PoweredSoft.DynamicLinq.Helpers; using PoweredSoft.DynamicLinq.Parser; using System; using System.Collections.Generic; using System.Linq; using System.Linq.Expressions; +using System.Text; -namespace PoweredSoft.DynamicLinq.ConsoleApp +namespace PoweredSoft.DynamicLinq.Resolver { - public class BetterProto2 - { - /* - * (parent, innerExpression, innerExpressionLambda) => - { - var listGenericArgumentType = parent.Type.GetGenericArguments().First(); - Expression ret = null; - if (selectCollectionHandling == SelectCollectionHandling.LeaveAsIs || !QueryableHelpers.IsGenericEnumerable(innerExpression)) - ret = Expression.Call(typeof(Enumerable), "Select", new Type[] { listGenericArgumentType, innerExpression.Type }, parent, innerExpressionLambda); - else - ret = Expression.Call(typeof(Enumerable), "SelectMany", new Type[] { listGenericArgumentType, innerExpression.Type.GenericTypeArguments.First() }, parent, innerExpressionLambda); - - return ret; - } - */ - public static void Run() - { - //Case1(); - Case2(); - } - - public static void Case1() - { - // the expression parser. - var ep = new ExpressionParser(typeof(Author), "Posts.Comments.Id"); - - // the builder. - var per = new PathExpressionResolver(ep); - per.Resolve(); - - // the result expression. - var result = per.Result; - } - - public static void Case2() - { - // the expression parser. - var ep = new ExpressionParser(typeof(Author), "Posts.Author.Website.Url"); - - new List().AsQueryable().Select(t => new - { - A = t.Posts.Select(t2 => t2.Author.Posts.Select(t3 => t3.Author.Website.Url)) , - B = t.Posts.Where(t2 => t2.Author != null).Select(t2 => t2.Author.Posts.Where(t3 => t3.Author != null && t3.Author.Website != null).Select(t3 => t3.Author.Website.Url)) - }); - - new List().AsQueryable().Select(t => new - { - FirstNames = t.Author == null ? new List() : (t.Author.Posts == null ? new List() : t.Author.Posts.Where(t2 => t2.Author != null).Select(t2 => t2.Author.FirstName)), - PostsAuthors = t.Author == null ? new List() : (t.Author.Posts == null ? new List() : t.Author.Posts.Where(t2 => t2.Author != null).Select(t2 => t2.Author)), - Comments = t.Comments == null ? new List() : t.Comments, - CommentLikes = (t.Comments == null ? new List() : t.Comments.Where(t2 => t2.CommentLikes != null).SelectMany(t2 => t2.CommentLikes)), - CommentLikeIds = (t.Comments == null ? new List() : t.Comments.Where(t2 => t2.CommentLikes != null).SelectMany(t2 => t2.CommentLikes.Select(t3 => t3.Id))), - CommentsLikes = (t.Comments == null ? new List>() : t.Comments.Where(t2 => t2.CommentLikes != null).Select(t2 => t2.CommentLikes)) - }); - - // the builder. - var per = new PathExpressionResolver(ep); - per.NullHandling = SelectNullHandling.Handle; - per.Resolve(); - - // the result expression. - var result = per.Result; - } - } - - - public class PathExpressionResolver { - public SelectNullHandling NullHandling { get; set; } = SelectNullHandling.LeaveAsIs; + public bool NullChecking { get; set; } = false; public SelectCollectionHandling CollectionHandling { get; set; } = SelectCollectionHandling.LeaveAsIs; public ExpressionParser Parser { get; protected set; } @@ -89,7 +22,7 @@ namespace PoweredSoft.DynamicLinq.ConsoleApp Parser = parser; } - protected Expression CompileGroup(ExpressionParserPieceGroup group, SelectNullHandling NullHandling) + protected Expression CompileGroup(ExpressionParserPieceGroup group, bool nullChecking) { var expr = group.Parameter as Expression; group.Pieces.ForEach(piece => @@ -109,14 +42,14 @@ namespace PoweredSoft.DynamicLinq.ConsoleApp // group the piece by common parameters var groups = Parser.GroupBySharedParameters(); - NullType = groups.ResolveNullHandlingType(); - Expression currentExpression = null; - foreach(var group in groups.Reversed()) - { + foreach (var group in groups.Reversed()) + { + var isLastGroup = groups.IndexOf(group) == groups.Count - 1; + if (currentExpression == null) { - var groupExpression = CompileGroup(group, NullHandling); + var groupExpression = CompileGroup(group, NullChecking); var groupExpressionLambda = Expression.Lambda(groupExpression, group.Parameter); if (group.Parent == null) @@ -126,16 +59,16 @@ namespace PoweredSoft.DynamicLinq.ConsoleApp } var parent = group.Parent; - var parentExpression = CompileGroup(parent, NullHandling); + var parentExpression = CompileGroup(parent, NullChecking); // check null with where. - if (NullHandling != SelectNullHandling.LeaveAsIs) - parentExpression = CheckNullOnEnumerableParent(group, parent, parentExpression); + if (NullChecking != false) + parentExpression = CheckNullOnEnumerableParent(group, parent, parentExpression, isLastGroup); // the select expression. var selectType = parent.GroupEnumerableType(); - var selectExpression = Expression.Call(typeof(Enumerable), "Select", - new Type[] { selectType, groupExpression.Type }, + var selectExpression = Expression.Call(typeof(Enumerable), "Select", + new Type[] { selectType, groupExpression.Type }, parentExpression, groupExpressionLambda); currentExpression = selectExpression; } @@ -143,7 +76,7 @@ namespace PoweredSoft.DynamicLinq.ConsoleApp { if (group.Parent == null) { - if (NullHandling != SelectNullHandling.LeaveAsIs) + if (NullChecking != false) currentExpression = CheckNullOnFirstGroup(group, currentExpression); currentExpression = Expression.Lambda(currentExpression, group.Parameter); @@ -151,12 +84,12 @@ namespace PoweredSoft.DynamicLinq.ConsoleApp } var parent = group.Parent; - var parentExpression = CompileGroup(parent, NullHandling); + var parentExpression = CompileGroup(parent, NullChecking); var selectType = parent.GroupEnumerableType(); - if (NullHandling != SelectNullHandling.LeaveAsIs) - parentExpression = CheckNullOnEnumerableParent(group, parent, parentExpression); + if (NullChecking != false) + parentExpression = CheckNullOnEnumerableParent(group, parent, parentExpression, isLastGroup); var currentExpressionLambda = Expression.Lambda(currentExpression, group.Parameter); currentExpression = Expression.Call(typeof(Enumerable), "Select", @@ -172,15 +105,16 @@ namespace PoweredSoft.DynamicLinq.ConsoleApp { var path = string.Join(".", group.Pieces.Select(t => t.Name)); var whereExpression = QueryableHelpers.CreateConditionExpression(group.Parameter.Type, path, - ConditionOperators.NotEqual, null, QueryConvertStrategy.ConvertConstantToComparedPropertyOrField, + ConditionOperators.Equal, null, QueryConvertStrategy.ConvertConstantToComparedPropertyOrField, parameter: group.Parameter, nullChecking: true); var whereBodyExpression = (whereExpression as LambdaExpression).Body; + var nullType = currentExpression.Type; Expression ifTrueExpression = null; - if (QueryableHelpers.IsGenericEnumerable(NullType)) + if (QueryableHelpers.IsGenericEnumerable(nullType)) { - var listType = typeof(List<>).MakeGenericType(NullType.GenericTypeArguments.First()); + var listType = typeof(List<>).MakeGenericType(nullType.GenericTypeArguments.First()); ifTrueExpression = Expression.New(listType); } @@ -192,11 +126,16 @@ namespace PoweredSoft.DynamicLinq.ConsoleApp return Expression.Condition(whereBodyExpression, ifTrueExpression, currentExpression, currentExpression.Type); } - private static Expression CheckNullOnEnumerableParent(ExpressionParserPieceGroup group, ExpressionParserPieceGroup parent, Expression parentExpression) + private static Expression CheckNullOnEnumerableParent(ExpressionParserPieceGroup group, ExpressionParserPieceGroup parent, Expression parentExpression, bool isLastGroup) { - if (group.Pieces.Count > 1) + string path = null; + if (isLastGroup) + path = string.Join(".", group.Pieces.Take(group.Pieces.Count - 1).Select(t => t.Name)); + else + path = string.Join(".", group.Pieces.Select(t => t.Name)); + + if (!string.IsNullOrEmpty(path)) { - var path = string.Join(".", group.Pieces.Take(group.Pieces.Count - 1).Select(T => T.Name)); var whereExpression = QueryableHelpers.CreateConditionExpression(group.Parameter.Type, path, ConditionOperators.NotEqual, null, QueryConvertStrategy.ConvertConstantToComparedPropertyOrField, parameter: group.Parameter, nullChecking: true); @@ -206,10 +145,6 @@ namespace PoweredSoft.DynamicLinq.ConsoleApp new Type[] { parent.GroupEnumerableType() }, parentExpression, whereExpression); } - else - { - - } return parentExpression; }