From 18bc30e0c08ad9cb513a8cedfa106cb510a97d32 Mon Sep 17 00:00:00 2001 From: jkuehner Date: Thu, 9 Nov 2017 13:53:28 +0100 Subject: [PATCH 1/4] New feature -> Configuration for Expression Parsing --- .../DynamicExpressionParser.cs | 81 ++++++++++++++++++- .../DynamicQueryableExtensions.cs | 4 +- .../ExpressionParser.cs | 53 +++++++----- src/System.Linq.Dynamic.Core/GlobalConfig.cs | 2 +- src/System.Linq.Dynamic.Core/ParsingConfig.cs | 20 +++++ 5 files changed, 134 insertions(+), 26 deletions(-) create mode 100644 src/System.Linq.Dynamic.Core/ParsingConfig.cs diff --git a/src/System.Linq.Dynamic.Core/DynamicExpressionParser.cs b/src/System.Linq.Dynamic.Core/DynamicExpressionParser.cs index 5e7f627c..75c111d2 100644 --- a/src/System.Linq.Dynamic.Core/DynamicExpressionParser.cs +++ b/src/System.Linq.Dynamic.Core/DynamicExpressionParser.cs @@ -35,7 +35,26 @@ public static LambdaExpression ParseLambda(bool createParameterCtor, [CanBeNull] { Check.NotEmpty(expression, nameof(expression)); - var parser = new ExpressionParser(new ParameterExpression[0], expression, values); + var parser = new ExpressionParser(new ParameterExpression[0], expression, values, null); + + return Expression.Lambda(parser.Parse(resultType, true)); + } + + /// + /// Parses an expression into a LambdaExpression. + /// + /// if set to true then also create a constructor for all the parameters. Note that this doesn't work for Linq-to-Database entities. + /// Type of the result. If not specified, it will be generated dynamically. + /// The expression. + /// The Configuration for the parsing. + /// An object array that contains zero or more objects which are used as replacement values. + /// The generated + [PublicAPI] + public static LambdaExpression ParseLambda(bool createParameterCtor, [CanBeNull] Type resultType, string expression, ParsingConfig parsingConfig, params object[] values) + { + Check.NotEmpty(expression, nameof(expression)); + + var parser = new ExpressionParser(new ParameterExpression[0], expression, values, parsingConfig); return Expression.Lambda(parser.Parse(resultType, true)); } @@ -54,6 +73,21 @@ public static LambdaExpression ParseLambda([NotNull] Type itType, [CanBeNull] Ty return ParseLambda(true, itType, resultType, expression, values); } + /// + /// Parses an expression into a LambdaExpression. (Also create a constructor for all the parameters. Note that this doesn't work for Linq-to-Database entities.) + /// + /// The main type from the dynamic class expression. + /// Type of the result. If not specified, it will be generated dynamically. + /// The expression. + /// The Configuration for the parsing. + /// An object array that contains zero or more objects which are used as replacement values. + /// The generated + [PublicAPI] + public static LambdaExpression ParseLambda([NotNull] Type itType, [CanBeNull] Type resultType, string expression, ParsingConfig parsingConfig, params object[] values) + { + return ParseLambda(true, itType, resultType, expression, parsingConfig, values); + } + /// /// Parses an expression into a LambdaExpression. /// @@ -72,6 +106,25 @@ public static LambdaExpression ParseLambda(bool createParameterCtor, [NotNull] T return ParseLambda(createParameterCtor, new[] { Expression.Parameter(itType, "") }, resultType, expression, values); } + /// + /// Parses an expression into a LambdaExpression. + /// + /// if set to true then also create a constructor for all the parameters. Note that this doesn't work for Linq-to-Database entities. + /// The main type from the dynamic class expression. + /// Type of the result. If not specified, it will be generated dynamically. + /// The expression. + /// The Configuration for the parsing. + /// An object array that contains zero or more objects which are used as replacement values. + /// The generated + [PublicAPI] + public static LambdaExpression ParseLambda(bool createParameterCtor, [NotNull] Type itType, [CanBeNull] Type resultType, string expression, ParsingConfig parsingConfig, params object[] values) + { + Check.NotNull(itType, nameof(itType)); + Check.NotEmpty(expression, nameof(expression)); + + return ParseLambda(createParameterCtor, new[] { Expression.Parameter(itType, "") }, resultType, expression, parsingConfig, values); + } + /// /// Parses an expression into a LambdaExpression. (Also create a constructor for all the parameters. Note that this doesn't work for Linq-to-Database entities.) /// @@ -102,9 +155,31 @@ public static LambdaExpression ParseLambda(bool createParameterCtor, [NotNull] P Check.Condition(parameters, p => p.Count(x => x == null) == 0, nameof(parameters)); Check.NotEmpty(expression, nameof(expression)); - var parser = new ExpressionParser(parameters, expression, values); + var parser = new ExpressionParser(parameters, expression, values, null); + + return Expression.Lambda(parser.Parse(resultType, createParameterCtor), parameters); + } + + /// + /// Parses an expression into a LambdaExpression. + /// + /// if set to true then also create a constructor for all the parameters. Note that this doesn't work for Linq-to-Database entities. + /// A array from ParameterExpressions. + /// Type of the result. If not specified, it will be generated dynamically. + /// The expression. + /// The Configuration for the parsing. + /// An object array that contains zero or more objects which are used as replacement values. + /// The generated + [PublicAPI] + public static LambdaExpression ParseLambda(bool createParameterCtor, [NotNull] ParameterExpression[] parameters, [CanBeNull] Type resultType, string expression, ParsingConfig parsingConfig, params object[] values) + { + Check.NotNull(parameters, nameof(parameters)); + Check.Condition(parameters, p => p.Count(x => x == null) == 0, nameof(parameters)); + Check.NotEmpty(expression, nameof(expression)); + + var parser = new ExpressionParser(parameters, expression, values, parsingConfig); return Expression.Lambda(parser.Parse(resultType, createParameterCtor), parameters); } } -} \ No newline at end of file +} diff --git a/src/System.Linq.Dynamic.Core/DynamicQueryableExtensions.cs b/src/System.Linq.Dynamic.Core/DynamicQueryableExtensions.cs index da0bbb22..08279849 100644 --- a/src/System.Linq.Dynamic.Core/DynamicQueryableExtensions.cs +++ b/src/System.Linq.Dynamic.Core/DynamicQueryableExtensions.cs @@ -861,7 +861,7 @@ public static IOrderedQueryable OrderBy([NotNull] this IQueryable source, [NotNu Check.NotEmpty(ordering, nameof(ordering)); ParameterExpression[] parameters = { Expression.Parameter(source.ElementType, "") }; - ExpressionParser parser = new ExpressionParser(parameters, ordering, args); + ExpressionParser parser = new ExpressionParser(parameters, ordering, args, null); IList dynamicOrderings = parser.ParseOrdering(); Expression queryExpr = source.Expression; @@ -1561,7 +1561,7 @@ public static IOrderedQueryable ThenBy([NotNull] this IOrderedQueryable source, Check.NotEmpty(ordering, nameof(ordering)); ParameterExpression[] parameters = { Expression.Parameter(source.ElementType, "") }; - ExpressionParser parser = new ExpressionParser(parameters, ordering, args); + ExpressionParser parser = new ExpressionParser(parameters, ordering, args, null); IList dynamicOrderings = parser.ParseOrdering(forceThenBy: true); Expression queryExpr = source.Expression; diff --git a/src/System.Linq.Dynamic.Core/ExpressionParser.cs b/src/System.Linq.Dynamic.Core/ExpressionParser.cs index 887ffb62..72e6ff05 100644 --- a/src/System.Linq.Dynamic.Core/ExpressionParser.cs +++ b/src/System.Linq.Dynamic.Core/ExpressionParser.cs @@ -351,7 +351,10 @@ private static void UpdatePredefinedTypes(string typeName, int x) } private readonly TextParser _textParser; - public ExpressionParser(ParameterExpression[] parameters, string expression, object[] values) + private readonly ParsingConfig _parsingConfig; + + + public ExpressionParser(ParameterExpression[] parameters, string expression, object[] values, ParsingConfig parsingConfig) { if (_keywords == null) _keywords = CreateKeywords(); @@ -1405,29 +1408,39 @@ private Expression CreateNewExpression(List properties, List); - ConstructorInfo constructorForKeyValuePair = typeForKeyValuePair.GetTypeInfo().DeclaredConstructors.First(); - - var arrayIndexParams = new List(); - for (int i = 0; i < expressions.Count; i++) +#if !UAP10_0 + if ((_parsingConfig != null && _parsingConfig.UseDynamicObjectClassForAnonymousTypes) || (_parsingConfig == null && GlobalConfig.UseDynamicObjectClassForAnonymousTypes)) { - // Just convert the expression always to an object expression. - UnaryExpression boxingExpression = Expression.Convert(expressions[i], typeof(object)); - NewExpression parameter = Expression.New(constructorForKeyValuePair, new[] { (Expression)Expression.Constant(properties[i].Name), boxingExpression }); +#endif + type = typeof(DynamicClass); + Type typeForKeyValuePair = typeof(KeyValuePair); + ConstructorInfo constructorForKeyValuePair = + typeForKeyValuePair.GetTypeInfo().DeclaredConstructors.First(); - arrayIndexParams.Add(parameter); - } + var arrayIndexParams = new List(); + for (int i = 0; i < expressions.Count; i++) + { + // Just convert the expression always to an object expression. + UnaryExpression boxingExpression = Expression.Convert(expressions[i], typeof(object)); + NewExpression parameter = Expression.New(constructorForKeyValuePair, + new[] {(Expression) Expression.Constant(properties[i].Name), boxingExpression}); - // Create an expression tree that represents creating and initializing a one-dimensional array of type KeyValuePair. - NewArrayExpression newArrayExpression = Expression.NewArrayInit(typeof(KeyValuePair), arrayIndexParams); + arrayIndexParams.Add(parameter); + } - // Get the "public DynamicClass(KeyValuePair[] propertylist)" constructor - ConstructorInfo constructor = type.GetTypeInfo().DeclaredConstructors.First(); - return Expression.New(constructor, newArrayExpression); -#else - type = DynamicClassFactory.CreateType(properties, _createParameterCtor); + // Create an expression tree that represents creating and initializing a one-dimensional array of type KeyValuePair. + NewArrayExpression newArrayExpression = + Expression.NewArrayInit(typeof(KeyValuePair), arrayIndexParams); + + // Get the "public DynamicClass(KeyValuePair[] propertylist)" constructor + ConstructorInfo constructor = type.GetTypeInfo().DeclaredConstructors.First(); + return Expression.New(constructor, newArrayExpression); +#if !UAP10_0 + } + else + { + type = DynamicClassFactory.CreateType(properties, _createParameterCtor); + } #endif } diff --git a/src/System.Linq.Dynamic.Core/GlobalConfig.cs b/src/System.Linq.Dynamic.Core/GlobalConfig.cs index db97e167..64a895f9 100644 --- a/src/System.Linq.Dynamic.Core/GlobalConfig.cs +++ b/src/System.Linq.Dynamic.Core/GlobalConfig.cs @@ -63,4 +63,4 @@ public static bool AreContextKeywordsEnabled /// public static bool UseDynamicObjectClassForAnonymousTypes { get; set; } } -} \ No newline at end of file +} diff --git a/src/System.Linq.Dynamic.Core/ParsingConfig.cs b/src/System.Linq.Dynamic.Core/ParsingConfig.cs new file mode 100644 index 00000000..634ef9df --- /dev/null +++ b/src/System.Linq.Dynamic.Core/ParsingConfig.cs @@ -0,0 +1,20 @@ +using System.Linq.Dynamic.Core.CustomTypeProviders; + +namespace System.Linq.Dynamic.Core +{ + /// + /// Configuration class for Dynamic Linq. + /// + public class ParsingConfig + { + internal static ParsingConfig Default { get; } = new ParsingConfig(); + + /// + /// Gets or sets a value indicating whether to use dynamic object class for anonymous types. + /// + /// + /// true if wether to use dynamic object class for anonymous types; otherwise, false. + /// + public bool UseDynamicObjectClassForAnonymousTypes { get; set; } + } +} From 9585b9f2617267108c9be06fb26addeb50c1f3f4 Mon Sep 17 00:00:00 2001 From: jkuehner Date: Thu, 9 Nov 2017 22:14:36 +0100 Subject: [PATCH 2/4] Support of Initializing Named Types with new --- .../ExpressionParser.cs | 26 ++++++++- .../DynamicExpressionParserTests.cs | 56 ++++++++++++++++++- 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/System.Linq.Dynamic.Core/ExpressionParser.cs b/src/System.Linq.Dynamic.Core/ExpressionParser.cs index 72e6ff05..ea8cfe0a 100644 --- a/src/System.Linq.Dynamic.Core/ExpressionParser.cs +++ b/src/System.Linq.Dynamic.Core/ExpressionParser.cs @@ -1314,10 +1314,22 @@ Expression ParseNew() if (_textParser.CurrentToken.Id == TokenId.Identifier) { var newTypeName = _textParser.CurrentToken.Text; + _textParser.NextToken(); + + while (_textParser.CurrentToken.Id == TokenId.Dot || _textParser.CurrentToken.Id == TokenId.Plus) + { + var sep = _textParser.CurrentToken.Text; + _textParser.NextToken(); + if (_textParser.CurrentToken.Id != TokenId.Identifier) + throw ParseError(Res.IdentifierExpected); + newTypeName += sep + _textParser.CurrentToken.Text; + _textParser.NextToken(); + } + newType = FindType(newTypeName); if (newType == null) throw ParseError(_textParser.CurrentToken.Pos, Res.TypeNotFound, newTypeName); - _textParser.NextToken(); + if (_textParser.CurrentToken.Id != TokenId.OpenParen && _textParser.CurrentToken.Id != TokenId.OpenBracket && _textParser.CurrentToken.Id != TokenId.OpenCurlyParen) @@ -1414,9 +1426,13 @@ private Expression CreateNewExpression(List properties, List); +#if NET35 || NET40 + ConstructorInfo constructorForKeyValuePair = + typeForKeyValuePair.GetConstructors().First(); +#else ConstructorInfo constructorForKeyValuePair = typeForKeyValuePair.GetTypeInfo().DeclaredConstructors.First(); - +#endif var arrayIndexParams = new List(); for (int i = 0; i < expressions.Count; i++) { @@ -1433,7 +1449,11 @@ private Expression CreateNewExpression(List properties, List), arrayIndexParams); // Get the "public DynamicClass(KeyValuePair[] propertylist)" constructor +#if NET35 || NET40 + ConstructorInfo constructor = type.GetConstructors().First(); +#else ConstructorInfo constructor = type.GetTypeInfo().DeclaredConstructors.First(); +#endif return Expression.New(constructor, newArrayExpression); #if !UAP10_0 } @@ -1446,7 +1466,7 @@ private Expression CreateNewExpression(List properties, List p.PropertyType).ToArray(); ConstructorInfo ctor = type.GetConstructor(propertyTypes); - if (ctor != null) + if (ctor != null && ctor.GetParameters().Length == expressions.Count) return Expression.New(ctor, expressions); MemberBinding[] bindings = new MemberBinding[properties.Count]; diff --git a/test/System.Linq.Dynamic.Core.Tests/DynamicExpressionParserTests.cs b/test/System.Linq.Dynamic.Core.Tests/DynamicExpressionParserTests.cs index 48b908b1..8e59ddbf 100644 --- a/test/System.Linq.Dynamic.Core.Tests/DynamicExpressionParserTests.cs +++ b/test/System.Linq.Dynamic.Core.Tests/DynamicExpressionParserTests.cs @@ -1,7 +1,9 @@ using NFluent; using System.Collections.Generic; +using System.Linq.Dynamic.Core.CustomTypeProviders; using System.Linq.Dynamic.Core.Exceptions; using System.Linq.Expressions; +using System.Reflection; using Xunit; using User = System.Linq.Dynamic.Core.Tests.Helpers.Models.User; @@ -23,6 +25,29 @@ private class ComplexParseLambda1Result public int TotalIncome; } + [DynamicLinqType] + public class ComplexParseLambda3Result + { + public int? Age { get; set; } + public int TotalIncome { get; set; } + } + + private class TestCustomTypeProvider : AbstractDynamicLinqCustomTypeProvider, IDynamicLinkCustomTypeProvider + { + private HashSet _customTypes; + + public virtual HashSet GetCustomTypes() + { + if (_customTypes != null) + return _customTypes; + + _customTypes = + new HashSet( + FindTypesMarkedWithDynamicLinqTypeAttribute(new[] {this.GetType().GetTypeInfo().Assembly})); + return _customTypes; + } + } + [Fact] public void ParseLambda_ToList() { @@ -70,7 +95,6 @@ public void ParseLambda_Complex_1() Check.That(result.ToArray()[0]).Equals(expected[0]); } - [Fact] public void ParseLambda_Complex_2() { @@ -92,6 +116,36 @@ public void ParseLambda_Complex_2() Check.That(result.ToArray()[0]).Equals(expected[0]); } + [Fact] + public void ParseLambda_Complex_3() + { + GlobalConfig.CustomTypeProvider = new TestCustomTypeProvider(); + + // Arrange + var testList = User.GenerateSampleModels(51); + var qry = testList.AsQueryable(); + + var externals = new Dictionary + { + { "Users", qry } + }; + + // Act + string query = "Users.GroupBy(x => new { x.Profile.Age }).OrderBy(gg => gg.Key.Age).Select(j => new System.Linq.Dynamic.Core.Tests.DynamicExpressionParserTests+ComplexParseLambda3Result{j.Key.Age, j.Sum(k => k.Income) As TotalIncome})"; + LambdaExpression expression = DynamicExpressionParser.ParseLambda(null, query, externals); + Delegate del = expression.Compile(); + IEnumerable result = del.DynamicInvoke() as IEnumerable; + + var expected = qry.GroupBy(x => new { x.Profile.Age }).OrderBy(gg => gg.Key.Age).Select(j => new ComplexParseLambda3Result { Age = j.Key.Age, TotalIncome = j.Sum(k => k.Income) }).Cast().ToArray(); + + // Assert + Check.That(result).IsNotNull(); + Check.That(result).HasSize(expected.Length); + Check.That(result.ToArray()[0]).Equals(expected[0]); + + GlobalConfig.CustomTypeProvider = null; + } + [Fact] public void ParseLambda_Select_1() { From aebdd1b0fef3b084638943ae3b0dd89f49ae446a Mon Sep 17 00:00:00 2001 From: jkuehner Date: Tue, 14 Nov 2017 13:40:44 +0100 Subject: [PATCH 3/4] Better support of new with anonymous types, so it looks more like when you use a real expression --- .../ExpressionParser.cs | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/src/System.Linq.Dynamic.Core/ExpressionParser.cs b/src/System.Linq.Dynamic.Core/ExpressionParser.cs index ea8cfe0a..5b697129 100644 --- a/src/System.Linq.Dynamic.Core/ExpressionParser.cs +++ b/src/System.Linq.Dynamic.Core/ExpressionParser.cs @@ -1464,10 +1464,23 @@ private Expression CreateNewExpression(List properties, List p.PropertyType).ToArray(); + IEnumerable propertyInfos = type.GetProperties(); +#if !UAP10_0 && !NET35 && !NETSTANDARD1_3 + if (type.BaseType == typeof(DynamicClass)) + { + propertyInfos = propertyInfos.Where(x => x.Name != "Item"); + } +#elif NETSTANDARD + if (type.GetTypeInfo().BaseType == typeof(DynamicClass)) + { + propertyInfos = propertyInfos.Where(x => x.Name != "Item"); + } +#endif + + Type[] propertyTypes = propertyInfos.Select(p => p.PropertyType).ToArray(); ConstructorInfo ctor = type.GetConstructor(propertyTypes); if (ctor != null && ctor.GetParameters().Length == expressions.Count) - return Expression.New(ctor, expressions); + return Expression.New(ctor, expressions, (IEnumerable) propertyInfos); MemberBinding[] bindings = new MemberBinding[properties.Count]; for (int i = 0; i < bindings.Length; i++) From 84461ef2e6074ccbd3afc0aa56401858122a1652 Mon Sep 17 00:00:00 2001 From: Stef Heyenrath Date: Mon, 27 Nov 2017 21:47:43 +0100 Subject: [PATCH 4/4] reformat code (CreateNewExpression method) --- .../ExpressionParser.cs | 36 ++++++++----------- src/System.Linq.Dynamic.Core/ParsingConfig.cs | 4 +-- 2 files changed, 15 insertions(+), 25 deletions(-) diff --git a/src/System.Linq.Dynamic.Core/ExpressionParser.cs b/src/System.Linq.Dynamic.Core/ExpressionParser.cs index 5b697129..3038c0da 100644 --- a/src/System.Linq.Dynamic.Core/ExpressionParser.cs +++ b/src/System.Linq.Dynamic.Core/ExpressionParser.cs @@ -1329,7 +1329,7 @@ Expression ParseNew() newType = FindType(newTypeName); if (newType == null) throw ParseError(_textParser.CurrentToken.Pos, Res.TypeNotFound, newTypeName); - + if (_textParser.CurrentToken.Id != TokenId.OpenParen && _textParser.CurrentToken.Id != TokenId.OpenBracket && _textParser.CurrentToken.Id != TokenId.OpenCurlyParen) @@ -1421,32 +1421,29 @@ private Expression CreateNewExpression(List properties, List); #if NET35 || NET40 - ConstructorInfo constructorForKeyValuePair = - typeForKeyValuePair.GetConstructors().First(); + ConstructorInfo constructorForKeyValuePair = typeForKeyValuePair.GetConstructors().First(); #else - ConstructorInfo constructorForKeyValuePair = - typeForKeyValuePair.GetTypeInfo().DeclaredConstructors.First(); + ConstructorInfo constructorForKeyValuePair = typeForKeyValuePair.GetTypeInfo().DeclaredConstructors.First(); #endif var arrayIndexParams = new List(); for (int i = 0; i < expressions.Count; i++) { // Just convert the expression always to an object expression. UnaryExpression boxingExpression = Expression.Convert(expressions[i], typeof(object)); - NewExpression parameter = Expression.New(constructorForKeyValuePair, - new[] {(Expression) Expression.Constant(properties[i].Name), boxingExpression}); + NewExpression parameter = Expression.New(constructorForKeyValuePair, (Expression)Expression.Constant(properties[i].Name), boxingExpression); arrayIndexParams.Add(parameter); } // Create an expression tree that represents creating and initializing a one-dimensional array of type KeyValuePair. - NewArrayExpression newArrayExpression = - Expression.NewArrayInit(typeof(KeyValuePair), arrayIndexParams); + NewArrayExpression newArrayExpression = Expression.NewArrayInit(typeof(KeyValuePair), arrayIndexParams); // Get the "public DynamicClass(KeyValuePair[] propertylist)" constructor #if NET35 || NET40 @@ -1457,34 +1454,29 @@ private Expression CreateNewExpression(List properties, List propertyInfos = type.GetProperties(); -#if !UAP10_0 && !NET35 && !NETSTANDARD1_3 - if (type.BaseType == typeof(DynamicClass)) - { - propertyInfos = propertyInfos.Where(x => x.Name != "Item"); - } -#elif NETSTANDARD if (type.GetTypeInfo().BaseType == typeof(DynamicClass)) { propertyInfos = propertyInfos.Where(x => x.Name != "Item"); } -#endif Type[] propertyTypes = propertyInfos.Select(p => p.PropertyType).ToArray(); ConstructorInfo ctor = type.GetConstructor(propertyTypes); if (ctor != null && ctor.GetParameters().Length == expressions.Count) - return Expression.New(ctor, expressions, (IEnumerable) propertyInfos); + { + return Expression.New(ctor, expressions, (IEnumerable)propertyInfos); + } MemberBinding[] bindings = new MemberBinding[properties.Count]; for (int i = 0; i < bindings.Length; i++) + { bindings[i] = Expression.Bind(type.GetProperty(properties[i].Name), expressions[i]); + } return Expression.MemberInit(Expression.New(type), bindings); } diff --git a/src/System.Linq.Dynamic.Core/ParsingConfig.cs b/src/System.Linq.Dynamic.Core/ParsingConfig.cs index 634ef9df..255dfc14 100644 --- a/src/System.Linq.Dynamic.Core/ParsingConfig.cs +++ b/src/System.Linq.Dynamic.Core/ParsingConfig.cs @@ -1,6 +1,4 @@ -using System.Linq.Dynamic.Core.CustomTypeProviders; - -namespace System.Linq.Dynamic.Core +namespace System.Linq.Dynamic.Core { /// /// Configuration class for Dynamic Linq.