From c51bd066d1a43dd83c5d9c41dfd22bae823f96d3 Mon Sep 17 00:00:00 2001 From: Stef Heyenrath Date: Tue, 23 Apr 2024 13:32:20 +0200 Subject: [PATCH] Fix DynamicExpressionParser for IQueryable --- .../Parser/ExpressionParser.cs | 16 +++++--------- .../Parser/TypeHelper.cs | 19 ++++++++-------- .../DynamicExpressionParserTests.cs | 22 +++++++++++++++++++ 3 files changed, 38 insertions(+), 19 deletions(-) diff --git a/src/System.Linq.Dynamic.Core/Parser/ExpressionParser.cs b/src/System.Linq.Dynamic.Core/Parser/ExpressionParser.cs index 86f29e05..8187cb87 100644 --- a/src/System.Linq.Dynamic.Core/Parser/ExpressionParser.cs +++ b/src/System.Linq.Dynamic.Core/Parser/ExpressionParser.cs @@ -1799,16 +1799,12 @@ private Expression ParseMemberAccess(Type? type, Expression? expression, string? var isStaticAccess = expression == null; - if (!isStaticAccess) + if (!isStaticAccess && TypeHelper.TryFindGenericType(typeof(IEnumerable<>), type, out var enumerableType)) { - var enumerableType = TypeHelper.FindGenericType(typeof(IEnumerable<>), type); - if (enumerableType != null) + var elementType = enumerableType.GetTypeInfo().GetGenericTypeArguments()[0]; + if (TryParseEnumerable(expression!, elementType, id, errorPos, type, out args, out var enumerableExpression)) { - var elementType = enumerableType.GetTypeInfo().GetGenericTypeArguments()[0]; - if (TryParseEnumerable(expression!, elementType, id, errorPos, type, out args, out var enumerableExpression)) - { - return enumerableExpression; - } + return enumerableExpression; } } @@ -2075,7 +2071,7 @@ private bool TryParseEnumerable(Expression instance, Type elementType, string me expression = null; return false; } - + if (type != null && TypeHelper.IsDictionary(type) && _methodFinder.ContainsMethod(type, methodName, false)) { var dictionaryMethod = type.GetMethod(methodName)!; @@ -2087,7 +2083,7 @@ private bool TryParseEnumerable(Expression instance, Type elementType, string me _methodFinder.CheckAggregateMethodAndTryUpdateArgsToMatchMethodArgs(methodName, ref args); var callType = typeof(Enumerable); - if (type != null && TypeHelper.FindGenericType(typeof(IQueryable<>), type) != null && _methodFinder.ContainsMethod(type, methodName)) + if (TypeHelper.TryFindGenericType(typeof(IQueryable<>), type, out _) && _methodFinder.ContainsMethod(typeof(Queryable), methodName)) { callType = typeof(Queryable); } diff --git a/src/System.Linq.Dynamic.Core/Parser/TypeHelper.cs b/src/System.Linq.Dynamic.Core/Parser/TypeHelper.cs index c7254060..d164c4b4 100644 --- a/src/System.Linq.Dynamic.Core/Parser/TypeHelper.cs +++ b/src/System.Linq.Dynamic.Core/Parser/TypeHelper.cs @@ -20,23 +20,23 @@ public static bool TryGetFirstGenericArgument(Type type, [NotNullWhen(true)] out return true; } - public static Type? FindGenericType(Type generic, Type? type) + public static bool TryFindGenericType(Type generic, Type? type, [NotNullWhen(true)] out Type? foundType) { while (type != null && type != typeof(object)) { if (type.GetTypeInfo().IsGenericType && type.GetGenericTypeDefinition() == generic) { - return type; + foundType = type; + return true; } if (generic.GetTypeInfo().IsInterface) { - foreach (Type interfaceType in type.GetInterfaces()) + foreach (var interfaceType in type.GetInterfaces()) { - var foundType = FindGenericType(generic, interfaceType); - if (foundType != null) + if (TryFindGenericType(generic, interfaceType, out foundType)) { - return foundType; + return true; } } } @@ -44,7 +44,8 @@ public static bool TryGetFirstGenericArgument(Type type, [NotNullWhen(true)] out type = type.GetTypeInfo().BaseType; } - return null; + foundType = null; + return false; } public static bool IsCompatibleWith(Type source, Type target) @@ -509,12 +510,12 @@ public static bool TryParseEnum(string value, Type? type, [NotNullWhen(true)] ou public static bool IsDictionary(Type? type) { return - FindGenericType(typeof(IDictionary<,>), type) != null || + TryFindGenericType(typeof(IDictionary<,>), type, out _) || #if NET35 || NET40 // ReSharper disable once RedundantLogicalConditionalExpressionOperand false; #else - FindGenericType(typeof(IReadOnlyDictionary<,>), type) != null; + TryFindGenericType(typeof(IReadOnlyDictionary<,>), type, out _); #endif } } \ No newline at end of file diff --git a/test/System.Linq.Dynamic.Core.Tests/DynamicExpressionParserTests.cs b/test/System.Linq.Dynamic.Core.Tests/DynamicExpressionParserTests.cs index 4afc9605..7185c405 100644 --- a/test/System.Linq.Dynamic.Core.Tests/DynamicExpressionParserTests.cs +++ b/test/System.Linq.Dynamic.Core.Tests/DynamicExpressionParserTests.cs @@ -2,6 +2,7 @@ using System.Globalization; using System.Linq.Dynamic.Core.CustomTypeProviders; using System.Linq.Dynamic.Core.Exceptions; +using System.Linq.Dynamic.Core.Tests.Helpers; using System.Linq.Dynamic.Core.Tests.Helpers.Models; using System.Linq.Dynamic.Core.Tests.TestHelpers; using System.Linq.Expressions; @@ -577,6 +578,27 @@ public void DynamicExpressionParser_ParseLambda_Select_2() Assert.NotNull(result); } + // #801 + [Fact] + public void DynamicExpressionParser_ParseLambda_IQueryable() + { + // Assign + var qry = new[] + { + new + { + MessageClassName = "mc" + } + }.AsQueryable(); + + // Act + var expression = DynamicExpressionParser.ParseLambda(qry.GetType(), null, "it.GroupBy(MessageClassName).Select(it.Key).Where(it != null).Distinct().OrderBy(it).Take(1000)"); + + // Assert + expression.ToDebugView().Should().Contain(".Call System.Linq.Queryable"); + expression.ToDebugView().Should().NotContain(".Call System.Linq.Enumerable"); + } + // https://github.com/StefH/System.Linq.Dynamic.Core/issues/58 [Fact] public void DynamicExpressionParser_ParseLambda_Issue58()