diff --git a/src/System.Linq.Dynamic.Core/DynamicExpressionParser.cs b/src/System.Linq.Dynamic.Core/DynamicExpressionParser.cs
index cb95d3e3..c02fd6bb 100644
--- a/src/System.Linq.Dynamic.Core/DynamicExpressionParser.cs
+++ b/src/System.Linq.Dynamic.Core/DynamicExpressionParser.cs
@@ -9,6 +9,24 @@ namespace System.Linq.Dynamic.Core
///
public static class DynamicExpressionParser
{
+ ///
+ /// Parses a expression into a LambdaExpression. (Also create a constructor for all the parameters.)
+ ///
+ /// The main type from the dynamic class expression.
+ /// Type of the result. If not specified, it will be generated dynamically.
+ /// The expression.
+ /// An object array that contains zero or more objects which are used as replacement values.
+ /// The generated
+ public static LambdaExpression ParseLambda([CanBeNull] Type resultType, [NotNull] string expression, params object[] values)
+ {
+ Check.NotEmpty(expression, nameof(expression));
+
+
+ var parser = new ExpressionParser(new ParameterExpression[0], expression, values);
+
+ return Expression.Lambda(parser.Parse(resultType, true));
+ }
+
///
/// Parses a expression into a LambdaExpression. (Also create a constructor for all the parameters.)
///
diff --git a/src/System.Linq.Dynamic.Core/ExpressionParser.cs b/src/System.Linq.Dynamic.Core/ExpressionParser.cs
index 2aa87631..a923d124 100644
--- a/src/System.Linq.Dynamic.Core/ExpressionParser.cs
+++ b/src/System.Linq.Dynamic.Core/ExpressionParser.cs
@@ -149,6 +149,7 @@ interface IEnumerableSignatures
void Take(int count);
void TakeWhile(bool predicate);
void Distinct();
+ void GroupBy(object selector);
//Executors
void Single();
@@ -222,6 +223,7 @@ interface IEnumerableSignatures
readonly Dictionary _symbols;
IDictionary _externals;
+ readonly Dictionary _internals;
readonly Dictionary _literals;
ParameterExpression _it;
ParameterExpression _parent;
@@ -269,6 +271,7 @@ public ExpressionParser(ParameterExpression[] parameters, string expression, obj
{
if (_keywords == null) _keywords = CreateKeywords();
_symbols = new Dictionary(StringComparer.OrdinalIgnoreCase);
+ _internals = new Dictionary();
_literals = new Dictionary();
if (parameters != null) ProcessParameters(parameters);
if (values != null) ProcessValues(values);
@@ -388,7 +391,7 @@ Expression ParseExpression()
// ?? (null-coalescing) operator
Expression ParseNullCoalescing()
{
- Expression expr = ParseConditionalOr();
+ Expression expr = ParseLambda();
if (_textParser.CurrentToken.Id == TokenId.NullCoalescing)
{
_textParser.NextToken();
@@ -398,6 +401,24 @@ Expression ParseNullCoalescing()
return expr;
}
+ // => operator - Added Support for projection operator
+ Expression ParseLambda()
+ {
+ Expression expr = ParseConditionalOr();
+ if (_textParser.CurrentToken.Id == TokenId.Lambda && _it.Type == expr.Type)
+ {
+ _textParser.NextToken();
+ if (_textParser.CurrentToken.Id == TokenId.Identifier ||
+ _textParser.CurrentToken.Id == TokenId.OpenParen)
+ {
+ var right = ParseExpression();
+ return Expression.Lambda(right, new[] {(ParameterExpression) expr});
+ }
+ _textParser.ValidateToken(TokenId.OpenParen, Res.OpenParenExpected);
+ }
+ return expr;
+ }
+
// isnull(a,b) operator
Expression ParseIsNull()
{
@@ -1003,7 +1024,8 @@ Expression ParseIdentifier()
}
if (_symbols.TryGetValue(_textParser.CurrentToken.Text, out value) ||
- _externals != null && _externals.TryGetValue(_textParser.CurrentToken.Text, out value))
+ _externals != null && _externals.TryGetValue(_textParser.CurrentToken.Text, out value) ||
+ _internals.TryGetValue(_textParser.CurrentToken.Text, out value))
{
Expression expr = value as Expression;
if (expr == null)
@@ -1093,7 +1115,24 @@ Expression GenerateConditional(Expression test, Expression expr1, Expression exp
Expression ParseNew()
{
_textParser.NextToken();
- _textParser.ValidateToken(TokenId.OpenParen, Res.OpenParenExpected);
+ if (_textParser.CurrentToken.Id != TokenId.OpenParen &&
+ _textParser.CurrentToken.Id != TokenId.OpenCurlyParen &&
+ _textParser.CurrentToken.Id != TokenId.Identifier)
+ throw ParseError(Res.OpenParenOrIdentifierExpected);
+
+ Type newType = null;
+ if (_textParser.CurrentToken.Id == TokenId.Identifier)
+ {
+ var newTypeName = _textParser.CurrentToken.Text;
+ 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.OpenCurlyParen)
+ throw ParseError(Res.OpenParenExpected);
+ }
+
_textParser.NextToken();
var properties = new List();
@@ -1125,16 +1164,18 @@ Expression ParseNew()
_textParser.NextToken();
}
- _textParser.ValidateToken(TokenId.CloseParen, Res.CloseParenOrCommaExpected);
+ if (_textParser.CurrentToken.Id != TokenId.CloseParen &&
+ _textParser.CurrentToken.Id != TokenId.CloseCurlyParen)
+ throw ParseError(Res.CloseParenOrCommaExpected);
_textParser.NextToken();
- return CreateNewExpression(properties, expressions);
+ return CreateNewExpression(properties, expressions, newType);
}
- private Expression CreateNewExpression(List properties, List expressions)
+ private Expression CreateNewExpression(List properties, List expressions, Type newType)
{
// http://solutionizing.net/category/linq/
- Type type = _resultType;
+ Type type = newType ?? _resultType;
if (type == null)
{
@@ -1323,7 +1364,19 @@ Expression ParseMemberAccess(Type type, Expression instance)
MemberInfo member = FindPropertyOrField(type, id, instance == null);
if (member == null)
{
- throw ParseError(errorPos, Res.UnknownPropertyOrField, id, GetTypeName(type));
+ if (_textParser.CurrentToken.Id == TokenId.Lambda && _it.Type == type)
+ {
+ // This might be an internal variable for use within a lambda expression, so store it as such
+ _internals.Add(id, _it);
+ _textParser.NextToken();
+ var right = ParseExpression();
+ return right;
+ }
+ else
+ {
+ throw ParseError(errorPos, Res.UnknownPropertyOrField, id, GetTypeName(type));
+ }
+
}
var property = member as PropertyInfo;
@@ -1352,6 +1405,28 @@ static Type FindGenericType(Type generic, Type type)
return null;
}
+ Type FindType(string name)
+ {
+ object type;
+ _keywords.TryGetValue(name, out type);
+ var result = type as Type;
+ if (result != null)
+ return result;
+ if (_it != null && _it.Type.Name == name)
+ return _it.Type;
+ if (_parent != null && _parent.Type.Name == name)
+ return _parent.Type;
+ if (_root != null && _root.Type.Name == name)
+ return _root.Type;
+ if (_it != null && _it.Type.Namespace + "." + _it.Type.Name == name)
+ return _it.Type;
+ if (_parent != null && _parent.Type.Namespace + "." + _parent.Type.Name == name)
+ return _parent.Type;
+ if (_root != null && _root.Type.Namespace + "." + _root.Type.Name == name)
+ return _root.Type;
+ return null;
+ }
+
Expression ParseAggregate(Expression instance, Type elementType, string methodName, int errorPos)
{
var oldParent = _parent;
@@ -1381,7 +1456,7 @@ Expression ParseAggregate(Expression instance, Type elementType, string methodNa
throw ParseError(errorPos, Res.NoApplicableAggregate, methodName);
Type[] typeArgs;
- if (new[] { "Min", "Max", "Select", "OrderBy", "OrderByDescending", "ThenBy", "ThenByDescending" }.Contains(signature.Name))
+ if (new[] { "Min", "Max", "Select", "OrderBy", "OrderByDescending", "ThenBy", "ThenByDescending", "GroupBy" }.Contains(signature.Name))
{
typeArgs = new[] { elementType, args[0].Type };
}
@@ -2360,10 +2435,6 @@ static Expression OptimizeStringForEqualityIfPossible(string text, Type type)
return null;
}
-
-
-
-
bool TokenIdentifierIs(string id)
{
return _textParser.CurrentToken.Id == TokenId.Identifier && string.Equals(id, _textParser.CurrentToken.Text, StringComparison.OrdinalIgnoreCase);
diff --git a/src/System.Linq.Dynamic.Core/Res.cs b/src/System.Linq.Dynamic.Core/Res.cs
index 2cc2cc1f..531c2b58 100644
--- a/src/System.Linq.Dynamic.Core/Res.cs
+++ b/src/System.Linq.Dynamic.Core/Res.cs
@@ -55,5 +55,6 @@ internal static class Res
public const string IdentifierExpected = "Identifier expected";
public const string OpenParenOrIdentifierExpected = "'(' or Identifier expected";
public const string IdentifierImplementingInterfaceExpected = "Identifier implementing interface '{0}' expected";
+ public const string TypeNotFound = "Type '{0}' not Found";
}
}
\ No newline at end of file
diff --git a/src/System.Linq.Dynamic.Core/Tokenizer/TextParser.cs b/src/System.Linq.Dynamic.Core/Tokenizer/TextParser.cs
index e41eae55..7de69480 100644
--- a/src/System.Linq.Dynamic.Core/Tokenizer/TextParser.cs
+++ b/src/System.Linq.Dynamic.Core/Tokenizer/TextParser.cs
@@ -100,6 +100,16 @@ public void NextToken()
t = TokenId.CloseParen;
break;
+ case '{':
+ NextChar();
+ t = TokenId.OpenCurlyParen;
+ break;
+
+ case '}':
+ NextChar();
+ t = TokenId.CloseCurlyParen;
+ break;
+
case '*':
NextChar();
t = TokenId.Asterisk;
@@ -164,6 +174,11 @@ public void NextToken()
NextChar();
t = TokenId.DoubleEqual;
}
+ else if (_ch == '>')
+ {
+ NextChar();
+ t = TokenId.Lambda;
+ }
else
{
t = TokenId.Equal;
diff --git a/src/System.Linq.Dynamic.Core/Tokenizer/TokenId.cs b/src/System.Linq.Dynamic.Core/Tokenizer/TokenId.cs
index cee90bbf..1d8fce1d 100644
--- a/src/System.Linq.Dynamic.Core/Tokenizer/TokenId.cs
+++ b/src/System.Linq.Dynamic.Core/Tokenizer/TokenId.cs
@@ -13,6 +13,8 @@ internal enum TokenId
Amphersand,
OpenParen,
CloseParen,
+ OpenCurlyParen,
+ CloseCurlyParen,
Asterisk,
Plus,
Comma,
@@ -36,6 +38,7 @@ internal enum TokenId
DoubleBar,
DoubleGreaterThan,
DoubleLessThan,
- NullCoalescing
+ NullCoalescing,
+ Lambda
}
}
\ No newline at end of file
diff --git a/test/System.Linq.Dynamic.Core.Tests/ComplexTests.cs b/test/System.Linq.Dynamic.Core.Tests/ComplexTests.cs
index 9816afb7..ed9a78ed 100644
--- a/test/System.Linq.Dynamic.Core.Tests/ComplexTests.cs
+++ b/test/System.Linq.Dynamic.Core.Tests/ComplexTests.cs
@@ -1,5 +1,6 @@
using System.Collections.Generic;
using System.Linq.Dynamic.Core.Tests.Helpers.Models;
+using System.Linq.Expressions;
using Xunit;
namespace System.Linq.Dynamic.Core.Tests
@@ -94,5 +95,37 @@ public void GroupByAndSelect_TestDynamicSelectMember()
selectQry.AsEnumerable().Select(x => x.TotalIncome).Cast().ToArray());
#endif
}
+
+ [Fact]
+ public void ComplexString1()
+ {
+ //Arrange
+ var testList = User.GenerateSampleModels(51);
+ var qry = testList.AsQueryable();
+
+ var externals = new Dictionary();
+ externals.Add("Users", qry);
+
+ var query = "Users.GroupBy(x => new { x.Profile.Age }).OrderBy(gg => gg.Key.Age).Select(j => new (j.Key.Age, j.Sum(k=>k.Income) As TotalIncome))";
+ var expression = DynamicExpressionParser.ParseLambda(null, query, externals);
+ var del = expression.Compile();
+ var selectQry = del.DynamicInvoke();
+ }
+
+ [Fact]
+ public void ComplexString2()
+ {
+ //Arrange
+ var testList = User.GenerateSampleModels(51);
+ var qry = testList.AsQueryable();
+
+ var externals = new Dictionary();
+ externals.Add("Users", qry);
+
+ var query = "Users.Select(j => new User(j.Income As Income))";
+ var expression = DynamicExpressionParser.ParseLambda(null, query, externals);
+ var del = expression.Compile();
+ var res = del.DynamicInvoke();
+ }
}
}