From a0c59a613a17b364be05771caa32c97941c78656 Mon Sep 17 00:00:00 2001 From: David Lebee Date: Fri, 31 Jul 2020 13:02:51 -0400 Subject: [PATCH] ready for pull-request. --- PoweredSoft.DynamicLinq.Test/HelpersTests.cs | 19 ++++++ PoweredSoft.DynamicLinq.Test/SelectTests.cs | 60 +++++++++++++++++++ .../SimpleQueriesTest.cs | 25 +++++++- .../Helpers/QueryableHelpers.cs | 47 ++++++++++++--- .../Parser/ExpressionParser.cs | 2 +- .../Resolver/PathExpressionResolver.cs | 9 ++- 6 files changed, 147 insertions(+), 15 deletions(-) diff --git a/PoweredSoft.DynamicLinq.Test/HelpersTests.cs b/PoweredSoft.DynamicLinq.Test/HelpersTests.cs index 5c124a1..1d6e390 100644 --- a/PoweredSoft.DynamicLinq.Test/HelpersTests.cs +++ b/PoweredSoft.DynamicLinq.Test/HelpersTests.cs @@ -7,10 +7,29 @@ using PoweredSoft.DynamicLinq.Helpers; namespace PoweredSoft.DynamicLinq.Test { + class Foo + { + + } + + class ListOfFoo : List + { + + } + [TestClass] public class HelpersTests { + [TestMethod] + public void TestInheritanceOfListAsGenericEnumerableType() + { + var shouldBeTrue = QueryableHelpers.IsGenericEnumerable(typeof(ListOfFoo)); + Assert.IsTrue(shouldBeTrue); + var type = QueryableHelpers.GetTypeOfEnumerable(typeof(ListOfFoo), true); + Assert.IsTrue(type == typeof(Foo)); + } + [TestMethod] public void TestCreateFilterExpression() { diff --git a/PoweredSoft.DynamicLinq.Test/SelectTests.cs b/PoweredSoft.DynamicLinq.Test/SelectTests.cs index e966e48..eba876e 100644 --- a/PoweredSoft.DynamicLinq.Test/SelectTests.cs +++ b/PoweredSoft.DynamicLinq.Test/SelectTests.cs @@ -23,9 +23,69 @@ namespace PoweredSoft.DynamicLinq.Test public List FirstNames { get; set; } } + public class MockPerson + { + public string Name { get; set; } + public MockListOfPhone Phones { get; set; } + } + + public class MockPhone + { + public string Number { get; set; } + } + + public class MockListOfPhone : List + { + + } + [TestClass] public class SelectTests { + [TestMethod] + public void TestSelectWithInheritedList() + { + var list = new List() + { + new MockPerson + { + Name = "David Lebee", + Phones = new MockListOfPhone + { + new MockPhone + { + Number = "0000000000" + } + } + }, + new MockPerson + { + Name = "Yubing Liang", + Phones = new MockListOfPhone + { + new MockPhone + { + Number = "1111111111" + } + } + } + }; + + var names = list.AsQueryable() + .Where(t => t.Equal("Phones.Number", "1111111111")) + .Select(t => + { + t.Path("Name"); + t.FirstOrDefault("Phones.Number", "Number", SelectCollectionHandling.Flatten); + }) + .ToDynamicClassList(); + + Assert.IsTrue(names.Count() == 1); + var firstPerson = names.First(); + Assert.AreEqual("Yubing Liang", firstPerson.GetDynamicPropertyValue("Name")); + Assert.AreEqual("1111111111", firstPerson.GetDynamicPropertyValue("Number")); + } + [TestMethod] public void TestSelect() { diff --git a/PoweredSoft.DynamicLinq.Test/SimpleQueriesTest.cs b/PoweredSoft.DynamicLinq.Test/SimpleQueriesTest.cs index 1ab7aeb..53bc020 100644 --- a/PoweredSoft.DynamicLinq.Test/SimpleQueriesTest.cs +++ b/PoweredSoft.DynamicLinq.Test/SimpleQueriesTest.cs @@ -135,9 +135,9 @@ namespace PoweredSoft.DynamicLinq.Test // subject. var posts = new List() { - new Post { Id = 1, AuthorId = 1, Title = "Hello 1", Content = "World" }, - new Post { Id = 2, AuthorId = 1, Title = "Hello 2", Content = "World" }, - new Post { Id = 3, AuthorId = 2, Title = "Hello 3", Content = "World" }, + new Post { Id = 1, AuthorId = 1, Title = "Hello 1", Content = "World"}, + new Post { Id = 2, AuthorId = 1, Title = "Hello 2", Content = "World"}, + new Post { Id = 3, AuthorId = 2, Title = "Hello 3", Content = "World"}, }; // the query. @@ -151,5 +151,24 @@ namespace PoweredSoft.DynamicLinq.Test Assert.IsTrue(first.Id == 3); Assert.IsTrue(second.Id == 1); } + + [TestMethod] + public void TestingSort2() + { + // subject. + var posts = new List() + { + new Post { Id = 1, AuthorId = 1, Title = "Hello 1", Content = "World", Comments = new List { } }, + new Post { Id = 2, AuthorId = 1, Title = "Hello 2", Content = "World", Comments = new List { } }, + new Post { Id = 3, AuthorId = 2, Title = "Hello 3", Content = "World", Comments = new List { } }, + }; + + // the query. + var query = posts.AsQueryable(); + + // just testing that the expressionm can be created, some drivers support seleting in collections. + var query2 = query.OrderByDescending("Comments"); + var query3 = query.OrderByDescending("Comments.PostId"); + } } } diff --git a/PoweredSoft.DynamicLinq/Helpers/QueryableHelpers.cs b/PoweredSoft.DynamicLinq/Helpers/QueryableHelpers.cs index 7587b4e..dc0253f 100644 --- a/PoweredSoft.DynamicLinq/Helpers/QueryableHelpers.cs +++ b/PoweredSoft.DynamicLinq/Helpers/QueryableHelpers.cs @@ -253,7 +253,7 @@ namespace PoweredSoft.DynamicLinq.Helpers Expression selectExpression; if (QueryableHelpers.IsGenericEnumerable(innerSelectType) && selectCollectionHandling == SelectCollectionHandling.Flatten) - selectExpression = Expression.Call(typeof(Enumerable), "SelectMany", new Type[] { selectType, innerSelectType.GenericTypeArguments.First() }, parameter, innerLambdaExpression); + selectExpression = Expression.Call(typeof(Enumerable), "SelectMany", new Type[] { selectType, QueryableHelpers.GetTypeOfEnumerable(innerSelectType, true) }, parameter, innerLambdaExpression); else selectExpression = Expression.Call(typeof(Enumerable), "Select", new Type[] { selectType, innerSelectType }, parameter, innerLambdaExpression); @@ -359,7 +359,7 @@ namespace PoweredSoft.DynamicLinq.Helpers else { // enumerable. - var listGenericArgumentType = memberExpression.Type.GetGenericArguments().First(); + var listGenericArgumentType = QueryableHelpers.GetTypeOfEnumerable(memberExpression.Type, true); // sub param. var innerParam = Expression.Parameter(listGenericArgumentType); @@ -432,7 +432,7 @@ namespace PoweredSoft.DynamicLinq.Helpers public static IQueryable CreateOrderByExpression(IQueryable query, string path, QueryOrderByDirection direction, bool append = true) { var parameter = Expression.Parameter(query.ElementType, "t"); - var member = QueryableHelpers.ResolvePathForExpression(parameter, path); + var member = QueryableHelpers.ResolvePathForExpression(parameter, path, false); string sortCommand = null; if (direction == QueryOrderByDirection.Descending) @@ -503,7 +503,7 @@ namespace PoweredSoft.DynamicLinq.Helpers if (IsGenericEnumerable(memberExpression)) { - var listGenericArgumentType = memberExpression.Type.GetGenericArguments().First(); + var listGenericArgumentType = QueryableHelpers.GetTypeOfEnumerable(memberExpression.Type, true); var innerParameter = Expression.Parameter(listGenericArgumentType, $"t{++recursionStep}"); var innerLambda = InternalCreateConditionExpression(recursionStep, listGenericArgumentType, innerParameter, innerParameter, parts.Skip(1).ToList(), condition, value, convertStrategy, collectionHandling, nullChecking, stringComparison, negate); @@ -623,12 +623,43 @@ namespace PoweredSoft.DynamicLinq.Helpers public static bool IsGenericEnumerable(Expression member) => IsGenericEnumerable(member.Type); public static bool IsGenericEnumerable(Type type) { - if (!type.IsGenericType) + if (type == typeof(string)) return false; - var genericArgumentType = type.GenericTypeArguments.First(); - var ret = typeof(IEnumerable<>).MakeGenericType(genericArgumentType).IsAssignableFrom(type); - return ret; + if (type.IsGenericType) + { + var makeGenericType = typeof(IEnumerable<>).MakeGenericType(type.GetGenericArguments()[0]); + var possible = makeGenericType.IsAssignableFrom(type); + if (possible) + return true; + } + + var result = type.GetInterfaces().Any(t => t.IsGenericType && t.GetGenericTypeDefinition() == typeof(IEnumerable<>)); + return result; + } + + public static Type GetTypeOfEnumerable(Type genericEnumerableType, bool throwIfNotEnumerable) + { + Type result = null; + + if (genericEnumerableType.IsGenericType) + { + var makeGenericType = typeof(IEnumerable<>).MakeGenericType(genericEnumerableType.GetGenericArguments()[0]); + var possible = makeGenericType.IsAssignableFrom(genericEnumerableType); + if (possible) + return genericEnumerableType.GetGenericArguments()[0]; + } + + result = genericEnumerableType.GetInterfaces().FirstOrDefault(t => t.IsGenericType && t.GetGenericTypeDefinition() == typeof(IEnumerable<>)); + if (result == null) + { + if (throwIfNotEnumerable) + throw new Exception("Not a IEnumerable"); + + return null; + } + + return result.GetGenericArguments().First(); } } } diff --git a/PoweredSoft.DynamicLinq/Parser/ExpressionParser.cs b/PoweredSoft.DynamicLinq/Parser/ExpressionParser.cs index 721d459..32ce8f5 100644 --- a/PoweredSoft.DynamicLinq/Parser/ExpressionParser.cs +++ b/PoweredSoft.DynamicLinq/Parser/ExpressionParser.cs @@ -50,7 +50,7 @@ namespace PoweredSoft.DynamicLinq.Parser { Type = memberExpression.Type, IsGenericEnumerable = QueryableHelpers.IsGenericEnumerable(memberExpression), - EnumerableType = memberExpression.Type.GenericTypeArguments.FirstOrDefault(), + EnumerableType = QueryableHelpers.GetTypeOfEnumerable(memberExpression.Type, false), Parent = parent, Name = pp }; diff --git a/PoweredSoft.DynamicLinq/Resolver/PathExpressionResolver.cs b/PoweredSoft.DynamicLinq/Resolver/PathExpressionResolver.cs index 9871beb..7e026b0 100644 --- a/PoweredSoft.DynamicLinq/Resolver/PathExpressionResolver.cs +++ b/PoweredSoft.DynamicLinq/Resolver/PathExpressionResolver.cs @@ -74,8 +74,9 @@ namespace PoweredSoft.DynamicLinq.Resolver if (isSelectMany) { var selectType = parent.GroupEnumerableType(); + var groupExpressionEnumerableType = QueryableHelpers.GetTypeOfEnumerable(groupExpression.Type, true); var selectExpression = Expression.Call(typeof(Enumerable), "SelectMany", - new Type[] { selectType, groupExpression.Type.GenericTypeArguments.First() }, + new Type[] { selectType, groupExpressionEnumerableType }, parentExpression, groupExpressionLambda); currentExpression = selectExpression; } @@ -109,9 +110,10 @@ namespace PoweredSoft.DynamicLinq.Resolver if (isSelectMany) { + var currentExpressionEnumerableType = QueryableHelpers.GetTypeOfEnumerable(currentExpression.Type, true); var currentExpressionLambda = Expression.Lambda(currentExpression, group.Parameter); currentExpression = Expression.Call(typeof(Enumerable), "SelectMany", - new Type[] { selectType, currentExpression.Type.GenericTypeArguments.First() }, + new Type[] { selectType, currentExpressionEnumerableType }, parentExpression, currentExpressionLambda); } else @@ -144,7 +146,8 @@ namespace PoweredSoft.DynamicLinq.Resolver Expression ifTrueExpression = null; if (QueryableHelpers.IsGenericEnumerable(nullType)) { - var listType = typeof(List<>).MakeGenericType(nullType.GenericTypeArguments.First()); + var enumerableType = QueryableHelpers.GetTypeOfEnumerable(nullType, true); + var listType = typeof(List<>).MakeGenericType(enumerableType); ifTrueExpression = Expression.New(listType); }