diff --git a/src/System.Linq.Dynamic.Core/Parser/ExpressionHelper.cs b/src/System.Linq.Dynamic.Core/Parser/ExpressionHelper.cs index 8dfe35f5..07a64f4a 100644 --- a/src/System.Linq.Dynamic.Core/Parser/ExpressionHelper.cs +++ b/src/System.Linq.Dynamic.Core/Parser/ExpressionHelper.cs @@ -319,13 +319,12 @@ private List CollectExpressions(bool addSelf, Expression sourceExpre { case MemberExpression memberExpression: expression = GetMemberExpression(memberExpression.Expression); - expressionRecognized = true; + expressionRecognized = expression != null; break; case MethodCallExpression methodCallExpression: - // FIX method without parameter: https://github.com/zzzprojects/System.Linq.Dynamic.Core/issues/432 - expression = methodCallExpression.Arguments.FirstOrDefault(); - expressionRecognized = true; + expression = GetMethodCallExpression(methodCallExpression); + expressionRecognized = expression != null; break; default: @@ -341,5 +340,17 @@ private List CollectExpressions(bool addSelf, Expression sourceExpre return list; } + + private static Expression GetMethodCallExpression(MethodCallExpression methodCallExpression) + { + if (methodCallExpression.Object != null) + { + // Something like: "np(FooValue.Zero().Length)" + return methodCallExpression.Object; + } + + // Something like: "np(MyClasses.FirstOrDefault())" + return methodCallExpression.Arguments.FirstOrDefault(); + } } } diff --git a/test/System.Linq.Dynamic.Core.Tests/DynamicExpressionParserTests.cs b/test/System.Linq.Dynamic.Core.Tests/DynamicExpressionParserTests.cs index 19005375..741c6eda 100644 --- a/test/System.Linq.Dynamic.Core.Tests/DynamicExpressionParserTests.cs +++ b/test/System.Linq.Dynamic.Core.Tests/DynamicExpressionParserTests.cs @@ -1,20 +1,30 @@ -using NFluent; -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; +using System.Linq.Dynamic.Core.Tests.TestHelpers; using System.Linq.Expressions; using System.Reflection; using System.Runtime.CompilerServices; using FluentAssertions; +using NFluent; using Xunit; -using User = System.Linq.Dynamic.Core.Tests.Helpers.Models.User; -using System.Linq.Dynamic.Core.Tests.TestHelpers; namespace System.Linq.Dynamic.Core.Tests { public class DynamicExpressionParserTests { + public class Foo + { + public Foo FooValue { get; set; } + + public string Zero() => null; + + public string One(int x) => null; + + public string Two(int x, int y) => null; + } + private class MyClass { public List MyStrings { get; set; } @@ -291,7 +301,7 @@ public void DynamicExpressionParser_ParseLambda_UseParameterizedNamesInDynamicQu dynamic wrappedObj = constantExpression.Value; var propertyInfo = wrappedObj.GetType().GetProperty("Value", BindingFlags.Instance | BindingFlags.Public); - int value = (int) propertyInfo.GetValue(wrappedObj); + int value = (int)propertyInfo.GetValue(wrappedObj); Check.That(value).IsEqualTo(42); } @@ -299,7 +309,7 @@ public void DynamicExpressionParser_ParseLambda_UseParameterizedNamesInDynamicQu [Theory] [InlineData("NullableIntValue", "42")] [InlineData("NullableDoubleValue", "42.23")] - public void DynamicExpressionParser_ParseLambda_UseParameterizedNamesInDynamicQuery_ForNullableProperty_true(string propName, string valueString) + public void DynamicExpressionParser_ParseLambda_UseParameterizedNamesInDynamicQuery_ForNullableProperty_true(string propName, string valueString) { // Assign var config = new ParsingConfig @@ -1102,21 +1112,73 @@ public void DynamicExpressionParser_ParseLambda_SupportEnumerationStringComparis Check.That(result).IsEqualTo(expectedResult); } + [Fact] + public void DynamicExpressionParser_ParseLambda_NullPropagation_InstanceMethod_Zero_Arguments() + { + // Arrange + var expression = "np(FooValue.Zero().Length)"; + + // Act + var lambdaExpression = DynamicExpressionParser.ParseLambda(typeof(Foo), null, expression, new Foo()); + + // Assert +#if NET452 + lambdaExpression.ToString().Should().Be("Param_0 => IIF((((Param_0 != null) AndAlso (Param_0.FooValue != null)) AndAlso (Param_0.FooValue.Zero() != null)), Convert(Param_0.FooValue.Zero().Length), null)"); +#else + lambdaExpression.ToString().Should().Be("Param_0 => IIF((((Param_0 != null) AndAlso (Param_0.FooValue != null)) AndAlso (Param_0.FooValue.Zero() != null)), Convert(Param_0.FooValue.Zero().Length, Nullable`1), null)"); +#endif + } + + [Fact] + public void DynamicExpressionParser_ParseLambda_NullPropagation_InstanceMethod_One_Argument() + { + // Arrange + var expression = "np(FooValue.One(1).Length)"; + + // Act + var lambdaExpression = DynamicExpressionParser.ParseLambda(typeof(Foo), null, expression, new Foo()); + + // Assert +#if NET452 + lambdaExpression.ToString().Should().Be("Param_0 => IIF((((Param_0 != null) AndAlso (Param_0.FooValue != null)) AndAlso (Param_0.FooValue.One(1) != null)), Convert(Param_0.FooValue.One(1).Length), null)"); +#else + lambdaExpression.ToString().Should().Be("Param_0 => IIF((((Param_0 != null) AndAlso (Param_0.FooValue != null)) AndAlso (Param_0.FooValue.One(1) != null)), Convert(Param_0.FooValue.One(1).Length, Nullable`1), null)"); +#endif + } + + [Fact] + public void DynamicExpressionParser_ParseLambda_NullPropagation_InstanceMethod_Two_Arguments() + { + // Arrange + var expression = "np(FooValue.Two(1, 42).Length)"; + + // Act + var lambdaExpression = DynamicExpressionParser.ParseLambda(typeof(Foo), null, expression, new Foo()); + + // Assert +#if NET452 + lambdaExpression.ToString().Should().Be("Param_0 => IIF((((Param_0 != null) AndAlso (Param_0.FooValue != null)) AndAlso (Param_0.FooValue.Two(1, 42) != null)), Convert(Param_0.FooValue.Two(1, 42).Length), null)"); +#else + lambdaExpression.ToString().Should().Be("Param_0 => IIF((((Param_0 != null) AndAlso (Param_0.FooValue != null)) AndAlso (Param_0.FooValue.Two(1, 42) != null)), Convert(Param_0.FooValue.Two(1, 42).Length, Nullable`1), null)"); +#endif + } + [Fact] public void DynamicExpressionParser_ParseLambda_NullPropagation_MethodCallExpression() { // Arrange - var dataSource = new MyClass(); + var myClass = new MyClass(); + var dataSource = new { MyClasses = new[] { myClass, null } }; var expressionText = "np(MyClasses.FirstOrDefault())"; // Act LambdaExpression expression = DynamicExpressionParser.ParseLambda(ParsingConfig.Default, dataSource.GetType(), typeof(MyClass), expressionText); Delegate del = expression.Compile(); - MyClass result = del.DynamicInvoke(dataSource) as MyClass; + var result = del.DynamicInvoke(dataSource) as MyClass; // Assert - result.Should().BeNull(); + result.Should().Be(myClass); } [Theory] diff --git a/test/System.Linq.Dynamic.Core.Tests/ExpressionTests.cs b/test/System.Linq.Dynamic.Core.Tests/ExpressionTests.cs index 6173389f..fa1a545c 100644 --- a/test/System.Linq.Dynamic.Core.Tests/ExpressionTests.cs +++ b/test/System.Linq.Dynamic.Core.Tests/ExpressionTests.cs @@ -1,4 +1,5 @@ -using Newtonsoft.Json.Linq; +using FluentAssertions; +using Newtonsoft.Json.Linq; using NFluent; using System.Collections.Generic; using System.Dynamic; @@ -49,6 +50,17 @@ public class TestObjectIdClass public long ObjectId { get; set; } } + public class Foo + { + public Foo FooValue { get; set; } + + public string Zero() => null; + + public string One(int x) => null; + + public string Two(int x, int y) => null; + } + [Fact] public void ExpressionTests_Add_Number() { @@ -1394,6 +1406,48 @@ public void ExpressionTests_NullPropagating(string test, string query) Check.That(queryAsString).Equals(query); } + [Fact] + public void ExpressionTests_NullPropagating_InstanceMethod_Zero_Arguments() + { + // Arrange 1 + var expression = "np(FooValue.Zero().Length)"; + var q = new[] { new Foo { FooValue = new Foo() } }.AsQueryable(); + + // Act 2 + var result = q.Select(expression).FirstOrDefault() as int?; + + // Assert 2 + result.Should().BeNull(); + } + + [Fact] + public void ExpressionTests_NullPropagating_InstanceMethod_One_Argument() + { + // Arrange + var expression = "np(FooValue.One(1).Length)"; + var q = new[] { new Foo { FooValue = new Foo() } }.AsQueryable(); + + // Act + var result = q.Select(expression).FirstOrDefault() as int?; + + // Assert + result.Should().BeNull(); + } + + [Fact] + public void ExpressionTests_NullPropagating_InstanceMethod_Two_Arguments() + { + // Arrange + var expression = "np(FooValue.Two(1, 42).Length)"; + var q = new[] { new Foo { FooValue = new Foo() } }.AsQueryable(); + + // Act + var result = q.Select(expression).FirstOrDefault() as int?; + + // Assert + result.Should().BeNull(); + } + [Fact] public void ExpressionTests_NullPropagation_Method() { diff --git a/test/System.Linq.Dynamic.Core.Tests/System.Linq.Dynamic.Core.Tests.csproj b/test/System.Linq.Dynamic.Core.Tests/System.Linq.Dynamic.Core.Tests.csproj index a82fc137..aa49b467 100644 --- a/test/System.Linq.Dynamic.Core.Tests/System.Linq.Dynamic.Core.Tests.csproj +++ b/test/System.Linq.Dynamic.Core.Tests/System.Linq.Dynamic.Core.Tests.csproj @@ -37,6 +37,7 @@ +