From 8f35c2a377adc779dc76f9295746979b4dd7eed9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20Leb=C3=A9e?= Date: Wed, 21 Mar 2018 22:15:45 -0500 Subject: [PATCH] select works just need to add null checks. --- PoweredSoft.DynamicLinq.Test/SelectTests.cs | 36 ++++++++---- PoweredSoft.DynamicLinq/Constants.cs | 6 ++ .../Fluent/Select/SelectBuilder.cs | 8 ++- .../Helpers/QueryableHelpers.cs | 57 +++++++++++++------ 4 files changed, 77 insertions(+), 30 deletions(-) diff --git a/PoweredSoft.DynamicLinq.Test/SelectTests.cs b/PoweredSoft.DynamicLinq.Test/SelectTests.cs index 64af1a4..5e0ebc0 100644 --- a/PoweredSoft.DynamicLinq.Test/SelectTests.cs +++ b/PoweredSoft.DynamicLinq.Test/SelectTests.cs @@ -14,26 +14,34 @@ namespace PoweredSoft.DynamicLinq.Test [TestMethod] public void TestSelect() { - var regularSyntax = TestData.Authors + var regularSyntaxA = TestData.Authors .AsQueryable() .Select(t => new { Id = t.Id, AuthorFirstName = t.FirstName, AuthorLastName = t.LastName, - Posts = t.Posts.ToList() - }).ToList(); + Posts = t.Posts.ToList(), + Comments = t.Posts.Select(t2 => t2.Comments).ToList(), + Comments2 = t.Posts.SelectMany(t2 => t2.Comments).ToList() + }); + + //var regularSyntax = regularSyntaxA.ToList(); var dynamicSyntax = TestData.Authors .AsQueryable() .Select(t => - { - t.Path("Id"); - t.Path("FirstName", "AuthorFirstName"); - t.Path("LastName", "AuthorLastName"); - t.PathToList("Posts"); - }).ToDynamicClassList(); + { + t.Path("Id"); + t.Path("FirstName", "AuthorFirstName"); + t.Path("LastName", "AuthorLastName"); + t.PathToList("Posts"); + t.PathToList("Posts.Comments"); + t.PathToList("Posts.Comments", "Comments2", SelectCollectionHandling.SelectMany); + }) + .ToDynamicClassList(); + /* Assert.AreEqual(regularSyntax.Count, dynamicSyntax.Count); for(var i = 0; i < regularSyntax.Count; i++) { @@ -41,7 +49,15 @@ namespace PoweredSoft.DynamicLinq.Test Assert.AreEqual(regularSyntax[i].AuthorFirstName, dynamicSyntax[i].GetDynamicPropertyValue("AuthorFirstName")); Assert.AreEqual(regularSyntax[i].AuthorLastName, dynamicSyntax[i].GetDynamicPropertyValue("AuthorLastName")); Helpers.QueryableAssert.AreEqual(regularSyntax[i].Posts.AsQueryable(), dynamicSyntax[i].GetDynamicPropertyValue>("Posts").AsQueryable()); - } + //Helpers.QueryableAssert.AreEqual(regularSyntax[i].Comments.AsQueryable(), dynamicSyntax[i].GetDynamicPropertyValue>("Comments").AsQueryable()); + }*/ + } + + [TestMethod] + public void SelectNullChecking() + { + // TODO: null checking in select operations :D + throw new Exception("TODO"); } } } diff --git a/PoweredSoft.DynamicLinq/Constants.cs b/PoweredSoft.DynamicLinq/Constants.cs index 4f6b27f..2661c8e 100644 --- a/PoweredSoft.DynamicLinq/Constants.cs +++ b/PoweredSoft.DynamicLinq/Constants.cs @@ -53,6 +53,12 @@ namespace PoweredSoft.DynamicLinq Path } + public enum SelectCollectionHandling + { + Select, + SelectMany + } + internal static class Constants { internal static readonly MethodInfo GroupByMethod = typeof(Queryable).GetMethods().First(t => t.Name == "GroupBy"); diff --git a/PoweredSoft.DynamicLinq/Fluent/Select/SelectBuilder.cs b/PoweredSoft.DynamicLinq/Fluent/Select/SelectBuilder.cs index 420c7ae..f1d7654 100644 --- a/PoweredSoft.DynamicLinq/Fluent/Select/SelectBuilder.cs +++ b/PoweredSoft.DynamicLinq/Fluent/Select/SelectBuilder.cs @@ -11,6 +11,7 @@ namespace PoweredSoft.DynamicLinq.Fluent public string Path { get; set; } public string PropertyName { get; set; } public SelectTypes SelectType { get; set; } + public SelectCollectionHandling SelectCollectionHandling { get; set; } } public class SelectBuilder : IQueryBuilder @@ -133,7 +134,7 @@ namespace PoweredSoft.DynamicLinq.Fluent return this; } - public SelectBuilder PathToList(string path, string propertyName = null) + public SelectBuilder PathToList(string path, string propertyName = null, SelectCollectionHandling selectCollectionHandling = SelectCollectionHandling.Select) { if (propertyName == null) propertyName = path.Split('.').LastOrDefault(); @@ -144,7 +145,8 @@ namespace PoweredSoft.DynamicLinq.Fluent { Path = path, PropertyName = propertyName, - SelectType = SelectTypes.PathToList + SelectType = SelectTypes.PathToList, + SelectCollectionHandling = selectCollectionHandling }); return this; @@ -155,7 +157,7 @@ namespace PoweredSoft.DynamicLinq.Fluent if (Empty) throw new Exception("No select specified, please specify at least one select path"); - var partsTuple = Parts.Select(t => (selectType: t.SelectType, propertyName: t.PropertyName, path: t.Path)).ToList(); + var partsTuple = Parts.Select(t => (selectType: t.SelectType, propertyName: t.PropertyName, path: t.Path, selectCollectionHandling: t.SelectCollectionHandling)).ToList(); return QueryableHelpers.Select(Query, partsTuple, DestinationType); } } diff --git a/PoweredSoft.DynamicLinq/Helpers/QueryableHelpers.cs b/PoweredSoft.DynamicLinq/Helpers/QueryableHelpers.cs index fcd1a6d..5389c30 100644 --- a/PoweredSoft.DynamicLinq/Helpers/QueryableHelpers.cs +++ b/PoweredSoft.DynamicLinq/Helpers/QueryableHelpers.cs @@ -118,7 +118,7 @@ namespace PoweredSoft.DynamicLinq.Helpers return result; } - public static IQueryable Select(IQueryable query, List<(SelectTypes selectType, string propertyName, string path)> parts, Type destinationType = null) + public static IQueryable Select(IQueryable query, List<(SelectTypes selectType, string propertyName, string path, SelectCollectionHandling selectCollectionHandling)> parts, Type destinationType = null) { // create parameter. var queryType = query.ElementType; @@ -129,7 +129,7 @@ namespace PoweredSoft.DynamicLinq.Helpers var partExpressions = new List<(Expression expression, string propertyName)>(); parts.ForEach(part => { - var partBodyExpression = CreateSelectExpression(query, parameter, part.selectType, part.path); + var partBodyExpression = CreateSelectExpression(query, parameter, part.selectType, part.path, part.selectCollectionHandling); fields.Add((partBodyExpression.Type, part.propertyName)); partExpressions.Add((partBodyExpression, part.propertyName)); }); @@ -146,11 +146,11 @@ namespace PoweredSoft.DynamicLinq.Helpers return result; } - private static Expression CreateSelectExpressionForGrouping(IQueryable query, ParameterExpression parameter, SelectTypes selectType, string path) + private static Expression CreateSelectExpressionForGrouping(IQueryable query, ParameterExpression parameter, SelectTypes selectType, string path, SelectCollectionHandling selectCollectionHandling) { if (selectType == SelectTypes.Key) { - return ResolvePathForExpression(parameter, path); + return ResolvePathForExpression(parameter, path, selectCollectionHandling); } else if (selectType == SelectTypes.Count) { @@ -193,23 +193,23 @@ namespace PoweredSoft.DynamicLinq.Helpers throw new NotSupportedException($"unkown select type {selectType}"); } - private static Expression CreateSelectExpression(IQueryable query, ParameterExpression parameter, SelectTypes selectType, string path) + private static Expression CreateSelectExpression(IQueryable query, ParameterExpression parameter, SelectTypes selectType, string path, SelectCollectionHandling selectCollectionHandling) { if (!IsGrouping(query)) - return CreateSelectExpressionRegular(query, parameter, selectType, path); + return CreateSelectExpressionRegular(query, parameter, selectType, path, selectCollectionHandling); - return CreateSelectExpressionForGrouping(query, parameter, selectType, path); + return CreateSelectExpressionForGrouping(query, parameter, selectType, path, selectCollectionHandling); } - private static Expression CreateSelectExpressionRegular(IQueryable query, ParameterExpression parameter, SelectTypes selectType, string path) + private static Expression CreateSelectExpressionRegular(IQueryable query, ParameterExpression parameter, SelectTypes selectType, string path, SelectCollectionHandling selectCollectionHandling) { if (selectType == SelectTypes.Path) { - return ResolvePathForExpression(parameter, path); + return ResolvePathForExpression(parameter, path, selectCollectionHandling); } else if (selectType == SelectTypes.PathToList) { - var expr = ResolvePathForExpression(parameter, path); + var expr = ResolvePathForExpression(parameter, path, selectCollectionHandling); var notGroupedType = expr.Type.GenericTypeArguments.FirstOrDefault(); if (notGroupedType == null) throw new Exception($"Path must be a Enumerable but its a {expr.Type}"); @@ -241,20 +241,43 @@ namespace PoweredSoft.DynamicLinq.Helpers return result; } + internal static Expression InternalResolvePathExpression(ParameterExpression param, List parts, SelectCollectionHandling selectCollectionHandling) + { + var isLast = parts.Count == 1; + var currentPart = parts.First(); + var memberExpression = Expression.PropertyOrField(param, currentPart); + + + if (isLast) + return memberExpression; + + if (!IsEnumerable(memberExpression)) + return InternalResolvePathExpression(param, parts.Skip(1).ToList(), selectCollectionHandling); + + // enumerable. + var listGenericArgumentType = memberExpression.Type.GetGenericArguments().First(); + + // sub param. + var innerParam = Expression.Parameter(listGenericArgumentType); + var innerExpression = InternalResolvePathExpression(innerParam, parts.Skip(1).ToList(), selectCollectionHandling); + var lambda = Expression.Lambda(innerExpression, innerParam); + + if (selectCollectionHandling == SelectCollectionHandling.Select) + return Expression.Call(typeof(Enumerable), "Select", new Type[] { listGenericArgumentType, innerExpression.Type }, memberExpression, lambda); + + return Expression.Call(typeof(Enumerable), "SelectMany", new Type[] { listGenericArgumentType, innerExpression.Type.GenericTypeArguments.First() }, memberExpression, lambda); + } + /// /// Returns the right expression for a path supplied. /// /// Expression.Parameter(typeOfClassOrInterface) /// the path you wish to resolve example Contact.Profile.FirstName /// - public static Expression ResolvePathForExpression(ParameterExpression param, string path) + public static Expression ResolvePathForExpression(ParameterExpression param, string path, SelectCollectionHandling selectCollectionHandling = SelectCollectionHandling.Select) { - Expression body = param; - foreach (var member in path.Split('.')) - { - body = Expression.PropertyOrField(body, member); - } - return body; + var parts = path.Split('.').ToList(); + return InternalResolvePathExpression(param, parts, selectCollectionHandling); } public static ConstantExpression GetConstantSameAsLeftOperator(Expression member, object value)