diff --git a/src-console/ConsoleApp_net452_EF6/Program.cs b/src-console/ConsoleApp_net452_EF6/Program.cs index a74cd8dc..e4ee81b3 100644 --- a/src-console/ConsoleApp_net452_EF6/Program.cs +++ b/src-console/ConsoleApp_net452_EF6/Program.cs @@ -23,8 +23,6 @@ static void Main(string[] args) var found3 = context.Employees.FirstOrDefault($"EmployeeNumber > @0", em); Console.WriteLine($"found3 : {found3.Id} - {found3.EmployeeNumber}"); - return; - string search = "2"; var expected = context.Employees.Where(e => System.Data.Entity.SqlServer.SqlFunctions.StringConvert((double)e.EmployeeNumber).Contains(search)).ToArray(); foreach (var emp in expected) diff --git a/src/System.Linq.Dynamic.Core/Compatibility/ExpressionVisitor.cs b/src/System.Linq.Dynamic.Core/Compatibility/ExpressionVisitor.cs new file mode 100644 index 00000000..475cdd7b --- /dev/null +++ b/src/System.Linq.Dynamic.Core/Compatibility/ExpressionVisitor.cs @@ -0,0 +1,396 @@ +#if NET35 +using System.Collections.Generic; +using System.Collections.ObjectModel; + +namespace System.Linq.Expressions +{ + /// + /// Code copied from https://blogs.msdn.microsoft.com/mattwar/2007/07/31/linq-building-an-iqueryable-provider-part-ii/ + /// + internal abstract class ExpressionVisitor + { + /// + /// Initializes a new instance of the class. + /// + protected ExpressionVisitor() + { + } + + protected virtual Expression Visit(Expression exp) + { + if (exp == null) + return exp; + + switch (exp.NodeType) + { + case ExpressionType.Negate: + case ExpressionType.NegateChecked: + case ExpressionType.Not: + case ExpressionType.Convert: + case ExpressionType.ConvertChecked: + case ExpressionType.ArrayLength: + case ExpressionType.Quote: + case ExpressionType.TypeAs: + return VisitUnary((UnaryExpression)exp); + case ExpressionType.Add: + case ExpressionType.AddChecked: + case ExpressionType.Subtract: + case ExpressionType.SubtractChecked: + case ExpressionType.Multiply: + case ExpressionType.MultiplyChecked: + case ExpressionType.Divide: + case ExpressionType.Modulo: + case ExpressionType.And: + case ExpressionType.AndAlso: + case ExpressionType.Or: + case ExpressionType.OrElse: + case ExpressionType.LessThan: + case ExpressionType.LessThanOrEqual: + case ExpressionType.GreaterThan: + case ExpressionType.GreaterThanOrEqual: + case ExpressionType.Equal: + case ExpressionType.NotEqual: + case ExpressionType.Coalesce: + case ExpressionType.ArrayIndex: + case ExpressionType.RightShift: + case ExpressionType.LeftShift: + case ExpressionType.ExclusiveOr: + return VisitBinary((BinaryExpression)exp); + case ExpressionType.TypeIs: + return VisitTypeIs((TypeBinaryExpression)exp); + case ExpressionType.Conditional: + return VisitConditional((ConditionalExpression)exp); + case ExpressionType.Constant: + return VisitConstant((ConstantExpression)exp); + case ExpressionType.Parameter: + return VisitParameter((ParameterExpression)exp); + case ExpressionType.MemberAccess: + return VisitMemberAccess((MemberExpression)exp); + case ExpressionType.Call: + return VisitMethodCall((MethodCallExpression)exp); + case ExpressionType.Lambda: + return VisitLambda((LambdaExpression)exp); + case ExpressionType.New: + return VisitNew((NewExpression)exp); + case ExpressionType.NewArrayInit: + case ExpressionType.NewArrayBounds: + return VisitNewArray((NewArrayExpression)exp); + case ExpressionType.Invoke: + return VisitInvocation((InvocationExpression)exp); + case ExpressionType.MemberInit: + return VisitMemberInit((MemberInitExpression)exp); + case ExpressionType.ListInit: + return VisitListInit((ListInitExpression)exp); + default: + throw new Exception(string.Format("Unhandled expression type: '{0}'", exp.NodeType)); + } + } + + protected virtual MemberBinding VisitBinding(MemberBinding binding) + { + switch (binding.BindingType) + { + case MemberBindingType.Assignment: + return VisitMemberAssignment((MemberAssignment)binding); + case MemberBindingType.MemberBinding: + return VisitMemberMemberBinding((MemberMemberBinding)binding); + case MemberBindingType.ListBinding: + return VisitMemberListBinding((MemberListBinding)binding); + default: + throw new Exception(string.Format("Unhandled binding type '{0}'", binding.BindingType)); + } + } + + protected virtual ElementInit VisitElementInitializer(ElementInit initializer) + { + ReadOnlyCollection arguments = this.VisitExpressionList(initializer.Arguments); + if (arguments != initializer.Arguments) + { + return Expression.ElementInit(initializer.AddMethod, arguments); + } + + return initializer; + } + + protected virtual Expression VisitUnary(UnaryExpression u) + { + Expression operand = Visit(u.Operand); + if (operand != u.Operand) + { + return Expression.MakeUnary(u.NodeType, operand, u.Type, u.Method); + } + + return u; + } + + protected virtual Expression VisitBinary(BinaryExpression b) + { + Expression left = Visit(b.Left); + Expression right = Visit(b.Right); + Expression conversion = Visit(b.Conversion); + if (left != b.Left || right != b.Right || conversion != b.Conversion) + { + if (b.NodeType == ExpressionType.Coalesce && b.Conversion != null) + return Expression.Coalesce(left, right, conversion as LambdaExpression); + else + return Expression.MakeBinary(b.NodeType, left, right, b.IsLiftedToNull, b.Method); + } + + return b; + } + + protected virtual Expression VisitTypeIs(TypeBinaryExpression b) + { + Expression expr = Visit(b.Expression); + if (expr != b.Expression) + { + return Expression.TypeIs(expr, b.TypeOperand); + } + + return b; + } + + protected virtual Expression VisitConstant(ConstantExpression c) + { + return c; + } + + protected virtual Expression VisitConditional(ConditionalExpression c) + { + Expression test = Visit(c.Test); + Expression ifTrue = Visit(c.IfTrue); + Expression ifFalse = Visit(c.IfFalse); + if (test != c.Test || ifTrue != c.IfTrue || ifFalse != c.IfFalse) + { + return Expression.Condition(test, ifTrue, ifFalse); + } + + return c; + } + + protected virtual Expression VisitParameter(ParameterExpression p) + { + return p; + } + + protected virtual Expression VisitMemberAccess(MemberExpression m) + { + Expression exp = Visit(m.Expression); + if (exp != m.Expression) + { + return Expression.MakeMemberAccess(exp, m.Member); + } + + return m; + } + + protected virtual Expression VisitMethodCall(MethodCallExpression m) + { + Expression obj = Visit(m.Object); + IEnumerable args = this.VisitExpressionList(m.Arguments); + if (obj != m.Object || args != m.Arguments) + { + return Expression.Call(obj, m.Method, args); + } + + return m; + } + + protected virtual ReadOnlyCollection VisitExpressionList(ReadOnlyCollection original) + { + List list = null; + for (int i = 0, n = original.Count; i < n; i++) + { + Expression p = this.Visit(original[i]); + if (list != null) + { + list.Add(p); + } + else if (p != original[i]) + { + list = new List(n); + for (int j = 0; j < i; j++) + { + list.Add(original[j]); + } + + list.Add(p); + } + } + + if (list != null) + { + return list.AsReadOnly(); + } + + return original; + } + + protected virtual MemberAssignment VisitMemberAssignment(MemberAssignment assignment) + { + Expression e = Visit(assignment.Expression); + if (e != assignment.Expression) + { + return Expression.Bind(assignment.Member, e); + } + + return assignment; + } + + protected virtual MemberMemberBinding VisitMemberMemberBinding(MemberMemberBinding binding) + { + IEnumerable bindings = this.VisitBindingList(binding.Bindings); + if (bindings != binding.Bindings) + { + return Expression.MemberBind(binding.Member, bindings); + } + + return binding; + } + + protected virtual MemberListBinding VisitMemberListBinding(MemberListBinding binding) + { + IEnumerable initializers = this.VisitElementInitializerList(binding.Initializers); + if (initializers != binding.Initializers) + { + return Expression.ListBind(binding.Member, initializers); + } + + return binding; + } + + protected virtual IEnumerable VisitBindingList(ReadOnlyCollection original) + { + List list = null; + for (int i = 0, n = original.Count; i < n; i++) + { + MemberBinding b = this.VisitBinding(original[i]); + if (list != null) + { + list.Add(b); + } + else if (b != original[i]) + { + list = new List(n); + for (int j = 0; j < i; j++) + { + list.Add(original[j]); + } + + list.Add(b); + } + } + + if (list != null) + return list; + return original; + } + + protected virtual IEnumerable VisitElementInitializerList(ReadOnlyCollection original) + { + List list = null; + for (int i = 0, n = original.Count; i < n; i++) + { + ElementInit init = this.VisitElementInitializer(original[i]); + if (list != null) + { + list.Add(init); + } + else if (init != original[i]) + { + list = new List(n); + for (int j = 0; j < i; j++) + { + list.Add(original[j]); + } + + list.Add(init); + } + } + + if (list != null) + return list; + return original; + } + + protected virtual Expression VisitLambda(LambdaExpression lambda) + { + Expression body = Visit(lambda.Body); + if (body != lambda.Body) + { + return Expression.Lambda(lambda.Type, body, lambda.Parameters); + } + + return lambda; + } + + protected virtual NewExpression VisitNew(NewExpression nex) + { + IEnumerable args = this.VisitExpressionList(nex.Arguments); + if (args != nex.Arguments) + { + if (nex.Members != null) + return Expression.New(nex.Constructor, args, nex.Members); + else + return Expression.New(nex.Constructor, args); + } + + return nex; + } + + protected virtual Expression VisitMemberInit(MemberInitExpression init) + { + NewExpression n = VisitNew(init.NewExpression); + IEnumerable bindings = this.VisitBindingList(init.Bindings); + if (n != init.NewExpression || bindings != init.Bindings) + { + return Expression.MemberInit(n, bindings); + } + + return init; + } + + protected virtual Expression VisitListInit(ListInitExpression init) + { + NewExpression n = VisitNew(init.NewExpression); + IEnumerable initializers = this.VisitElementInitializerList(init.Initializers); + if (n != init.NewExpression || initializers != init.Initializers) + { + return Expression.ListInit(n, initializers); + } + + return init; + } + + protected virtual Expression VisitNewArray(NewArrayExpression na) + { + IEnumerable exprs = this.VisitExpressionList(na.Expressions); + if (exprs != na.Expressions) + { + if (na.NodeType == ExpressionType.NewArrayInit) + { + return Expression.NewArrayInit(na.Type.GetElementType(), exprs); + } + else + { + return Expression.NewArrayBounds(na.Type.GetElementType(), exprs); + } + } + + return na; + } + + protected virtual Expression VisitInvocation(InvocationExpression iv) + { + IEnumerable args = this.VisitExpressionList(iv.Arguments); + Expression expr = Visit(iv.Expression); + if (args != iv.Arguments || expr != iv.Expression) + { + return Expression.Invoke(expr, args); + } + + return iv; + } + } +} +#endif diff --git a/src/System.Linq.Dynamic.Core/DynamicExpressionParser.cs b/src/System.Linq.Dynamic.Core/DynamicExpressionParser.cs index 327901df..fa5ab9f1 100644 --- a/src/System.Linq.Dynamic.Core/DynamicExpressionParser.cs +++ b/src/System.Linq.Dynamic.Core/DynamicExpressionParser.cs @@ -1,7 +1,9 @@ using System.Linq.Dynamic.Core.Parser; +using System.Linq.Dynamic.Core.Util; using JetBrains.Annotations; using System.Linq.Dynamic.Core.Validation; using System.Linq.Expressions; +using System.Collections.Generic; namespace System.Linq.Dynamic.Core { @@ -63,7 +65,19 @@ public static LambdaExpression ParseLambda([CanBeNull] ParsingConfig parsingConf var parser = new ExpressionParser(parameters, expression, values, parsingConfig); - return Expression.Lambda(parser.Parse(resultType, createParameterCtor), parameters); + var parsedExpression = parser.Parse(resultType, createParameterCtor); + + if (parsingConfig != null && parsingConfig.RenameParameterExpression && parameters.Length == 1) + { + var renamer = new ParameterExpressionRenamer(parser.ItName); + parsedExpression = renamer.Rename(parsedExpression, out ParameterExpression newParameterExpression); + + return Expression.Lambda(parsedExpression, new[] { newParameterExpression }); + } + else + { + return Expression.Lambda(parsedExpression, parameters); + } } /// diff --git a/src/System.Linq.Dynamic.Core/Parser/ExpressionParser.cs b/src/System.Linq.Dynamic.Core/Parser/ExpressionParser.cs index 9b513588..8383631b 100644 --- a/src/System.Linq.Dynamic.Core/Parser/ExpressionParser.cs +++ b/src/System.Linq.Dynamic.Core/Parser/ExpressionParser.cs @@ -38,6 +38,11 @@ public class ExpressionParser private Type _resultType; private bool _createParameterCtor; + /// + /// Gets name for the `it` field. By default this is set to the KeyWord value "it". + /// + public string ItName { get; private set; } = KeywordsHelper.KEYWORD_IT; + /// /// Initializes a new instance of the class. /// @@ -1512,6 +1517,14 @@ Expression ParseMemberAccess(Type type, Expression instance) { // This might be an internal variable for use within a lambda expression, so store it as such _internals.Add(id, _it); + + // Also store ItName (only once) + if (string.Equals(ItName, KeywordsHelper.KEYWORD_IT)) + { + ItName = id; + } + + // next _textParser.NextToken(); return ParseConditionalOperator(); diff --git a/src/System.Linq.Dynamic.Core/ParsingConfig.cs b/src/System.Linq.Dynamic.Core/ParsingConfig.cs index bebd87d4..a2760a86 100644 --- a/src/System.Linq.Dynamic.Core/ParsingConfig.cs +++ b/src/System.Linq.Dynamic.Core/ParsingConfig.cs @@ -4,7 +4,7 @@ namespace System.Linq.Dynamic.Core { /// - /// Configuration class for Dynamic Linq. + /// Configuration class for System.Linq.Dynamic.Core. /// public class ParsingConfig { @@ -71,19 +71,20 @@ public IExpressionPromoter ExpressionPromoter /// /// Determines if the context keywords (it, parent, and root) are valid and usable inside a Dynamic Linq string expression. /// Does not affect the usability of the equivalent context symbols ($, ^ and ~). + /// Default value is true. /// public bool AreContextKeywordsEnabled { get; set; } = true; /// - /// Gets or sets a value indicating whether to use dynamic object class for anonymous types. + /// Gets or sets a value indicating whether to use dynamic object class for anonymous types. Default value is false. /// - public bool UseDynamicObjectClassForAnonymousTypes { get; set; } + public bool UseDynamicObjectClassForAnonymousTypes { get; set; } = false; /// - /// Gets or sets a value indicating whether the EntityFramwwork version supports evaluating GroupBy at database level. + /// Gets or sets a value indicating whether the EntityFramwwork version supports evaluating GroupBy at database level. Default value is false. /// See https://docs.microsoft.com/en-us/ef/core/what-is-new/ef-core-2.1#linq-groupby-translation /// - public bool EvaluateGroupByAtDatabase { get; set; } + public bool EvaluateGroupByAtDatabase { get; set; } = false; /// /// Use Parameterized Names in generated dynamic SQL query. Default set to true. @@ -92,8 +93,13 @@ public IExpressionPromoter ExpressionPromoter public bool UseParameterizedNamesInDynamicQuery { get; set; } = false; /// - /// Allows the New() keyword to evaluate any available Type. + /// Allows the New() keyword to evaluate any available Type. Default value is false. /// public bool AllowNewToEvaluateAnyType { get; set; } = false; + + /// + /// Renames the (Typed)ParameterExpression empty Name to a the correct supplied name from `it`. Default value is false. + /// + public bool RenameParameterExpression { get; set; } = false; } } diff --git a/src/System.Linq.Dynamic.Core/Properties/debugSettings.json b/src/System.Linq.Dynamic.Core/Properties/debugSettings.json deleted file mode 100644 index a44fad34..00000000 --- a/src/System.Linq.Dynamic.Core/Properties/debugSettings.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "Profiles": [] -} \ No newline at end of file diff --git a/src/System.Linq.Dynamic.Core/Util/ParameterExpressionRenamer.cs b/src/System.Linq.Dynamic.Core/Util/ParameterExpressionRenamer.cs new file mode 100644 index 00000000..960baa12 --- /dev/null +++ b/src/System.Linq.Dynamic.Core/Util/ParameterExpressionRenamer.cs @@ -0,0 +1,74 @@ +using JetBrains.Annotations; +using System.Linq.Dynamic.Core.Validation; +using System.Linq.Expressions; + +namespace System.Linq.Dynamic.Core.Util +{ + /// + /// Renames a single (Typed)ParameterExpression in an Expression. + /// + /// + internal class ParameterExpressionRenamer : ExpressionVisitor + { + private readonly string _newName; + private readonly string _oldName; + + private ParameterExpression _parameterExpression; + + /// + /// Initializes a new instance of the class. + /// + /// The new name (the oldName is assumed to be ""). + public ParameterExpressionRenamer([NotNull] string newName) : this("", newName) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The old name. + /// The new name. + public ParameterExpressionRenamer([NotNull] string oldName, [NotNull] string newName) + { + Check.NotNull(oldName, nameof(oldName)); + Check.NotEmpty(newName, nameof(newName)); + + _oldName = oldName; + _newName = newName; + } + + /// + /// Renames a single (Typed)ParameterExpression from specified expression. + /// + /// The expression. + /// The new generated (Typed)ParameterExpression. + /// Renamed Expression + public Expression Rename(Expression expression, out ParameterExpression parameterExpression) + { + var visitedExpression = Visit(expression); + + parameterExpression = _parameterExpression; + + return visitedExpression; + } + + /// + protected override Expression VisitParameter(ParameterExpression node) + { + if (string.Equals(_oldName, node.Name, StringComparison.Ordinal)) + { + if (_parameterExpression == null) + { + _parameterExpression = ParameterExpressionHelper.CreateParameterExpression(node.Type, _newName); + } + + return _parameterExpression; + // throw new InvalidOperationException($"The {nameof(ParameterExpressionRenamer)} can only rename 1 (Typed)ParameterExpression in an Expression."); + } + else + { + return node; + } + } + } +} diff --git a/test/EntityFramework.DynamicLinq.Tests.net452/EntityFramework.DynamicLinq.Tests.net452.csproj b/test/EntityFramework.DynamicLinq.Tests.net452/EntityFramework.DynamicLinq.Tests.net452.csproj index 844be632..effb692b 100644 --- a/test/EntityFramework.DynamicLinq.Tests.net452/EntityFramework.DynamicLinq.Tests.net452.csproj +++ b/test/EntityFramework.DynamicLinq.Tests.net452/EntityFramework.DynamicLinq.Tests.net452.csproj @@ -202,9 +202,6 @@ Entities\User.cs - - ExpressionPromoterTests.cs - ExpressionTests.cs @@ -354,6 +351,7 @@ + diff --git a/test/EntityFramework.DynamicLinq.Tests/EntityFramework.DynamicLinq.Tests.csproj b/test/EntityFramework.DynamicLinq.Tests/EntityFramework.DynamicLinq.Tests.csproj index 6eee6e5f..83af2fb0 100644 --- a/test/EntityFramework.DynamicLinq.Tests/EntityFramework.DynamicLinq.Tests.csproj +++ b/test/EntityFramework.DynamicLinq.Tests/EntityFramework.DynamicLinq.Tests.csproj @@ -23,6 +23,7 @@ + diff --git a/test/System.Linq.Dynamic.Core.Tests/DynamicExpressionParserTests.cs b/test/System.Linq.Dynamic.Core.Tests/DynamicExpressionParserTests.cs index 851a540d..64a970d9 100644 --- a/test/System.Linq.Dynamic.Core.Tests/DynamicExpressionParserTests.cs +++ b/test/System.Linq.Dynamic.Core.Tests/DynamicExpressionParserTests.cs @@ -23,6 +23,7 @@ private class ComplexParseLambda1Result { public int? Age; public int TotalIncome; + public string Name; } [DynamicLinqType] @@ -114,7 +115,7 @@ public Type ResolveType(string typeName) } [Fact] - public void ParseLambda_UseParameterizedNamesInDynamicQuery_true() + public void DynamicExpressionParser_ParseLambda_UseParameterizedNamesInDynamicQuery_true() { // Assign var config = new ParsingConfig @@ -134,7 +135,7 @@ public void ParseLambda_UseParameterizedNamesInDynamicQuery_true() } [Fact] - public void ParseLambda_UseParameterizedNamesInDynamicQuery_false() + public void DynamicExpressionParser_ParseLambda_UseParameterizedNamesInDynamicQuery_false() { // Assign var config = new ParsingConfig @@ -153,7 +154,7 @@ public void ParseLambda_UseParameterizedNamesInDynamicQuery_false() } [Fact] - public void ParseLambda_ToList() + public void DynamicExpressionParser_ParseLambda_ToList() { // Arrange var testList = User.GenerateSampleModels(51); @@ -174,7 +175,7 @@ public void ParseLambda_ToList() } [Fact] - public void ParseLambda_Complex_1() + public void DynamicExpressionParser_ParseLambda_Complex_1() { // Arrange var testList = User.GenerateSampleModels(51); @@ -204,7 +205,7 @@ public void ParseLambda_Complex_1() } [Fact] - public void ParseLambda_Complex_2() + public void DynamicExpressionParser_ParseLambda_Complex_2() { // Arrange var testList = User.GenerateSampleModels(51); @@ -229,7 +230,7 @@ public void ParseLambda_Complex_2() } [Fact] - public void ParseLambda_Complex_3() + public void DynamicExpressionParser_ParseLambda_Complex_3() { var config = new ParsingConfig { @@ -264,7 +265,7 @@ public void ParseLambda_Complex_3() } [Fact] - public void ParseLambda_Select_1() + public void DynamicExpressionParser_ParseLambda_Select_1() { // Arrange var testList = User.GenerateSampleModels(51); @@ -286,7 +287,7 @@ public void ParseLambda_Select_1() } [Fact] - public void ParseLambda_Select_2() + public void DynamicExpressionParser_ParseLambda_Select_2() { // Arrange var testList = User.GenerateSampleModels(5); @@ -309,7 +310,7 @@ public void ParseLambda_Select_2() // https://github.com/StefH/System.Linq.Dynamic.Core/issues/58 [Fact] - public void ParseLambda_4_Issue58() + public void DynamicExpressionParser_ParseLambda_4_Issue58() { var expressionParams = new[] { @@ -328,7 +329,7 @@ public void ParseLambda_4_Issue58() } [Fact] - public void ParseLambda_DuplicateParameterNames_ThrowsException() + public void DynamicExpressionParser_ParseLambda_DuplicateParameterNames_ThrowsException() { // Arrange var parameters = new[] @@ -344,7 +345,7 @@ public void ParseLambda_DuplicateParameterNames_ThrowsException() } [Fact] - public void ParseLambda_EmptyParameterList() + public void DynamicExpressionParser_ParseLambda_EmptyParameterList() { // Arrange var pEmpty = new ParameterExpression[] { }; @@ -358,7 +359,7 @@ public void ParseLambda_EmptyParameterList() } [Fact] - public void ParseLambda_ParameterName() + public void DynamicExpressionParser_ParseLambda_ParameterName() { // Arrange var parameters = new[] @@ -376,7 +377,7 @@ public void ParseLambda_ParameterName() } [Fact] - public void ParseLambda_ParameterName_Empty() + public void DynamicExpressionParser_ParseLambda_ParameterName_Empty() { // Arrange var parameters = new[] @@ -392,7 +393,7 @@ public void ParseLambda_ParameterName_Empty() } [Fact] - public void ParseLambda_ParameterName_Null() + public void DynamicExpressionParser_ParseLambda_ParameterName_Null() { // Arrange var parameters = new[] @@ -408,7 +409,7 @@ public void ParseLambda_ParameterName_Null() } [Fact] - public void ParseLambda_ParameterExpressionMethodCall_ReturnsIntExpression() + public void DynamicExpressionParser_ParseLambda_ParameterExpressionMethodCall_ReturnsIntExpression() { var expression = DynamicExpressionParser.ParseLambda(true, new[] { Expression.Parameter(typeof(int), "x") }, @@ -418,7 +419,7 @@ public void ParseLambda_ParameterExpressionMethodCall_ReturnsIntExpression() } [Fact] - public void ParseLambda_RealNumbers() + public void DynamicExpressionParser_ParseLambda_RealNumbers() { var parameters = new ParameterExpression[0]; @@ -435,7 +436,7 @@ public void ParseLambda_RealNumbers() } [Fact] - public void ParseLambda_StringLiteral_ReturnsBooleanLambdaExpression() + public void DynamicExpressionParser_ParseLambda_StringLiteral_ReturnsBooleanLambdaExpression() { var expression = DynamicExpressionParser.ParseLambda( new[] { Expression.Parameter(typeof(string), "Property1") }, typeof(bool), "Property1 == \"test\""); @@ -443,7 +444,7 @@ public void ParseLambda_StringLiteral_ReturnsBooleanLambdaExpression() } [Fact] - public void ParseLambda_StringLiteralEmpty_ReturnsBooleanLambdaExpression() + public void DynamicExpressionParser_ParseLambda_StringLiteralEmpty_ReturnsBooleanLambdaExpression() { var expression = DynamicExpressionParser.ParseLambda( new[] { Expression.Parameter(typeof(string), "Property1") }, typeof(bool), "Property1 == \"\""); @@ -451,7 +452,7 @@ public void ParseLambda_StringLiteralEmpty_ReturnsBooleanLambdaExpression() } [Fact] - public void ParseLambda_Config_StringLiteralEmpty_ReturnsBooleanLambdaExpression() + public void DynamicExpressionParser_ParseLambda_Config_StringLiteralEmpty_ReturnsBooleanLambdaExpression() { var config = new ParsingConfig(); var expression = DynamicExpressionParser.ParseLambda( @@ -460,7 +461,7 @@ public void ParseLambda_Config_StringLiteralEmpty_ReturnsBooleanLambdaExpression } [Fact] - public void ParseLambda_StringLiteralEmbeddedQuote_ReturnsBooleanLambdaExpression() + public void DynamicExpressionParser_ParseLambda_StringLiteralEmbeddedQuote_ReturnsBooleanLambdaExpression() { string expectedRightValue = "\"test \\\"string\""; @@ -476,7 +477,7 @@ public void ParseLambda_StringLiteralEmbeddedQuote_ReturnsBooleanLambdaExpressio } [Fact] - public void ParseLambda_StringLiteralStartEmbeddedQuote_ReturnsBooleanLambdaExpression() + public void DynamicExpressionParser_ParseLambda_StringLiteralStartEmbeddedQuote_ReturnsBooleanLambdaExpression() { // Assign string expectedRightValue = "\"\\\"test\""; @@ -492,7 +493,7 @@ public void ParseLambda_StringLiteralStartEmbeddedQuote_ReturnsBooleanLambdaExpr } [Fact] - public void ParseLambda_StringLiteral_MissingClosingQuote() + public void DynamicExpressionParser_ParseLambda_StringLiteral_MissingClosingQuote() { string expectedRightValue = "\"test\\\""; @@ -503,7 +504,7 @@ public void ParseLambda_StringLiteral_MissingClosingQuote() } [Fact] - public void ParseLambda_StringLiteralEscapedBackslash_ReturnsBooleanLambdaExpression() + public void DynamicExpressionParser_ParseLambda_StringLiteralEscapedBackslash_ReturnsBooleanLambdaExpression() { // Assign string expectedRightValue = "\"test\\string\""; @@ -520,7 +521,7 @@ public void ParseLambda_StringLiteralEscapedBackslash_ReturnsBooleanLambdaExpres } [Fact] - public void ParseLambda_TupleToStringMethodCall_ReturnsStringLambdaExpression() + public void DynamicExpressionParser_ParseLambda_TupleToStringMethodCall_ReturnsStringLambdaExpression() { var expression = DynamicExpressionParser.ParseLambda( typeof(Tuple), @@ -530,14 +531,14 @@ public void ParseLambda_TupleToStringMethodCall_ReturnsStringLambdaExpression() } [Fact] - public void ParseLambda_IllegalMethodCall_ThrowsException() + public void DynamicExpressionParser_ParseLambda_IllegalMethodCall_ThrowsException() { Check.ThatCode(() => { DynamicExpressionParser.ParseLambda(typeof(IO.FileStream), null, "it.Close()"); }) .Throws().WithMessage("Methods on type 'Stream' are not accessible"); } [Fact] - public void ParseLambda_CustomMethod() + public void DynamicExpressionParser_ParseLambda_CustomMethod() { // Assign var config = new ParsingConfig @@ -560,7 +561,7 @@ public void ParseLambda_CustomMethod() } [Fact] - public void ParseLambda_With_InnerStringLiteral() + public void DynamicExpressionParser_ParseLambda_With_InnerStringLiteral() { var originalTrueValue = "simple + \"quoted\""; var doubleQuotedTrueValue = "simple + \"\"quoted\"\""; @@ -572,7 +573,7 @@ public void ParseLambda_With_InnerStringLiteral() } [Fact] - public void ParseLambda_With_Guid_Equals_Null() + public void DynamicExpressionParser_ParseLambda_With_Guid_Equals_Null() { // Arrange var user = new User(); @@ -593,7 +594,7 @@ public void ParseLambda_With_Guid_Equals_Null() } [Fact] - public void ParseLambda_With_Null_Equals_Guid() + public void DynamicExpressionParser_ParseLambda_With_Null_Equals_Guid() { // Arrange var user = new User(); @@ -614,7 +615,7 @@ public void ParseLambda_With_Null_Equals_Guid() } [Fact] - public void ParseLambda_With_Guid_Equals_String() + public void DynamicExpressionParser_ParseLambda_With_Guid_Equals_String() { // Arrange Guid someId = Guid.NewGuid(); @@ -638,7 +639,7 @@ public void ParseLambda_With_Guid_Equals_String() } [Fact] - public void ParseLambda_With_Concat_String_CustomType() + public void DynamicExpressionParser_ParseLambda_With_Concat_String_CustomType() { // Arrange string name = "name1"; @@ -662,7 +663,7 @@ public void ParseLambda_With_Concat_String_CustomType() } [Fact] - public void ParseLambda_With_Concat_CustomType_String() + public void DynamicExpressionParser_ParseLambda_With_Concat_CustomType_String() { // Arrange string name = "name1"; @@ -686,7 +687,7 @@ public void ParseLambda_With_Concat_CustomType_String() } [Fact] - public void ParseLambda_Operator_Less_Greater_With_Guids() + public void DynamicExpressionParser_ParseLambda_Operator_Less_Greater_With_Guids() { var config = new ParsingConfig { @@ -713,5 +714,24 @@ public void ParseLambda_Operator_Less_Greater_With_Guids() // Assert Assert.Equal(anotherId, result); } + + [Theory] + [InlineData("c => c.Age == 8", "c => (c.Age == 8)")] + [InlineData("c => c.Name == \"test\"", "c => (c.Name == \"test\")")] + public void DynamicExpressionParser_ParseLambda_RenameParameterExpression(string expressionAsString, string expected) + { + // Arrange + var config = new ParsingConfig + { + RenameParameterExpression = true + }; + + // Act + var expression = DynamicExpressionParser.ParseLambda(config, true, expressionAsString); + string result = expression.ToString(); + + // Assert + Check.That(result).IsEqualTo(expected); + } } } diff --git a/test/System.Linq.Dynamic.Core.Tests/ExpressionHelperTests.cs b/test/System.Linq.Dynamic.Core.Tests/Parser/ExpressionHelperTests.cs similarity index 96% rename from test/System.Linq.Dynamic.Core.Tests/ExpressionHelperTests.cs rename to test/System.Linq.Dynamic.Core.Tests/Parser/ExpressionHelperTests.cs index 17da8691..c29ef4c6 100644 --- a/test/System.Linq.Dynamic.Core.Tests/ExpressionHelperTests.cs +++ b/test/System.Linq.Dynamic.Core.Tests/Parser/ExpressionHelperTests.cs @@ -3,7 +3,7 @@ using NFluent; using Xunit; -namespace System.Linq.Dynamic.Core.Tests +namespace System.Linq.Dynamic.Core.Parser.Tests { public class ExpressionHelperTests { diff --git a/test/System.Linq.Dynamic.Core.Tests/ExpressionPromoterTests.cs b/test/System.Linq.Dynamic.Core.Tests/Parser/ExpressionPromoterTests.cs similarity index 98% rename from test/System.Linq.Dynamic.Core.Tests/ExpressionPromoterTests.cs rename to test/System.Linq.Dynamic.Core.Tests/Parser/ExpressionPromoterTests.cs index ef6974b0..b08ddf76 100644 --- a/test/System.Linq.Dynamic.Core.Tests/ExpressionPromoterTests.cs +++ b/test/System.Linq.Dynamic.Core.Tests/Parser/ExpressionPromoterTests.cs @@ -5,7 +5,7 @@ using System.Linq.Expressions; using Xunit; -namespace System.Linq.Dynamic.Core.Tests +namespace System.Linq.Dynamic.Core.Parser.Tests { public class ExpressionPromoterTests { diff --git a/test/System.Linq.Dynamic.Core.Tests/TypeHelperTests.cs b/test/System.Linq.Dynamic.Core.Tests/Parser/TypeHelperTests.cs similarity index 96% rename from test/System.Linq.Dynamic.Core.Tests/TypeHelperTests.cs rename to test/System.Linq.Dynamic.Core.Tests/Parser/TypeHelperTests.cs index df2b921f..e473f8dd 100644 --- a/test/System.Linq.Dynamic.Core.Tests/TypeHelperTests.cs +++ b/test/System.Linq.Dynamic.Core.Tests/Parser/TypeHelperTests.cs @@ -2,7 +2,7 @@ using System.Linq.Dynamic.Core.Parser; using Xunit; -namespace System.Linq.Dynamic.Core.Tests +namespace System.Linq.Dynamic.Core.Tests.Parser { public class TypeHelperTests { diff --git a/test/System.Linq.Dynamic.Core.Tests/QueryableTests.Select.cs b/test/System.Linq.Dynamic.Core.Tests/QueryableTests.Select.cs index 307dfbd4..7785d83b 100644 --- a/test/System.Linq.Dynamic.Core.Tests/QueryableTests.Select.cs +++ b/test/System.Linq.Dynamic.Core.Tests/QueryableTests.Select.cs @@ -16,6 +16,43 @@ namespace System.Linq.Dynamic.Core.Tests { public partial class QueryableTests { + public class Example + { + public DateTime Time { get; set; } + public DayOfWeek? DOWNull { get; set; } + public DayOfWeek DOW { get; set; } + public int Sec { get; set; } + public int? SecNull { get; set; } + + public class NestedDto + { + public string Name { get; set; } + + public class NestedDto2 + { + public string Name2 { get; set; } + } + } + } + + public class ExampleWithConstructor + { + public DateTime Time { get; set; } + public DayOfWeek? DOWNull { get; set; } + public DayOfWeek DOW { get; set; } + public int Sec { get; set; } + public int? SecNull { get; set; } + + public ExampleWithConstructor(DateTime t, DayOfWeek? dn, DayOfWeek d, int s, int? sn) + { + Time = t; + DOWNull = dn; + DOW = d; + Sec = s; + SecNull = sn; + } + } + /// /// Cannot work with property which in base class. https://github.com/StefH/System.Linq.Dynamic.Core/issues/23 /// @@ -190,43 +227,6 @@ public void Select_Dynamic_IntoType() Assert.Equal(testList.Select(x => x.Profile).Cast().ToList(), userProfiles.ToDynamicList()); } - public class Example - { - public DateTime Time { get; set; } - public DayOfWeek? DOWNull { get; set; } - public DayOfWeek DOW { get; set; } - public int Sec { get; set; } - public int? SecNull { get; set; } - - public class NestedDto - { - public string Name { get; set; } - - public class NestedDto2 - { - public string Name2 { get; set; } - } - } - } - - public class ExampleWithConstructor - { - public DateTime Time { get; set; } - public DayOfWeek? DOWNull { get; set; } - public DayOfWeek DOW { get; set; } - public int Sec { get; set; } - public int? SecNull { get; set; } - - public ExampleWithConstructor(DateTime t, DayOfWeek? dn, DayOfWeek d, int s, int? sn) - { - Time = t; - DOWNull = dn; - DOW = d; - Sec = s; - SecNull = sn; - } - } - [Fact] public void Select_Dynamic_IntoTypeWithNullableProperties1() { @@ -302,6 +302,40 @@ public void Select_Dynamic_IntoKnownNestedTypeSecondLevel() Check.That(projectedData.Last().Name2).Equals("name2"); } + [Fact] + public void Select_Dynamic_RenameParameterExpression_Is_False() + { + // Arrange + var config = new ParsingConfig + { + RenameParameterExpression = false + }; + var queryable = new int[0].AsQueryable(); + + // Act + string result = queryable.Select(config, "it * it").ToString(); + + // Assert + Check.That(result).Equals("System.Int32[].Select(Param_0 => (Param_0 * Param_0))"); + } + + [Fact] + public void Select_Dynamic_RenameParameterExpression_Is_True() + { + // Arrange + var config = new ParsingConfig + { + RenameParameterExpression = true + }; + var queryable = new int[0].AsQueryable(); + + // Act + string result = queryable.Select(config, "it * it").ToString(); + + // Assert + Check.That(result).Equals("System.Int32[].Select(it => (it * it))"); + } + [Fact] public void Select_Dynamic_Exceptions() { diff --git a/test/System.Linq.Dynamic.Core.Tests/TextParserTests.cs b/test/System.Linq.Dynamic.Core.Tests/Tokenizer/TextParserTests.cs similarity index 99% rename from test/System.Linq.Dynamic.Core.Tests/TextParserTests.cs rename to test/System.Linq.Dynamic.Core.Tests/Tokenizer/TextParserTests.cs index 58d98837..71fa4fa5 100644 --- a/test/System.Linq.Dynamic.Core.Tests/TextParserTests.cs +++ b/test/System.Linq.Dynamic.Core.Tests/Tokenizer/TextParserTests.cs @@ -3,7 +3,7 @@ using System.Linq.Dynamic.Core.Tokenizer; using Xunit; -namespace System.Linq.Dynamic.Core.Tests +namespace System.Linq.Dynamic.Core.Tokenizer.Tests { public class TextParserTests { @@ -199,4 +199,4 @@ public void TextParser_Parse_ThrowsException() Check.ThatCode(() => { new TextParser(";"); }).Throws(); } } -} \ No newline at end of file +} diff --git a/test/System.Linq.Dynamic.Core.Tests/Util/ParameterExpressionRenamerTests.cs b/test/System.Linq.Dynamic.Core.Tests/Util/ParameterExpressionRenamerTests.cs new file mode 100644 index 00000000..fe12a0b9 --- /dev/null +++ b/test/System.Linq.Dynamic.Core.Tests/Util/ParameterExpressionRenamerTests.cs @@ -0,0 +1,58 @@ +using NFluent; +using System.Linq.Dynamic.Core.Util; +using System.Linq.Expressions; +using Xunit; + +namespace System.Linq.Dynamic.Core.Tests.Util +{ + public class ParameterExpressionRenamerTests + { + [Theory] + [InlineData("test", "(test + 42)")] + public void ParameterExpressionRenamer_Rename_ToNewName(string newName, string resultString) + { + // Assign + var expression = Expression.Add(Expression.Parameter(typeof(int), ""), Expression.Constant(42)); + var sut = new ParameterExpressionRenamer(newName); + + // Act + string result = sut.Rename(expression, out ParameterExpression parameterExpression).ToString(); + + // Assert + Check.That(result).IsEqualTo(resultString); + Check.That(parameterExpression.Name).IsEqualTo(newName); + } + + [Theory] + [InlineData("", "test", "(test + 42)")] + [InlineData("x", "test", "(test + 42)")] + public void ParameterExpressionRenamer_Rename_OldNameInNewName(string oldName, string newName, string resultString) + { + // Assign + var expression = Expression.Add(Expression.Parameter(typeof(int), oldName), Expression.Constant(42)); + var sut = new ParameterExpressionRenamer(oldName, newName); + + // Act + string result = sut.Rename(expression, out ParameterExpression parameterExpression).ToString(); + + // Assert + Check.That(result).IsEqualTo(resultString); + Check.That(parameterExpression.Name).IsEqualTo(newName); + } + + [Fact] + public void ParameterExpressionRenamer_Rename_NoParameterExpressionPresent() + { + // Assign + var expression = Expression.Add(Expression.Constant(1), Expression.Constant(2)); + var sut = new ParameterExpressionRenamer("test"); + + // Act + string result = sut.Rename(expression, out ParameterExpression parameterExpression).ToString(); + + // Assert + Check.That(result).IsEqualTo("(1 + 2)"); + Check.That(parameterExpression).IsNull(); + } + } +}