Skip to content

Commit 10bb5b5

Browse files
jogibear9988StefH
authored andcommitted
New features (#117)
* New feature -> Configuration for Expression Parsing * Support of Initializing Named Types with new * Better support of new with anonymous types, so it looks more like when you use a real expression * reformat code (CreateNewExpression method)
1 parent 103c9bb commit 10bb5b5

File tree

5 files changed

+213
-28
lines changed

5 files changed

+213
-28
lines changed

src/System.Linq.Dynamic.Core/DynamicExpressionParser.cs

+78-3
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,26 @@ public static LambdaExpression ParseLambda(bool createParameterCtor, [CanBeNull]
3535
{
3636
Check.NotEmpty(expression, nameof(expression));
3737

38-
var parser = new ExpressionParser(new ParameterExpression[0], expression, values);
38+
var parser = new ExpressionParser(new ParameterExpression[0], expression, values, null);
39+
40+
return Expression.Lambda(parser.Parse(resultType, true));
41+
}
42+
43+
/// <summary>
44+
/// Parses an expression into a LambdaExpression.
45+
/// </summary>
46+
/// <param name="createParameterCtor">if set to <c>true</c> then also create a constructor for all the parameters. Note that this doesn't work for Linq-to-Database entities.</param>
47+
/// <param name="resultType">Type of the result. If not specified, it will be generated dynamically.</param>
48+
/// <param name="expression">The expression.</param>
49+
/// <param name="parsingConfig">The Configuration for the parsing.</param>
50+
/// <param name="values">An object array that contains zero or more objects which are used as replacement values.</param>
51+
/// <returns>The generated <see cref="LambdaExpression"/></returns>
52+
[PublicAPI]
53+
public static LambdaExpression ParseLambda(bool createParameterCtor, [CanBeNull] Type resultType, string expression, ParsingConfig parsingConfig, params object[] values)
54+
{
55+
Check.NotEmpty(expression, nameof(expression));
56+
57+
var parser = new ExpressionParser(new ParameterExpression[0], expression, values, parsingConfig);
3958

4059
return Expression.Lambda(parser.Parse(resultType, true));
4160
}
@@ -54,6 +73,21 @@ public static LambdaExpression ParseLambda([NotNull] Type itType, [CanBeNull] Ty
5473
return ParseLambda(true, itType, resultType, expression, values);
5574
}
5675

76+
/// <summary>
77+
/// 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.)
78+
/// </summary>
79+
/// <param name="itType">The main type from the dynamic class expression.</param>
80+
/// <param name="resultType">Type of the result. If not specified, it will be generated dynamically.</param>
81+
/// <param name="expression">The expression.</param>
82+
/// <param name="parsingConfig">The Configuration for the parsing.</param>
83+
/// <param name="values">An object array that contains zero or more objects which are used as replacement values.</param>
84+
/// <returns>The generated <see cref="LambdaExpression"/></returns>
85+
[PublicAPI]
86+
public static LambdaExpression ParseLambda([NotNull] Type itType, [CanBeNull] Type resultType, string expression, ParsingConfig parsingConfig, params object[] values)
87+
{
88+
return ParseLambda(true, itType, resultType, expression, parsingConfig, values);
89+
}
90+
5791
/// <summary>
5892
/// Parses an expression into a LambdaExpression.
5993
/// </summary>
@@ -72,6 +106,25 @@ public static LambdaExpression ParseLambda(bool createParameterCtor, [NotNull] T
72106
return ParseLambda(createParameterCtor, new[] { Expression.Parameter(itType, "") }, resultType, expression, values);
73107
}
74108

109+
/// <summary>
110+
/// Parses an expression into a LambdaExpression.
111+
/// </summary>
112+
/// <param name="createParameterCtor">if set to <c>true</c> then also create a constructor for all the parameters. Note that this doesn't work for Linq-to-Database entities.</param>
113+
/// <param name="itType">The main type from the dynamic class expression.</param>
114+
/// <param name="resultType">Type of the result. If not specified, it will be generated dynamically.</param>
115+
/// <param name="expression">The expression.</param>
116+
/// <param name="parsingConfig">The Configuration for the parsing.</param>
117+
/// <param name="values">An object array that contains zero or more objects which are used as replacement values.</param>
118+
/// <returns>The generated <see cref="LambdaExpression"/></returns>
119+
[PublicAPI]
120+
public static LambdaExpression ParseLambda(bool createParameterCtor, [NotNull] Type itType, [CanBeNull] Type resultType, string expression, ParsingConfig parsingConfig, params object[] values)
121+
{
122+
Check.NotNull(itType, nameof(itType));
123+
Check.NotEmpty(expression, nameof(expression));
124+
125+
return ParseLambda(createParameterCtor, new[] { Expression.Parameter(itType, "") }, resultType, expression, parsingConfig, values);
126+
}
127+
75128
/// <summary>
76129
/// 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.)
77130
/// </summary>
@@ -102,9 +155,31 @@ public static LambdaExpression ParseLambda(bool createParameterCtor, [NotNull] P
102155
Check.Condition(parameters, p => p.Count(x => x == null) == 0, nameof(parameters));
103156
Check.NotEmpty(expression, nameof(expression));
104157

105-
var parser = new ExpressionParser(parameters, expression, values);
158+
var parser = new ExpressionParser(parameters, expression, values, null);
159+
160+
return Expression.Lambda(parser.Parse(resultType, createParameterCtor), parameters);
161+
}
162+
163+
/// <summary>
164+
/// Parses an expression into a LambdaExpression.
165+
/// </summary>
166+
/// <param name="createParameterCtor">if set to <c>true</c> then also create a constructor for all the parameters. Note that this doesn't work for Linq-to-Database entities.</param>
167+
/// <param name="parameters">A array from ParameterExpressions.</param>
168+
/// <param name="resultType">Type of the result. If not specified, it will be generated dynamically.</param>
169+
/// <param name="expression">The expression.</param>
170+
/// <param name="parsingConfig">The Configuration for the parsing.</param>
171+
/// <param name="values">An object array that contains zero or more objects which are used as replacement values.</param>
172+
/// <returns>The generated <see cref="LambdaExpression"/></returns>
173+
[PublicAPI]
174+
public static LambdaExpression ParseLambda(bool createParameterCtor, [NotNull] ParameterExpression[] parameters, [CanBeNull] Type resultType, string expression, ParsingConfig parsingConfig, params object[] values)
175+
{
176+
Check.NotNull(parameters, nameof(parameters));
177+
Check.Condition(parameters, p => p.Count(x => x == null) == 0, nameof(parameters));
178+
Check.NotEmpty(expression, nameof(expression));
179+
180+
var parser = new ExpressionParser(parameters, expression, values, parsingConfig);
106181

107182
return Expression.Lambda(parser.Parse(resultType, createParameterCtor), parameters);
108183
}
109184
}
110-
}
185+
}

src/System.Linq.Dynamic.Core/DynamicQueryableExtensions.cs

+2-2
Original file line numberDiff line numberDiff line change
@@ -861,7 +861,7 @@ public static IOrderedQueryable OrderBy([NotNull] this IQueryable source, [NotNu
861861
Check.NotEmpty(ordering, nameof(ordering));
862862

863863
ParameterExpression[] parameters = { Expression.Parameter(source.ElementType, "") };
864-
ExpressionParser parser = new ExpressionParser(parameters, ordering, args);
864+
ExpressionParser parser = new ExpressionParser(parameters, ordering, args, null);
865865
IList<DynamicOrdering> dynamicOrderings = parser.ParseOrdering();
866866

867867
Expression queryExpr = source.Expression;
@@ -1561,7 +1561,7 @@ public static IOrderedQueryable ThenBy([NotNull] this IOrderedQueryable source,
15611561
Check.NotEmpty(ordering, nameof(ordering));
15621562

15631563
ParameterExpression[] parameters = { Expression.Parameter(source.ElementType, "") };
1564-
ExpressionParser parser = new ExpressionParser(parameters, ordering, args);
1564+
ExpressionParser parser = new ExpressionParser(parameters, ordering, args, null);
15651565
IList<DynamicOrdering> dynamicOrderings = parser.ParseOrdering(forceThenBy: true);
15661566

15671567
Expression queryExpr = source.Expression;

src/System.Linq.Dynamic.Core/ExpressionParser.cs

+60-22
Original file line numberDiff line numberDiff line change
@@ -357,7 +357,10 @@ private static void UpdatePredefinedTypes(string typeName, int x)
357357
}
358358
private readonly TextParser _textParser;
359359

360-
public ExpressionParser(ParameterExpression[] parameters, string expression, object[] values)
360+
private readonly ParsingConfig _parsingConfig;
361+
362+
363+
public ExpressionParser(ParameterExpression[] parameters, string expression, object[] values, ParsingConfig parsingConfig)
361364
{
362365
if (_keywords == null)
363366
_keywords = CreateKeywords();
@@ -1317,10 +1320,22 @@ Expression ParseNew()
13171320
if (_textParser.CurrentToken.Id == TokenId.Identifier)
13181321
{
13191322
var newTypeName = _textParser.CurrentToken.Text;
1323+
_textParser.NextToken();
1324+
1325+
while (_textParser.CurrentToken.Id == TokenId.Dot || _textParser.CurrentToken.Id == TokenId.Plus)
1326+
{
1327+
var sep = _textParser.CurrentToken.Text;
1328+
_textParser.NextToken();
1329+
if (_textParser.CurrentToken.Id != TokenId.Identifier)
1330+
throw ParseError(Res.IdentifierExpected);
1331+
newTypeName += sep + _textParser.CurrentToken.Text;
1332+
_textParser.NextToken();
1333+
}
1334+
13201335
newType = FindType(newTypeName);
13211336
if (newType == null)
13221337
throw ParseError(_textParser.CurrentToken.Pos, Res.TypeNotFound, newTypeName);
1323-
_textParser.NextToken();
1338+
13241339
if (_textParser.CurrentToken.Id != TokenId.OpenParen &&
13251340
_textParser.CurrentToken.Id != TokenId.OpenBracket &&
13261341
_textParser.CurrentToken.Id != TokenId.OpenCurlyParen)
@@ -1411,40 +1426,63 @@ private Expression CreateNewExpression(List<DynamicProperty> properties, List<Ex
14111426

14121427
if (type == null)
14131428
{
1414-
#if UAP10_0
1415-
type = typeof(DynamicClass);
1416-
Type typeForKeyValuePair = typeof(KeyValuePair<string, object>);
1417-
ConstructorInfo constructorForKeyValuePair = typeForKeyValuePair.GetTypeInfo().DeclaredConstructors.First();
1418-
1419-
var arrayIndexParams = new List<Expression>();
1420-
for (int i = 0; i < expressions.Count; i++)
1429+
#if !UAP10_0
1430+
if (_parsingConfig != null && _parsingConfig.UseDynamicObjectClassForAnonymousTypes ||
1431+
_parsingConfig == null && GlobalConfig.UseDynamicObjectClassForAnonymousTypes)
14211432
{
1422-
// Just convert the expression always to an object expression.
1423-
UnaryExpression boxingExpression = Expression.Convert(expressions[i], typeof(object));
1424-
NewExpression parameter = Expression.New(constructorForKeyValuePair, new[] { (Expression)Expression.Constant(properties[i].Name), boxingExpression });
1433+
#endif
1434+
type = typeof(DynamicClass);
1435+
Type typeForKeyValuePair = typeof(KeyValuePair<string, object>);
1436+
#if NET35 || NET40
1437+
ConstructorInfo constructorForKeyValuePair = typeForKeyValuePair.GetConstructors().First();
1438+
#else
1439+
ConstructorInfo constructorForKeyValuePair = typeForKeyValuePair.GetTypeInfo().DeclaredConstructors.First();
1440+
#endif
1441+
var arrayIndexParams = new List<Expression>();
1442+
for (int i = 0; i < expressions.Count; i++)
1443+
{
1444+
// Just convert the expression always to an object expression.
1445+
UnaryExpression boxingExpression = Expression.Convert(expressions[i], typeof(object));
1446+
NewExpression parameter = Expression.New(constructorForKeyValuePair, (Expression)Expression.Constant(properties[i].Name), boxingExpression);
14251447

1426-
arrayIndexParams.Add(parameter);
1427-
}
1448+
arrayIndexParams.Add(parameter);
1449+
}
14281450

1429-
// Create an expression tree that represents creating and initializing a one-dimensional array of type KeyValuePair<string, object>.
1430-
NewArrayExpression newArrayExpression = Expression.NewArrayInit(typeof(KeyValuePair<string, object>), arrayIndexParams);
1451+
// Create an expression tree that represents creating and initializing a one-dimensional array of type KeyValuePair<string, object>.
1452+
NewArrayExpression newArrayExpression = Expression.NewArrayInit(typeof(KeyValuePair<string, object>), arrayIndexParams);
14311453

1432-
// Get the "public DynamicClass(KeyValuePair<string, object>[] propertylist)" constructor
1433-
ConstructorInfo constructor = type.GetTypeInfo().DeclaredConstructors.First();
1434-
return Expression.New(constructor, newArrayExpression);
1454+
// Get the "public DynamicClass(KeyValuePair<string, object>[] propertylist)" constructor
1455+
#if NET35 || NET40
1456+
ConstructorInfo constructor = type.GetConstructors().First();
14351457
#else
1458+
ConstructorInfo constructor = type.GetTypeInfo().DeclaredConstructors.First();
1459+
#endif
1460+
return Expression.New(constructor, newArrayExpression);
1461+
#if !UAP10_0
1462+
}
1463+
14361464
type = DynamicClassFactory.CreateType(properties, _createParameterCtor);
14371465
#endif
14381466
}
14391467

1440-
Type[] propertyTypes = type.GetProperties().Select(p => p.PropertyType).ToArray();
1468+
IEnumerable<PropertyInfo> propertyInfos = type.GetProperties();
1469+
if (type.GetTypeInfo().BaseType == typeof(DynamicClass))
1470+
{
1471+
propertyInfos = propertyInfos.Where(x => x.Name != "Item");
1472+
}
1473+
1474+
Type[] propertyTypes = propertyInfos.Select(p => p.PropertyType).ToArray();
14411475
ConstructorInfo ctor = type.GetConstructor(propertyTypes);
1442-
if (ctor != null)
1443-
return Expression.New(ctor, expressions);
1476+
if (ctor != null && ctor.GetParameters().Length == expressions.Count)
1477+
{
1478+
return Expression.New(ctor, expressions, (IEnumerable<MemberInfo>)propertyInfos);
1479+
}
14441480

14451481
MemberBinding[] bindings = new MemberBinding[properties.Count];
14461482
for (int i = 0; i < bindings.Length; i++)
1483+
{
14471484
bindings[i] = Expression.Bind(type.GetProperty(properties[i].Name), expressions[i]);
1485+
}
14481486
return Expression.MemberInit(Expression.New(type), bindings);
14491487
}
14501488

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
namespace System.Linq.Dynamic.Core
2+
{
3+
/// <summary>
4+
/// Configuration class for Dynamic Linq.
5+
/// </summary>
6+
public class ParsingConfig
7+
{
8+
internal static ParsingConfig Default { get; } = new ParsingConfig();
9+
10+
/// <summary>
11+
/// Gets or sets a value indicating whether to use dynamic object class for anonymous types.
12+
/// </summary>
13+
/// <value>
14+
/// <c>true</c> if wether to use dynamic object class for anonymous types; otherwise, <c>false</c>.
15+
/// </value>
16+
public bool UseDynamicObjectClassForAnonymousTypes { get; set; }
17+
}
18+
}

test/System.Linq.Dynamic.Core.Tests/DynamicExpressionParserTests.cs

+55-1
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
using NFluent;
22
using System.Collections.Generic;
3+
using System.Linq.Dynamic.Core.CustomTypeProviders;
34
using System.Linq.Dynamic.Core.Exceptions;
45
using System.Linq.Expressions;
6+
using System.Reflection;
57
using Xunit;
68
using User = System.Linq.Dynamic.Core.Tests.Helpers.Models.User;
79

@@ -23,6 +25,29 @@ private class ComplexParseLambda1Result
2325
public int TotalIncome;
2426
}
2527

28+
[DynamicLinqType]
29+
public class ComplexParseLambda3Result
30+
{
31+
public int? Age { get; set; }
32+
public int TotalIncome { get; set; }
33+
}
34+
35+
private class TestCustomTypeProvider : AbstractDynamicLinqCustomTypeProvider, IDynamicLinkCustomTypeProvider
36+
{
37+
private HashSet<Type> _customTypes;
38+
39+
public virtual HashSet<Type> GetCustomTypes()
40+
{
41+
if (_customTypes != null)
42+
return _customTypes;
43+
44+
_customTypes =
45+
new HashSet<Type>(
46+
FindTypesMarkedWithDynamicLinqTypeAttribute(new[] {this.GetType().GetTypeInfo().Assembly}));
47+
return _customTypes;
48+
}
49+
}
50+
2651
[Fact]
2752
public void ParseLambda_ToList()
2853
{
@@ -70,7 +95,6 @@ public void ParseLambda_Complex_1()
7095
Check.That(result.ToArray()[0]).Equals(expected[0]);
7196
}
7297

73-
7498
[Fact]
7599
public void ParseLambda_Complex_2()
76100
{
@@ -92,6 +116,36 @@ public void ParseLambda_Complex_2()
92116
Check.That(result.ToArray()[0]).Equals(expected[0]);
93117
}
94118

119+
[Fact]
120+
public void ParseLambda_Complex_3()
121+
{
122+
GlobalConfig.CustomTypeProvider = new TestCustomTypeProvider();
123+
124+
// Arrange
125+
var testList = User.GenerateSampleModels(51);
126+
var qry = testList.AsQueryable();
127+
128+
var externals = new Dictionary<string, object>
129+
{
130+
{ "Users", qry }
131+
};
132+
133+
// Act
134+
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})";
135+
LambdaExpression expression = DynamicExpressionParser.ParseLambda(null, query, externals);
136+
Delegate del = expression.Compile();
137+
IEnumerable<dynamic> result = del.DynamicInvoke() as IEnumerable<dynamic>;
138+
139+
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<dynamic>().ToArray();
140+
141+
// Assert
142+
Check.That(result).IsNotNull();
143+
Check.That(result).HasSize(expected.Length);
144+
Check.That(result.ToArray()[0]).Equals(expected[0]);
145+
146+
GlobalConfig.CustomTypeProvider = null;
147+
}
148+
95149
[Fact]
96150
public void ParseLambda_Select_1()
97151
{

0 commit comments

Comments
 (0)