diff --git a/src/System.Linq.Dynamic.Core/Parser/ExpressionParser.cs b/src/System.Linq.Dynamic.Core/Parser/ExpressionParser.cs index c871568e..1edb773e 100644 --- a/src/System.Linq.Dynamic.Core/Parser/ExpressionParser.cs +++ b/src/System.Linq.Dynamic.Core/Parser/ExpressionParser.cs @@ -1704,9 +1704,12 @@ Expression ParseMemberAccess(Type type, Expression expression) throw ParseError(errorPos, Res.MethodsAreInaccessible, TypeHelper.GetTypeName(method.DeclaringType)); } - if (expression == null) + if (method.IsGenericMethod) { - return Expression.Call(null, method, args); + var genericParameters = method.GetParameters().Where(p => p.ParameterType.IsGenericParameter); + var typeArguments = genericParameters.Select(a => args[a.Position].Type); + var constructedMethod = method.MakeGenericMethod(typeArguments.ToArray()); + return Expression.Call(expression, constructedMethod, args); } else { diff --git a/src/System.Linq.Dynamic.Core/Parser/ExpressionPromoter.cs b/src/System.Linq.Dynamic.Core/Parser/ExpressionPromoter.cs index 71770b95..7ee3ec86 100644 --- a/src/System.Linq.Dynamic.Core/Parser/ExpressionPromoter.cs +++ b/src/System.Linq.Dynamic.Core/Parser/ExpressionPromoter.cs @@ -22,7 +22,7 @@ public ExpressionPromoter(ParsingConfig config) /// public virtual Expression Promote(Expression expr, Type type, bool exact, bool convertExpr) { - if (expr.Type == type) + if (expr.Type == type || type.IsGenericParameter) { return expr; } diff --git a/src/System.Linq.Dynamic.Core/Parser/SupportedMethods/MethodFinder.cs b/src/System.Linq.Dynamic.Core/Parser/SupportedMethods/MethodFinder.cs index 0cf6f7e4..1e2a8efe 100644 --- a/src/System.Linq.Dynamic.Core/Parser/SupportedMethods/MethodFinder.cs +++ b/src/System.Linq.Dynamic.Core/Parser/SupportedMethods/MethodFinder.cs @@ -58,7 +58,7 @@ public int FindMethod(Type type, string methodName, bool staticAccess, ref Expre { if (_parsingConfig.CustomTypeProvider.GetExtensionMethods().TryGetValue(t, out var extensionMethodsOfType)) { - methods.AddRange(extensionMethodsOfType.Where(m => m.Name.Equals(methodName, StringComparison.OrdinalIgnoreCase) && !m.IsGenericMethod)); + methods.AddRange(extensionMethodsOfType.Where(m => m.Name.Equals(methodName, StringComparison.OrdinalIgnoreCase))); } } diff --git a/test/System.Linq.Dynamic.Core.Tests/DynamicExpressionParserTests.cs b/test/System.Linq.Dynamic.Core.Tests/DynamicExpressionParserTests.cs index 2b00d294..1fa67b45 100644 --- a/test/System.Linq.Dynamic.Core.Tests/DynamicExpressionParserTests.cs +++ b/test/System.Linq.Dynamic.Core.Tests/DynamicExpressionParserTests.cs @@ -1,4 +1,4 @@ -using System.Collections.Generic; +using System.Collections.Generic; using System.Linq.Dynamic.Core.CustomTypeProviders; using System.Linq.Dynamic.Core.Exceptions; using System.Linq.Dynamic.Core.Tests.Helpers.Models; @@ -1070,7 +1070,7 @@ public void DynamicExpressionParser_ParseLambda_Operator_Less_Greater_With_Guids // Assert Assert.Equal(anotherId, result); - } + } [Theory] [InlineData("c => c.Age == 8", "c => (c.Age == 8)")] @@ -1239,7 +1239,7 @@ public void DynamicExpressionParser_ParseLambda_String_TrimEnd_0_Parameters() var @delegate = expression.Compile(); - var result = (bool) @delegate.DynamicInvoke("This is a test "); + var result = (bool)@delegate.DynamicInvoke("This is a test "); // Assert result.Should().BeTrue(); @@ -1258,5 +1258,36 @@ public void DynamicExpressionParser_ParseLambda_String_TrimEnd_1_Parameter() // Assert result.Should().BeTrue(); } + + public class DefaultDynamicLinqCustomTypeProviderForGenericExtensionMethod : CustomTypeProviders.DefaultDynamicLinqCustomTypeProvider + { + public override HashSet GetCustomTypes() => new HashSet(base.GetCustomTypes()) { typeof(Methods), typeof(MethodsItemExtension) }; + } + + [Fact] + public void DynamicExpressionParser_ParseLambda_GenericExtensionMethod() + { + // Arrange + var testList = User.GenerateSampleModels(51); + var config = new ParsingConfig() + { + CustomTypeProvider = new DefaultDynamicLinqCustomTypeProviderForGenericExtensionMethod() + }; + + // Act + string query = "x => MethodsItemExtension.Functions.EfCoreCollate(x.UserName, \"tlh-KX\")==\"User4\" || MethodsItemExtension.Functions.EfCoreCollate(x.UserName, \"tlh-KX\")==\"User2\""; + var expression = DynamicExpressionParser.ParseLambda(config, false, query); + var del = expression.Compile(); + + var result = Enumerable.Where(testList, del); + + + var expected = testList.Where(x => new string[] { "User4", "User2" }.Contains(x.UserName)).ToList(); + + // Assert + Check.That(result).IsNotNull(); + Check.That(result).HasSize(expected.Count); + Check.That(result).Equals(expected); + } } } diff --git a/test/System.Linq.Dynamic.Core.Tests/ExpressionTests.cs b/test/System.Linq.Dynamic.Core.Tests/ExpressionTests.cs index ecbd1089..aa54a7e0 100644 --- a/test/System.Linq.Dynamic.Core.Tests/ExpressionTests.cs +++ b/test/System.Linq.Dynamic.Core.Tests/ExpressionTests.cs @@ -1,4 +1,4 @@ -using System.Collections.Generic; +using System.Collections.Generic; using System.Dynamic; using System.Globalization; using System.Linq.Dynamic.Core.Exceptions; @@ -1304,6 +1304,69 @@ public void ExpressionTests_Method_OneParam_With_it() Assert.Equal(expected.Count(), result.Count()); } + public class DefaultDynamicLinqCustomTypeProviderForStaticTesting : CustomTypeProviders.DefaultDynamicLinqCustomTypeProvider + { + public override HashSet GetCustomTypes() => new HashSet(base.GetCustomTypes()) { typeof(Methods), typeof(MethodsItemExtension) }; + } + + [Fact] + public void ExpressionTests_MethodCall_GenericStatic() + { + var config = new ParsingConfig + { + CustomTypeProvider = new DefaultDynamicLinqCustomTypeProviderForStaticTesting() + }; + + // Arrange + var list = new[] { 0, 1, 2, 3, 4 }.Select(value => new Methods.Item { Value = value }).ToArray(); + + // Act + var expectedResult = list.Where(x => Methods.StaticGenericMethod(x)); + var result = list.AsQueryable().Where(config, "Methods.StaticGenericMethod(it)"); + + // Assert + Assert.Equal(expectedResult.Count(), result.Count()); + } + + [Fact] + public void ExpressionTests_MethodCall_Generic() + { + var config = new ParsingConfig + { + CustomTypeProvider = new DefaultDynamicLinqCustomTypeProviderForStaticTesting() + }; + + // Arrange + var list = new[] { 0, 1, 2, 3, 4 }.Select(value => new Methods.Item { Value = value }).ToArray(); + + // Act + var methods = new Methods(); + var expectedResult = list.Where(x => methods.GenericMethod(x)); + var result = list.AsQueryable().Where("@0.GenericMethod(it)", methods); + + // Assert + Assert.Equal(expectedResult.Count(), result.Count()); + } + + [Fact] + public void ExpressionTests_MethodCall_GenericExtension() + { + var config = new ParsingConfig + { + CustomTypeProvider = new DefaultDynamicLinqCustomTypeProviderForStaticTesting() + }; + + // Arrange + var list = new[] { 0, 1, 2, 3, 4 }.Select(value => new Methods.Item { Value = value }).ToArray(); + + // Act + var methods = new Methods(); + var expectedResult = list.Where(x => MethodsItemExtension.Functions.EfCoreCollate(x.Value, "tlh-KX") == 2); + var result = list.AsQueryable().Where(config, "MethodsItemExtension.Functions.EfCoreCollate(it.Value,\"tlh-KX\")==2"); + + // Assert + Assert.Equal(expectedResult.Count(), result.Count()); + } [Fact] public void ExpressionTests_MethodCall_ValueTypeToValueTypeParameter() diff --git a/test/System.Linq.Dynamic.Core.Tests/Helpers/Models/Methods.cs b/test/System.Linq.Dynamic.Core.Tests/Helpers/Models/Methods.cs index 37cb557c..cca9c424 100644 --- a/test/System.Linq.Dynamic.Core.Tests/Helpers/Models/Methods.cs +++ b/test/System.Linq.Dynamic.Core.Tests/Helpers/Models/Methods.cs @@ -1,4 +1,4 @@ -namespace System.Linq.Dynamic.Core.Tests.Helpers.Models +namespace System.Linq.Dynamic.Core.Tests.Helpers.Models { public class Methods { @@ -22,5 +22,17 @@ public class Item { public int Value { get; set; } } + + public static bool StaticGenericMethod(T value) => value is Item item && item.Value == 1; + public bool GenericMethod(T value) => value is Item item && item.Value == 1; + + } + + public static class MethodsItemExtension + { + public class DummyFunctions { } + public static DummyFunctions Functions => new DummyFunctions(); + + public static T EfCoreCollate(this DummyFunctions _, T value, string collation) => value; } }