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..3038c0da 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();
@@ -1311,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)
@@ -1405,40 +1420,63 @@ 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);
+#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++)
+ {
+ // Just convert the expression always to an object expression.
+ UnaryExpression boxingExpression = Expression.Convert(expressions[i], typeof(object));
+ NewExpression parameter = Expression.New(constructorForKeyValuePair, (Expression)Expression.Constant(properties[i].Name), boxingExpression);
- arrayIndexParams.Add(parameter);
- }
+ 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);
+ // 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);
+ // 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
+ }
+
type = DynamicClassFactory.CreateType(properties, _createParameterCtor);
#endif
}
- Type[] propertyTypes = type.GetProperties().Select(p => p.PropertyType).ToArray();
+ IEnumerable propertyInfos = type.GetProperties();
+ if (type.GetTypeInfo().BaseType == typeof(DynamicClass))
+ {
+ propertyInfos = propertyInfos.Where(x => x.Name != "Item");
+ }
+
+ Type[] propertyTypes = propertyInfos.Select(p => p.PropertyType).ToArray();
ConstructorInfo ctor = type.GetConstructor(propertyTypes);
- if (ctor != null)
- return Expression.New(ctor, expressions);
+ if (ctor != null && ctor.GetParameters().Length == expressions.Count)
+ {
+ 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/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..255dfc14
--- /dev/null
+++ b/src/System.Linq.Dynamic.Core/ParsingConfig.cs
@@ -0,0 +1,18 @@
+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; }
+ }
+}
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()
{