Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
56 changes: 29 additions & 27 deletions src/System.Linq.Dynamic.Core/Parser/ExpressionHelper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -270,21 +270,16 @@ public bool TryGenerateAndAlsoNotNullExpression(Expression sourceExpression, boo
return true;
}

private static Expression GetMemberExpression(Expression expression)
public bool ExpressionQualifiesForNullPropagation(Expression expression)
{
if (expression is MemberExpression memberExpression)
{
return memberExpression;
}

if (expression is ParameterExpression parameterExpression)
{
return parameterExpression;
}
return expression is MemberExpression || expression is ParameterExpression || expression is MethodCallExpression;
}

if (expression is MethodCallExpression methodCallExpression)
private Expression GetMemberExpression(Expression expression)
{
if (ExpressionQualifiesForNullPropagation(expression))
{
return methodCallExpression;
return expression;
}

if (expression is LambdaExpression lambdaExpression)
Expand All @@ -303,7 +298,7 @@ private static Expression GetMemberExpression(Expression expression)
return null;
}

private static List<Expression> CollectExpressions(bool addSelf, Expression sourceExpression)
private List<Expression> CollectExpressions(bool addSelf, Expression sourceExpression)
{
Expression expression = GetMemberExpression(sourceExpression);

Expand All @@ -317,24 +312,31 @@ private static List<Expression> CollectExpressions(bool addSelf, Expression sour
}
}

while (expression is MemberExpression memberExpression)
bool expressionRecognized;
do
{
expression = GetMemberExpression(memberExpression.Expression);
if (expression is MemberExpression)
switch (expression)
{
list.Add(expression);
case MemberExpression memberExpression:
expression = GetMemberExpression(memberExpression.Expression);
expressionRecognized = true;
break;

case MethodCallExpression methodCallExpression:
expression = methodCallExpression.Arguments.First();
expressionRecognized = true;
break;

default:
expressionRecognized = false;
break;
}
}

if (expression is ParameterExpression)
{
list.Add(expression);
}

if (expression is MethodCallExpression)
{
list.Add(expression);
}
if (expressionRecognized && ExpressionQualifiesForNullPropagation(expression))
{
list.Add(expression);
}
} while (expressionRecognized);

return list;
}
Expand Down
10 changes: 5 additions & 5 deletions src/System.Linq.Dynamic.Core/Parser/ExpressionParser.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1135,20 +1135,20 @@ Expression ParseFunctionNullPropagation()
throw ParseError(errorPos, Res.NullPropagationRequiresCorrectArgs);
}

if (args[0] is MemberExpression memberExpression)
if (_expressionHelper.ExpressionQualifiesForNullPropagation(args[0]))
{
bool hasDefaultParameter = args.Length == 2;
Expression expressionIfFalse = hasDefaultParameter ? args[1] : Expression.Constant(null);

if (_expressionHelper.TryGenerateAndAlsoNotNullExpression(memberExpression, hasDefaultParameter, out Expression generatedExpression))
if (_expressionHelper.TryGenerateAndAlsoNotNullExpression(args[0], hasDefaultParameter, out Expression generatedExpression))
{
return GenerateConditional(generatedExpression, memberExpression, expressionIfFalse, errorPos);
return GenerateConditional(generatedExpression, args[0], expressionIfFalse, errorPos);
}

return memberExpression;
return args[0];
}

throw ParseError(errorPos, Res.NullPropagationRequiresMemberExpression);
throw ParseError(errorPos, Res.NullPropagationRequiresValidExpression);
}

// Is(...) function
Expand Down
2 changes: 2 additions & 0 deletions src/System.Linq.Dynamic.Core/Parser/IExpressionHelper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,8 @@ internal interface IExpressionHelper

bool TryGenerateAndAlsoNotNullExpression(Expression sourceExpression, bool addSelf, out Expression generatedExpression);

bool ExpressionQualifiesForNullPropagation(Expression expression);

void WrapConstantExpression(ref Expression argument);
}
}
2 changes: 1 addition & 1 deletion src/System.Linq.Dynamic.Core/Res.cs
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ internal static class Res
public const string NoParentInScope = "No 'parent' is in scope";
public const string NoRootInScope = "No 'root' is in scope";
public const string NullPropagationRequiresCorrectArgs = "The 'np' (null-propagation) function requires 1 or 2 arguments";
public const string NullPropagationRequiresMemberExpression = "The 'np' (null-propagation) function requires the first argument to be a MemberExpression";
public const string NullPropagationRequiresValidExpression = "The 'np' (null-propagation) function requires the first argument to be a MemberExpression, ParameterExpression or MethodCallExpression";
public const string OpenBracketExpected = "'[' expected";
public const string OpenCurlyParenExpected = "'{' expected";
public const string OpenParenExpected = "'(' expected";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,9 @@
<Reference Include="EntityFramework.SqlServer, Version=6.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089, processorArchitecture=MSIL">
<HintPath>..\..\packages\EntityFramework.6.1.3\lib\net45\EntityFramework.SqlServer.dll</HintPath>
</Reference>
<Reference Include="FluentAssertions, Version=5.10.3.0, Culture=neutral, PublicKeyToken=33f2691a05b67b6a, processorArchitecture=MSIL">
<HintPath>..\..\packages\FluentAssertions.5.10.3\lib\net45\FluentAssertions.dll</HintPath>
</Reference>
<Reference Include="Linq.PropertyTranslator.Core, Version=1.0.3.0, Culture=neutral, processorArchitecture=MSIL">
<HintPath>..\..\packages\Linq.PropertyTranslator.Core.1.0.3.0\lib\net452\Linq.PropertyTranslator.Core.dll</HintPath>
</Reference>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
<packages>
<package id="Castle.Core" version="4.4.0" targetFramework="net452" />
<package id="EntityFramework" version="6.1.3" targetFramework="net452" />
<package id="FluentAssertions" version="5.10.3" targetFramework="net452" />
<package id="Linq.PropertyTranslator.Core" version="1.0.3.0" targetFramework="net452" />
<package id="Microsoft.AspNet.Identity.Core" version="2.2.1" targetFramework="net452" />
<package id="Microsoft.AspNet.Identity.EntityFramework" version="2.2.1" targetFramework="net452" />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
</ItemGroup>

<ItemGroup>
<PackageReference Include="FluentAssertions" Version="5.10.3" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="15.3.0" />
<PackageReference Include="MongoDB.Driver" Version="2.4.4" />
<PackageReference Include="Newtonsoft.Json" Version="11.0.2" />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
using System.Linq.Dynamic.Core.Tests.Helpers.Models;
using System.Linq.Expressions;
using System.Reflection;
using FluentAssertions;
using Xunit;
using User = System.Linq.Dynamic.Core.Tests.Helpers.Models.User;

Expand All @@ -14,10 +15,18 @@ public class DynamicExpressionParserTests
{
private class MyClass
{
public List<string> MyStrings { get; set; }

public List<MyClass> MyClasses { get; set; }

public int Foo()
{
return 42;
}

public string Name { get; set; }

public MyClass Child { get; set; }
}

private class ComplexParseLambda1Result
Expand Down Expand Up @@ -1044,5 +1053,39 @@ public void DynamicExpressionParser_ParseLambda_SupportEnumerationStringComparis
// Assert
Check.That(result).IsEqualTo(expectedResult);
}

[Fact]
public void DynamicExpressionParser_ParseLambda_NullPropagation_MethodCallExpression()
{
// Arrange
var dataSource = new MyClass();

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;

// Assert
result.Should().BeNull();
}

[Theory]
[InlineData("np(MyClasses.FirstOrDefault().Name)")]
[InlineData("np(MyClasses.FirstOrDefault(Name == \"a\").Name)")]
public void DynamicExpressionParser_ParseLambda_NullPropagation_MethodCallExpression_With_Property(string expressionText)
{
// Arrange
var dataSource = new MyClass();

// Act
LambdaExpression expression = DynamicExpressionParser.ParseLambda(ParsingConfig.Default, dataSource.GetType(), typeof(string), expressionText);
Delegate del = expression.Compile();
string result = del.DynamicInvoke(dataSource) as string;

// Assert
result.Should().BeNull();
}
}
}
5 changes: 4 additions & 1 deletion test/System.Linq.Dynamic.Core.Tests/ExpressionTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1343,6 +1343,8 @@ public void ExpressionTests_NullCoalescing()
[InlineData("np(nested.nullablenumber, 42)", "Select(Param_0 => IIF((((Param_0 != null) AndAlso (Param_0.nested != null)) AndAlso (Param_0.nested.nullablenumber != null)), Param_0.nested.nullablenumber, 42))")]
[InlineData("np(nested._enumnullable)", "Select(Param_0 => IIF(((Param_0 != null) AndAlso (Param_0.nested != null)), Param_0.nested._enumnullable, null))")]
[InlineData("np(item.GuidNull)", "Select(Param_0 => IIF(((Param_0 != null) AndAlso (Param_0.item != null)), Param_0.item.GuidNull, null))")]
[InlineData("np(items.FirstOrDefault())", "Select(Param_0 => IIF(((Param_0 != null) AndAlso (Param_0.items != null)), Param_0.items.FirstOrDefault(), null))")]
[InlineData("np(items.FirstOrDefault(it != \"x\"))", "Select(Param_0 => IIF(((Param_0 != null) AndAlso (Param_0.items != null)), Param_0.items.FirstOrDefault(Param_1 => (Param_1 != \"x\")), null))")]
public void ExpressionTests_NullPropagating(string test, string query)
{
// Arrange
Expand Down Expand Up @@ -1378,7 +1380,8 @@ public void ExpressionTests_NullPropagating(string test, string query)
Id = 100,
GuidNormal = Guid.NewGuid(),
GuidNull = Guid.NewGuid()
}
},
items = new [] { "a", "b" }
}
}.AsQueryable();

Expand Down