Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

New features #117

Merged
merged 4 commits into from
Nov 28, 2017
Merged
Show file tree
Hide file tree
Changes from 3 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
81 changes: 78 additions & 3 deletions src/System.Linq.Dynamic.Core/DynamicExpressionParser.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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));
}

/// <summary>
/// Parses an expression into a LambdaExpression.
/// </summary>
/// <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>
/// <param name="resultType">Type of the result. If not specified, it will be generated dynamically.</param>
/// <param name="expression">The expression.</param>
/// <param name="parsingConfig">The Configuration for the parsing.</param>
/// <param name="values">An object array that contains zero or more objects which are used as replacement values.</param>
/// <returns>The generated <see cref="LambdaExpression"/></returns>
[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));
}
Expand All @@ -54,6 +73,21 @@ public static LambdaExpression ParseLambda([NotNull] Type itType, [CanBeNull] Ty
return ParseLambda(true, itType, resultType, expression, values);
}

/// <summary>
/// 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.)
/// </summary>
/// <param name="itType">The main type from the dynamic class expression.</param>
/// <param name="resultType">Type of the result. If not specified, it will be generated dynamically.</param>
/// <param name="expression">The expression.</param>
/// <param name="parsingConfig">The Configuration for the parsing.</param>
/// <param name="values">An object array that contains zero or more objects which are used as replacement values.</param>
/// <returns>The generated <see cref="LambdaExpression"/></returns>
[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);
}

/// <summary>
/// Parses an expression into a LambdaExpression.
/// </summary>
Expand All @@ -72,6 +106,25 @@ public static LambdaExpression ParseLambda(bool createParameterCtor, [NotNull] T
return ParseLambda(createParameterCtor, new[] { Expression.Parameter(itType, "") }, resultType, expression, values);
}

/// <summary>
/// Parses an expression into a LambdaExpression.
/// </summary>
/// <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>
/// <param name="itType">The main type from the dynamic class expression.</param>
/// <param name="resultType">Type of the result. If not specified, it will be generated dynamically.</param>
/// <param name="expression">The expression.</param>
/// <param name="parsingConfig">The Configuration for the parsing.</param>
/// <param name="values">An object array that contains zero or more objects which are used as replacement values.</param>
/// <returns>The generated <see cref="LambdaExpression"/></returns>
[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);
}

/// <summary>
/// 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.)
/// </summary>
Expand Down Expand Up @@ -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);
}

/// <summary>
/// Parses an expression into a LambdaExpression.
/// </summary>
/// <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>
/// <param name="parameters">A array from ParameterExpressions.</param>
/// <param name="resultType">Type of the result. If not specified, it will be generated dynamically.</param>
/// <param name="expression">The expression.</param>
/// <param name="parsingConfig">The Configuration for the parsing.</param>
/// <param name="values">An object array that contains zero or more objects which are used as replacement values.</param>
/// <returns>The generated <see cref="LambdaExpression"/></returns>
[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);
}
}
}
}
4 changes: 2 additions & 2 deletions src/System.Linq.Dynamic.Core/DynamicQueryableExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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<DynamicOrdering> dynamicOrderings = parser.ParseOrdering();

Expression queryExpr = source.Expression;
Expand Down Expand Up @@ -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<DynamicOrdering> dynamicOrderings = parser.ParseOrdering(forceThenBy: true);

Expression queryExpr = source.Expression;
Expand Down
92 changes: 69 additions & 23 deletions src/System.Linq.Dynamic.Core/ExpressionParser.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -1405,36 +1420,67 @@ private Expression CreateNewExpression(List<DynamicProperty> properties, List<Ex

if (type == null)
{
#if UAP10_0
type = typeof(DynamicClass);
Type typeForKeyValuePair = typeof(KeyValuePair<string, object>);
ConstructorInfo constructorForKeyValuePair = typeForKeyValuePair.GetTypeInfo().DeclaredConstructors.First();

var arrayIndexParams = new List<Expression>();
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<string, object>);
#if NET35 || NET40
ConstructorInfo constructorForKeyValuePair =
typeForKeyValuePair.GetConstructors().First();
#else
ConstructorInfo constructorForKeyValuePair =
typeForKeyValuePair.GetTypeInfo().DeclaredConstructors.First();
#endif
var arrayIndexParams = new List<Expression>();
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});

arrayIndexParams.Add(parameter);
}
arrayIndexParams.Add(parameter);
}

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

// Get the "public DynamicClass(KeyValuePair<string, object>[] propertylist)" constructor
ConstructorInfo constructor = type.GetTypeInfo().DeclaredConstructors.First();
return Expression.New(constructor, newArrayExpression);
// Get the "public DynamicClass(KeyValuePair<string, object>[] propertylist)" constructor
#if NET35 || NET40
ConstructorInfo constructor = type.GetConstructors().First();
#else
type = DynamicClassFactory.CreateType(properties, _createParameterCtor);
ConstructorInfo constructor = type.GetTypeInfo().DeclaredConstructors.First();
#endif
return Expression.New(constructor, newArrayExpression);
#if !UAP10_0
}
else
{
type = DynamicClassFactory.CreateType(properties, _createParameterCtor);
}
#endif
}

Type[] propertyTypes = type.GetProperties().Select(p => p.PropertyType).ToArray();
IEnumerable<PropertyInfo> 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))
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think you can use type.GetTypeInfo().BaseType for all frameworks, so no need for an if-elif structure.

{
propertyInfos = propertyInfos.Where(x => x.Name != "Item");
}
#endif

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<MemberInfo>) propertyInfos);

MemberBinding[] bindings = new MemberBinding[properties.Count];
for (int i = 0; i < bindings.Length; i++)
Expand Down
2 changes: 1 addition & 1 deletion src/System.Linq.Dynamic.Core/GlobalConfig.cs
Original file line number Diff line number Diff line change
Expand Up @@ -63,4 +63,4 @@ public static bool AreContextKeywordsEnabled
/// </value>
public static bool UseDynamicObjectClassForAnonymousTypes { get; set; }
}
}
}
20 changes: 20 additions & 0 deletions src/System.Linq.Dynamic.Core/ParsingConfig.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
using System.Linq.Dynamic.Core.CustomTypeProviders;

namespace System.Linq.Dynamic.Core
{
/// <summary>
/// Configuration class for Dynamic Linq.
/// </summary>
public class ParsingConfig
{
internal static ParsingConfig Default { get; } = new ParsingConfig();

/// <summary>
/// Gets or sets a value indicating whether to use dynamic object class for anonymous types.
/// </summary>
/// <value>
/// <c>true</c> if wether to use dynamic object class for anonymous types; otherwise, <c>false</c>.
/// </value>
public bool UseDynamicObjectClassForAnonymousTypes { get; set; }
}
}
Original file line number Diff line number Diff line change
@@ -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;

Expand All @@ -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<Type> _customTypes;

public virtual HashSet<Type> GetCustomTypes()
{
if (_customTypes != null)
return _customTypes;

_customTypes =
new HashSet<Type>(
FindTypesMarkedWithDynamicLinqTypeAttribute(new[] {this.GetType().GetTypeInfo().Assembly}));
return _customTypes;
}
}

[Fact]
public void ParseLambda_ToList()
{
Expand Down Expand Up @@ -70,7 +95,6 @@ public void ParseLambda_Complex_1()
Check.That(result.ToArray()[0]).Equals(expected[0]);
}


[Fact]
public void ParseLambda_Complex_2()
{
Expand All @@ -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<string, object>
{
{ "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<dynamic> result = del.DynamicInvoke() as IEnumerable<dynamic>;

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();

// 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()
{
Expand Down