From da41295aab22b2b905081e06d8ded5c9457d2bfd Mon Sep 17 00:00:00 2001 From: Thomas Bjelbo Thomsen <26603149+bjelbo@users.noreply.github.com> Date: Thu, 9 Jan 2025 17:43:19 +0100 Subject: [PATCH] Custom method can now have nullable parameters and nullable arguments for 9.x (#1374) * Custom method can now have nullable parameters and nullable arguments * added unit tests for MakeCustomFunctionCall * remove unused imports * align code --- .../Query/Expressions/ExpressionBinderBase.cs | 2 +- .../Expressions/ExpressionBinderHelper.cs | 16 +++++ .../QueryBinder.SingleValueFunctionCall.cs | 2 +- .../Query/Expressions/QueryBinderTests.cs | 67 ++++++++++++++++--- 4 files changed, 77 insertions(+), 10 deletions(-) diff --git a/src/Microsoft.AspNetCore.OData/Query/Expressions/ExpressionBinderBase.cs b/src/Microsoft.AspNetCore.OData/Query/Expressions/ExpressionBinderBase.cs index 0fa5d690..e616cb6a 100644 --- a/src/Microsoft.AspNetCore.OData/Query/Expressions/ExpressionBinderBase.cs +++ b/src/Microsoft.AspNetCore.OData/Query/Expressions/ExpressionBinderBase.cs @@ -700,7 +700,7 @@ private Expression BindCustomMethodExpressionOrNull(SingleValueFunctionCallNode MethodInfo methodInfo; if (UriFunctionsBinder.TryGetMethodInfo(node.Name, methodArgumentsType, out methodInfo)) { - return ExpressionBinderHelper.MakeFunctionCall(methodInfo, QuerySettings, arguments); + return ExpressionBinderHelper.MakeCustomFunctionCall(methodInfo, arguments); } return null; diff --git a/src/Microsoft.AspNetCore.OData/Query/Expressions/ExpressionBinderHelper.cs b/src/Microsoft.AspNetCore.OData/Query/Expressions/ExpressionBinderHelper.cs index 0f74c005..f4433bea 100644 --- a/src/Microsoft.AspNetCore.OData/Query/Expressions/ExpressionBinderHelper.cs +++ b/src/Microsoft.AspNetCore.OData/Query/Expressions/ExpressionBinderHelper.cs @@ -247,6 +247,22 @@ public static Expression MakeFunctionCall(MemberInfo member, ODataQuerySettings return CreateFunctionCallWithNullPropagation(functionCall, arguments, querySettings); } + //Custom methods might contain nullable parameters and, therefore, also should be able to take arguments of type Nullable + public static Expression MakeCustomFunctionCall(MethodInfo method, params Expression[] arguments) + { + Expression functionCall; + if (method.IsStatic) + { + functionCall = Expression.Call(null, method, arguments); + } + else + { + functionCall = Expression.Call(arguments.First(), method, arguments.Skip(1)); + } + + return functionCall; + } + public static Expression CreateFunctionCallWithNullPropagation(Expression functionCall, Expression[] arguments, ODataQuerySettings querySettings) { if (querySettings.HandleNullPropagation == HandleNullPropagationOption.True) diff --git a/src/Microsoft.AspNetCore.OData/Query/Expressions/QueryBinder.SingleValueFunctionCall.cs b/src/Microsoft.AspNetCore.OData/Query/Expressions/QueryBinder.SingleValueFunctionCall.cs index 100564d1..3af97de0 100644 --- a/src/Microsoft.AspNetCore.OData/Query/Expressions/QueryBinder.SingleValueFunctionCall.cs +++ b/src/Microsoft.AspNetCore.OData/Query/Expressions/QueryBinder.SingleValueFunctionCall.cs @@ -758,7 +758,7 @@ protected virtual Expression BindCustomMethodExpressionOrNull(SingleValueFunctio MethodInfo methodInfo; if (UriFunctionsBinder.TryGetMethodInfo(node.Name, methodArgumentsType, out methodInfo)) { - return ExpressionBinderHelper.MakeFunctionCall(methodInfo, context.QuerySettings, arguments); + return ExpressionBinderHelper.MakeCustomFunctionCall(methodInfo, arguments); } return null; diff --git a/test/Microsoft.AspNetCore.OData.Tests/Query/Expressions/QueryBinderTests.cs b/test/Microsoft.AspNetCore.OData.Tests/Query/Expressions/QueryBinderTests.cs index 52a600fa..92ea1828 100644 --- a/test/Microsoft.AspNetCore.OData.Tests/Query/Expressions/QueryBinderTests.cs +++ b/test/Microsoft.AspNetCore.OData.Tests/Query/Expressions/QueryBinderTests.cs @@ -5,22 +5,23 @@ // //------------------------------------------------------------------------------ -using System; -using System.Collections.Generic; -using System.Linq.Expressions; -using System.Reflection; -using Moq; -using Xunit; using Microsoft.AspNetCore.OData.Edm; using Microsoft.AspNetCore.OData.Query; using Microsoft.AspNetCore.OData.Query.Expressions; using Microsoft.AspNetCore.OData.Query.Wrapper; +using Microsoft.AspNetCore.OData.TestCommon; using Microsoft.AspNetCore.OData.Tests.Commons; +using Microsoft.AspNetCore.OData.Tests.Models; using Microsoft.OData.Edm; using Microsoft.OData.ModelBuilder; using Microsoft.OData.UriParser; -using Microsoft.AspNetCore.OData.Tests.Models; -using Microsoft.AspNetCore.OData.TestCommon; +using Moq; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Linq.Expressions; +using System.Reflection; +using Xunit; namespace Microsoft.AspNetCore.OData.Tests.Query.Expressions; @@ -493,6 +494,56 @@ private static IEdmModel BuildAndGetEdmModel() return builder.GetEdmModel(); } #endregion + + + [Theory] + [InlineData(0)] + [InlineData(null)] + public void MakeCustomFunctionCall_StaticMethod_ShouldCreateCorrectExpression(int? value) + { + // Arrange + MethodInfo methodInfo = typeof(TestCustomFunctionCall).GetMethod(nameof(TestCustomFunctionCall.StaticCustomMethod)); + Expression[] arguments = { Expression.Constant(value, typeof(int?)) }; + + // Act + Expression result = ExpressionBinderHelper.MakeCustomFunctionCall(methodInfo, arguments); + + // Assert + Assert.NotNull(result); + Assert.IsAssignableFrom(result); + var methodCall = (MethodCallExpression)result; + Assert.Equal(methodInfo, methodCall.Method); + Assert.Equal(arguments, methodCall.Arguments); + } + + [Theory] + [InlineData(0)] + [InlineData(null)] + public void MakeCustomFunctionCall_InstanceMethod_ShouldCreateCorrectExpression(int? value) + { + // Arrange + MethodInfo methodInfo = typeof(TestCustomFunctionCall).GetMethod(nameof(TestCustomFunctionCall.InstanceCustomMethod)); + Expression instance = Expression.Constant(new TestCustomFunctionCall()); + Expression[] arguments = { instance, Expression.Constant(value, typeof(int?)) }; + + // Act + Expression result = ExpressionBinderHelper.MakeCustomFunctionCall(methodInfo, arguments); + + // Assert + Assert.NotNull(result); + Assert.IsAssignableFrom(result); + var methodCall = (MethodCallExpression)result; + Assert.Equal(methodInfo, methodCall.Method); + Assert.Equal(arguments.Skip(1), methodCall.Arguments); + Assert.Equal(instance, methodCall.Object); + } +} + + +internal class TestCustomFunctionCall +{ + public static void StaticCustomMethod(int? x) { } + public void InstanceCustomMethod(int? x) { } } public class MyQueryBinder : QueryBinder