diff --git a/PoweredSoft.DynamicLinq.ConsoleApp/BetterParserProto1.cs b/PoweredSoft.DynamicLinq.ConsoleApp/BetterParserProto1.cs new file mode 100644 index 0000000..84e2a7b --- /dev/null +++ b/PoweredSoft.DynamicLinq.ConsoleApp/BetterParserProto1.cs @@ -0,0 +1,231 @@ +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/BetterProto2.cs b/PoweredSoft.DynamicLinq.ConsoleApp/BetterProto2.cs new file mode 100644 index 0000000..64e9ca1 --- /dev/null +++ b/PoweredSoft.DynamicLinq.ConsoleApp/BetterProto2.cs @@ -0,0 +1,92 @@ +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; + +namespace PoweredSoft.DynamicLinq.ConsoleApp +{ + 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() + { + var ep = new ExpressionParser(typeof(Author), "Posts.Comments.Id"); + ep.Parse(); + } + } + + public class ExpressionParserPiece + { + public ParameterExpression Parameter { get; set; } + public MemberExpression MemberExpression { get; set; } + public ExpressionParserPiece Parent { get; set; } + + public bool IsGenericEnumerable => QueryableHelpers.IsGenericEnumerable(MemberExpression); + public Type EnumerableType => MemberExpression.Type.GenericTypeArguments.FirstOrDefault(); + } + + public class ExpressionParser + { + public ParameterExpression Parameter { get; protected set; } + public string Path { get; set; } + public List Pieces { get; set; } = new List(); + + public ExpressionParser(Type type, string path) : this(Expression.Parameter(type), path) + { + + } + + public ExpressionParser(ParameterExpression parameter, string path) + { + Parameter = parameter; + Path = path; + } + + public void Parse() + { + Pieces.Clear(); + + var param = Parameter; + var pathPieces = Path.Split('.').ToList(); + ExpressionParserPiece parent = null; + + pathPieces.ForEach(pp => + { + var memberExpression = Expression.PropertyOrField(param, pp); + var current = new ExpressionParserPiece + { + Parameter = param, + MemberExpression = memberExpression, + Parent = parent + }; + + Pieces.Add(current); + param = ResolveNextParam(current); + parent = current; + }); + } + + private ParameterExpression ResolveNextParam(ExpressionParserPiece current) + { + var type = current.IsGenericEnumerable ? current.EnumerableType : current.MemberExpression.Type; + var result = Expression.Parameter(type); + return result; + } + } +} diff --git a/PoweredSoft.DynamicLinq.ConsoleApp/PoweredSoft.DynamicLinq.ConsoleApp.csproj b/PoweredSoft.DynamicLinq.ConsoleApp/PoweredSoft.DynamicLinq.ConsoleApp.csproj index f60bcbc..1f995c4 100644 --- a/PoweredSoft.DynamicLinq.ConsoleApp/PoweredSoft.DynamicLinq.ConsoleApp.csproj +++ b/PoweredSoft.DynamicLinq.ConsoleApp/PoweredSoft.DynamicLinq.ConsoleApp.csproj @@ -42,6 +42,8 @@ + + diff --git a/PoweredSoft.DynamicLinq.ConsoleApp/Program.cs b/PoweredSoft.DynamicLinq.ConsoleApp/Program.cs index 7961f92..6b06876 100644 --- a/PoweredSoft.DynamicLinq.ConsoleApp/Program.cs +++ b/PoweredSoft.DynamicLinq.ConsoleApp/Program.cs @@ -14,106 +14,8 @@ namespace PoweredSoft.DynamicLinq.ConsoleApp { static void Main(string[] args) { - 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); + BetterProto2.Run(); } - 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; - } - - - - /* - * (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; - } - */ } } diff --git a/PoweredSoft.DynamicLinq/ExpressionPathPart.cs b/PoweredSoft.DynamicLinq/ExpressionPathPart.cs index e8ce85d..b3396bd 100644 --- a/PoweredSoft.DynamicLinq/ExpressionPathPart.cs +++ b/PoweredSoft.DynamicLinq/ExpressionPathPart.cs @@ -5,130 +5,6 @@ using System.Linq.Expressions; namespace PoweredSoft.DynamicLinq.Helpers { - 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(); - } - } - public class ExpressionPathPart { public Expression ParentExpression { get; set; } diff --git a/PoweredSoft.DynamicLinq/Extensions/EnumerableExtensions.cs b/PoweredSoft.DynamicLinq/Extensions/EnumerableExtensions.cs index dab828a..8e8f37a 100644 --- a/PoweredSoft.DynamicLinq/Extensions/EnumerableExtensions.cs +++ b/PoweredSoft.DynamicLinq/Extensions/EnumerableExtensions.cs @@ -46,5 +46,33 @@ namespace PoweredSoft.DynamicLinq public static IQueryable GroupBy(this IEnumerable list, Type type, Action callback) => list.AsQueryable().GroupBy(type, callback); + + public static List Reversed(this List list) + { + var copy = list.ToList(); + copy.Reverse(); + return copy; + } + + public delegate void ForEachDelegate(T element, int index); + + public static void ForEach(this List list, ForEachDelegate callback) + { + for (var i = 0; i < list.Count; i++) + callback(list[i], i); + } + + public static void ReversedForEach(this List list, Action action) + { + list.Reversed().ForEach(action); + } + + public static void ReversedForEach(this List list, ForEachDelegate callback) + { + var reversed = list.Reversed(); + reversed.ForEach(callback); + } + + } }