diff --git a/src/System.Linq.Dynamic.Core/DynamicExpressionParser.cs b/src/System.Linq.Dynamic.Core/DynamicExpressionParser.cs index d644aaba..c48e7a3f 100644 --- a/src/System.Linq.Dynamic.Core/DynamicExpressionParser.cs +++ b/src/System.Linq.Dynamic.Core/DynamicExpressionParser.cs @@ -68,7 +68,7 @@ public static LambdaExpression ParseLambda([CanBeNull] ParsingConfig parsingConf if (parsingConfig != null && parsingConfig.RenameParameterExpression && parameters.Length == 1) { - var renamer = new ParameterExpressionRenamer(parser.ItName); + var renamer = new ParameterExpressionRenamer(parser.LastLambdaItName); parsedExpression = renamer.Rename(parsedExpression, out ParameterExpression newParameterExpression); return Expression.Lambda(parsedExpression, new[] { newParameterExpression }); diff --git a/src/System.Linq.Dynamic.Core/Parser/ExpressionParser.cs b/src/System.Linq.Dynamic.Core/Parser/ExpressionParser.cs index 07ef88c9..a9bfbd90 100644 --- a/src/System.Linq.Dynamic.Core/Parser/ExpressionParser.cs +++ b/src/System.Linq.Dynamic.Core/Parser/ExpressionParser.cs @@ -44,6 +44,15 @@ public class ExpressionParser /// public string ItName { get; private set; } = KeywordsHelper.KEYWORD_IT; + /// + /// There was a problem when an expression contained multiple lambdas where + /// the ItName was not cleared and freed for the next lambda. This variable + /// store the ItName of the last parsed lambda. Not used internally by + /// ExpressionParser, but used to preserve compatiblity of parsingConfig.RenameParameterExpression + /// which was designed to wonly work with mono-lambda expressions + /// + public string LastLambdaItName { get; private set; } = KeywordsHelper.KEYWORD_IT; + /// /// Initializes a new instance of the class. /// @@ -1676,6 +1685,7 @@ Expression ParseMemberAccess(Type type, Expression instance) { // This might be an internal variable for use within a lambda expression, so store it as such _internals.Add(id, _it); + string _previousItName = ItName; // Also store ItName (only once) if (string.Equals(ItName, KeywordsHelper.KEYWORD_IT)) @@ -1686,7 +1696,14 @@ Expression ParseMemberAccess(Type type, Expression instance) // next _textParser.NextToken(); - return ParseConditionalOperator(); + LastLambdaItName = ItName; + var exp = ParseConditionalOperator(); + + // Restore previous context and clear internals + _internals.Remove(id); + ItName = _previousItName; + + return exp; } throw ParseError(errorPos, Res.UnknownPropertyOrField, id, TypeHelper.GetTypeName(type)); diff --git a/test/System.Linq.Dynamic.Core.Tests/DynamicExpressionParserTests.cs b/test/System.Linq.Dynamic.Core.Tests/DynamicExpressionParserTests.cs index 7cd30ac9..dee9d5c0 100644 --- a/test/System.Linq.Dynamic.Core.Tests/DynamicExpressionParserTests.cs +++ b/test/System.Linq.Dynamic.Core.Tests/DynamicExpressionParserTests.cs @@ -1,10 +1,10 @@ -using System.Collections.Generic; +using NFluent; +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.Expressions; using System.Reflection; -using NFluent; using Xunit; using User = System.Linq.Dynamic.Core.Tests.Helpers.Models.User; @@ -256,7 +256,7 @@ public void DynamicExpressionParser_ParseLambda_WithStructWithEquality(string qu var qry = testList.AsQueryable(); // Act - ulong expectedX = (ulong) long.MaxValue + 3; + ulong expectedX = (ulong)long.MaxValue + 3; query = string.Format(query, expectedX); LambdaExpression expression = DynamicExpressionParser.ParseLambda(qry.GetType(), null, query); @@ -613,6 +613,40 @@ public void DynamicExpressionParser_ParseLambda_StringLiteralEmbeddedQuote_Retur Assert.Equal(expectedRightValue, rightValue); } + /// + /// @see https://github.com/StefH/System.Linq.Dynamic.Core/issues/294 + /// + [Fact] + public void DynamicExpressionParser_ParseLambda_MultipleLambdas() + { + var users = new[] + { + new { name = "Juan", age = 25 }, + new { name = "Juan", age = 25 }, + new { name = "David", age = 12 }, + new { name = "Juan", age = 25 }, + new { name = "Juan", age = 4 }, + new { name = "Pedro", age = 2 }, + new { name = "Juan", age = 25 } + }.ToList(); + + IQueryable query; + + // One lambda + string res1 = "[{\"Key\":{\"name\":\"Juan\"},\"nativeAggregates\":{\"ageSum\":104},\"Grouping\":[{\"name\":\"Juan\",\"age\":25},{\"name\":\"Juan\",\"age\":25},{\"name\":\"Juan\",\"age\":25},{\"name\":\"Juan\",\"age\":4},{\"name\":\"Juan\",\"age\":25}]},{\"Key\":{\"name\":\"David\"},\"nativeAggregates\":{\"ageSum\":12},\"Grouping\":[{\"name\":\"David\",\"age\":12}]},{\"Key\":{\"name\":\"Pedro\"},\"nativeAggregates\":{\"ageSum\":2},\"Grouping\":[{\"name\":\"Pedro\",\"age\":2}]}]"; + query = users.AsQueryable(); + query = query.GroupBy("new(name as name)", "it"); + query = query.Select("new (it.Key as Key, new(it.Sum(x => x.age) as ageSum) as nativeAggregates, it as Grouping)"); + Assert.Equal(res1, Newtonsoft.Json.JsonConvert.SerializeObject(query)); + + // Multiple lambdas + string res2 = "[{\"Key\":{\"name\":\"Juan\"},\"nativeAggregates\":{\"ageSum\":0,\"ageSum2\":104},\"Grouping\":[{\"name\":\"Juan\",\"age\":25},{\"name\":\"Juan\",\"age\":25},{\"name\":\"Juan\",\"age\":25},{\"name\":\"Juan\",\"age\":4},{\"name\":\"Juan\",\"age\":25}]},{\"Key\":{\"name\":\"David\"},\"nativeAggregates\":{\"ageSum\":0,\"ageSum2\":12},\"Grouping\":[{\"name\":\"David\",\"age\":12}]},{\"Key\":{\"name\":\"Pedro\"},\"nativeAggregates\":{\"ageSum\":0,\"ageSum2\":2},\"Grouping\":[{\"name\":\"Pedro\",\"age\":2}]}]"; + query = users.AsQueryable(); + query = query.GroupBy("new(name as name)", "it"); + query = query.Select("new (it.Key as Key, new(it.Sum(x => x.age > 25 ? 1 : 0) as ageSum, it.Sum(x => x.age) as ageSum2) as nativeAggregates, it as Grouping)"); + Assert.Equal(res2, Newtonsoft.Json.JsonConvert.SerializeObject(query)); + } + [Fact] public void DynamicExpressionParser_ParseLambda_StringLiteralStartEmbeddedQuote_ReturnsBooleanLambdaExpression() {