diff --git a/src/System.Linq.Dynamic.Core/Parser/ExpressionParser.cs b/src/System.Linq.Dynamic.Core/Parser/ExpressionParser.cs index 3a3b41e3..ce749380 100644 --- a/src/System.Linq.Dynamic.Core/Parser/ExpressionParser.cs +++ b/src/System.Linq.Dynamic.Core/Parser/ExpressionParser.cs @@ -24,6 +24,7 @@ public class ExpressionParser static readonly string methodThenByDescending = nameof(Queryable.ThenByDescending); private readonly ParsingConfig _parsingConfig; + private readonly MethodFinder _methodFinder; private readonly KeywordsHelper _keywordsHelper; private readonly TextParser _textParser; private readonly Dictionary _internals; @@ -64,6 +65,7 @@ public ExpressionParser([CanBeNull] ParameterExpression[] parameters, [NotNull] _keywordsHelper = new KeywordsHelper(_parsingConfig); _textParser = new TextParser(expression); + _methodFinder = new MethodFinder(_parsingConfig); } void ProcessParameters(ParameterExpression[] parameters) @@ -130,7 +132,7 @@ public Expression Parse([CanBeNull] Type resultType, bool createParameterCtor = if (resultType != null) { - if ((expr = ExpressionPromoter.Promote(expr, resultType, true, false)) == null) + if ((expr = _parsingConfig.ExpressionPromoter.Promote(expr, resultType, true, false)) == null) { throw ParseError(exprPos, Res.ExpressionTypeMismatch, TypeHelper.GetTypeName(resultType)); } @@ -352,7 +354,7 @@ Expression ParseIn() var args = new[] { left }; - if (MethodFinder.FindMethod(typeof(IEnumerableSignatures), nameof(IEnumerableSignatures.Contains), false, args, out MethodBase containsSignature) != 1) + if (_methodFinder.FindMethod(typeof(IEnumerableSignatures), nameof(IEnumerableSignatures.Contains), false, args, out MethodBase containsSignature) != 1) { throw ParseError(op.Pos, Res.NoApplicableAggregate, nameof(IEnumerableSignatures.Contains)); } @@ -469,11 +471,11 @@ Expression ParseComparisonOperator() if (left.Type != right.Type) { Expression e; - if ((e = ExpressionPromoter.Promote(right, left.Type, true, false)) != null) + if ((e = _parsingConfig.ExpressionPromoter.Promote(right, left.Type, true, false)) != null) { right = e; } - else if ((e = ExpressionPromoter.Promote(left, right.Type, true, false)) != null) + else if ((e = _parsingConfig.ExpressionPromoter.Promote(left, right.Type, true, false)) != null) { left = e; } @@ -1059,8 +1061,8 @@ Expression GenerateConditional(Expression test, Expression expr1, Expression exp if (expr1.Type != expr2.Type) { - Expression expr1As2 = expr2 != Constants.NullLiteral ? ExpressionPromoter.Promote(expr1, expr2.Type, true, false) : null; - Expression expr2As1 = expr1 != Constants.NullLiteral ? ExpressionPromoter.Promote(expr2, expr1.Type, true, false) : null; + Expression expr1As2 = expr2 != Constants.NullLiteral ? _parsingConfig.ExpressionPromoter.Promote(expr1, expr2.Type, true, false) : null; + Expression expr2As1 = expr1 != Constants.NullLiteral ? _parsingConfig.ExpressionPromoter.Promote(expr2, expr1.Type, true, false) : null; if (expr1As2 != null && expr2As1 == null) { expr1 = expr1As2; @@ -1200,7 +1202,7 @@ private Expression CreateArrayInitializerExpression(List expressions if (newType != null) { - return Expression.NewArrayInit(newType, expressions.Select(expression => ExpressionPromoter.Promote(expression, newType, true, true))); + return Expression.NewArrayInit(newType, expressions.Select(expression => _parsingConfig.ExpressionPromoter.Promote(expression, newType, true, true))); } return Expression.NewArrayInit(expressions.All(expression => expression.Type == expressions[0].Type) ? expressions[0].Type : typeof(object), expressions); @@ -1270,7 +1272,7 @@ private Expression CreateNewExpression(List properties, List)propertyInfos); @@ -1284,7 +1286,7 @@ private Expression CreateNewExpression(List properties, List + public virtual Expression Promote(Expression expr, Type type, bool exact, bool convertExpr) { if (expr.Type == type) { diff --git a/src/System.Linq.Dynamic.Core/Parser/IExpressionPromoter.cs b/src/System.Linq.Dynamic.Core/Parser/IExpressionPromoter.cs new file mode 100644 index 00000000..3dac3764 --- /dev/null +++ b/src/System.Linq.Dynamic.Core/Parser/IExpressionPromoter.cs @@ -0,0 +1,22 @@ +using System.Linq.Expressions; + +namespace System.Linq.Dynamic.Core.Parser +{ + /// + /// Expression promoter is used to promote object or value types + /// to their destination type when an automatic promotion is available + /// such as: int to int? + /// + public interface IExpressionPromoter + { + /// + /// Promote an expression + /// + /// Source expression + /// Destionation data type to promote + /// If the match must be exact + /// Convert expression + /// + Expression Promote(Expression expr, Type type, bool exact, bool convertExpr); + } +} diff --git a/src/System.Linq.Dynamic.Core/Parser/SupportedMethods/MethodFinder.cs b/src/System.Linq.Dynamic.Core/Parser/SupportedMethods/MethodFinder.cs index c1a51610..93b73604 100644 --- a/src/System.Linq.Dynamic.Core/Parser/SupportedMethods/MethodFinder.cs +++ b/src/System.Linq.Dynamic.Core/Parser/SupportedMethods/MethodFinder.cs @@ -4,14 +4,25 @@ namespace System.Linq.Dynamic.Core.Parser.SupportedMethods { - internal static class MethodFinder + internal class MethodFinder { - public static bool ContainsMethod(Type type, string methodName, bool staticAccess, Expression[] args) + private readonly ParsingConfig _parsingConfig; + + /// + /// Get an instance + /// + /// + public MethodFinder(ParsingConfig parsingConfig) + { + _parsingConfig = parsingConfig; + } + + public bool ContainsMethod(Type type, string methodName, bool staticAccess, Expression[] args) { return FindMethod(type, methodName, staticAccess, args, out var _) == 1; } - public static int FindMethod(Type type, string methodName, bool staticAccess, Expression[] args, out MethodBase method) + public int FindMethod(Type type, string methodName, bool staticAccess, Expression[] args, out MethodBase method) { #if !(NETFX_CORE || WINDOWS_APP || DOTNET5_1 || UAP10_0 || NETSTANDARD) BindingFlags flags = BindingFlags.Public | BindingFlags.DeclaredOnly | (staticAccess ? BindingFlags.Static : BindingFlags.Instance); @@ -39,7 +50,7 @@ public static int FindMethod(Type type, string methodName, bool staticAccess, Ex return 0; } - public static int FindBestMethod(IEnumerable methods, Expression[] args, out MethodBase method) + public int FindBestMethod(IEnumerable methods, Expression[] args, out MethodBase method) { MethodData[] applicable = methods. Select(m => new MethodData { MethodBase = m, Parameters = m.GetParameters() }). @@ -73,7 +84,7 @@ public static int FindBestMethod(IEnumerable methods, Expression[] a return applicable.Length; } - public static int FindIndexer(Type type, Expression[] args, out MethodBase method) + public int FindIndexer(Type type, Expression[] args, out MethodBase method) { foreach (Type t in SelfAndBaseTypes(type)) { @@ -99,7 +110,7 @@ public static int FindIndexer(Type type, Expression[] args, out MethodBase metho return 0; } - static bool IsApplicable(MethodData method, Expression[] args) + bool IsApplicable(MethodData method, Expression[] args) { if (method.Parameters.Length != args.Length) { @@ -115,7 +126,7 @@ static bool IsApplicable(MethodData method, Expression[] args) return false; } - Expression promoted = ExpressionPromoter.Promote(args[i], pi.ParameterType, false, method.MethodBase.DeclaringType != typeof(IEnumerableSignatures)); + Expression promoted = this._parsingConfig.ExpressionPromoter.Promote(args[i], pi.ParameterType, false, method.MethodBase.DeclaringType != typeof(IEnumerableSignatures)); if (promoted == null) { return false; @@ -126,7 +137,7 @@ static bool IsApplicable(MethodData method, Expression[] args) return true; } - static bool IsBetterThan(Expression[] args, MethodData first, MethodData second) + bool IsBetterThan(Expression[] args, MethodData first, MethodData second) { bool better = false; for (int i = 0; i < args.Length; i++) @@ -158,7 +169,7 @@ static bool IsBetterThan(Expression[] args, MethodData first, MethodData second) // Return "First" if s -> t1 is a better conversion than s -> t2 // Return "Second" if s -> t2 is a better conversion than s -> t1 // Return "Both" if neither conversion is better - static CompareConversionType CompareConversions(Type source, Type first, Type second) + CompareConversionType CompareConversions(Type source, Type first, Type second) { if (first == second) { @@ -197,7 +208,7 @@ static CompareConversionType CompareConversions(Type source, Type first, Type se return CompareConversionType.Both; } - static IEnumerable SelfAndBaseTypes(Type type) + IEnumerable SelfAndBaseTypes(Type type) { if (type.GetTypeInfo().IsInterface) { @@ -208,7 +219,7 @@ static IEnumerable SelfAndBaseTypes(Type type) return SelfAndBaseClasses(type); } - static IEnumerable SelfAndBaseClasses(Type type) + IEnumerable SelfAndBaseClasses(Type type) { while (type != null) { @@ -217,7 +228,7 @@ static IEnumerable SelfAndBaseClasses(Type type) } } - static void AddInterface(List types, Type type) + void AddInterface(List types, Type type) { if (!types.Contains(type)) { diff --git a/src/System.Linq.Dynamic.Core/ParsingConfig.cs b/src/System.Linq.Dynamic.Core/ParsingConfig.cs index d9a70991..7c71f863 100644 --- a/src/System.Linq.Dynamic.Core/ParsingConfig.cs +++ b/src/System.Linq.Dynamic.Core/ParsingConfig.cs @@ -1,4 +1,5 @@ using System.Linq.Dynamic.Core.CustomTypeProviders; +using System.Linq.Dynamic.Core.Parser; namespace System.Linq.Dynamic.Core { @@ -22,6 +23,8 @@ public class ParsingConfig private IDynamicLinkCustomTypeProvider _customTypeProvider; + private IExpressionPromoter _expressionPromoter; + /// /// Gets or sets the . /// @@ -46,6 +49,25 @@ public IDynamicLinkCustomTypeProvider CustomTypeProvider } } + /// + /// Gets or sets the . + /// + public IExpressionPromoter ExpressionPromoter + { + get + { + return _expressionPromoter ?? (_expressionPromoter = new ExpressionPromoter()); + } + + set + { + if (_expressionPromoter != value) + { + _expressionPromoter = value; + } + } + } + /// /// 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 ~). 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 14a17143..844be632 100644 --- a/test/EntityFramework.DynamicLinq.Tests.net452/EntityFramework.DynamicLinq.Tests.net452.csproj +++ b/test/EntityFramework.DynamicLinq.Tests.net452/EntityFramework.DynamicLinq.Tests.net452.csproj @@ -34,6 +34,9 @@ 4 + + ..\..\packages\Castle.Core.4.3.1\lib\net45\Castle.Core.dll + ..\..\packages\EntityFramework.6.1.3\lib\net45\EntityFramework.dll @@ -58,21 +61,31 @@ ..\..\packages\MongoDB.Driver.Core.2.4.4\lib\net45\MongoDB.Driver.Core.dll + + ..\..\packages\Moq.4.10.0\lib\net45\Moq.dll + ..\..\packages\Newtonsoft.Json.9.0.1\lib\net45\Newtonsoft.Json.dll - - ..\..\packages\NFluent.2.0.0-alpha\lib\net40\NFluent.dll + + ..\..\packages\NFluent.2.1.1\lib\net45\NFluent.dll ..\..\packages\QueryInterceptor.Core.1.0.5.0\lib\net452\QueryInterceptor.Core.dll + ..\..\packages\System.Runtime.InteropServices.RuntimeInformation.4.0.0\lib\net45\System.Runtime.InteropServices.RuntimeInformation.dll + + ..\..\packages\System.Threading.Tasks.Extensions.4.3.0\lib\portable-net45+win8+wp8+wpa81\System.Threading.Tasks.Extensions.dll + + + ..\..\packages\System.ValueTuple.4.4.0\lib\netstandard1.0\System.ValueTuple.dll + @@ -189,6 +202,9 @@ Entities\User.cs + + ExpressionPromoterTests.cs + ExpressionTests.cs diff --git a/test/EntityFramework.DynamicLinq.Tests.net452/packages.config b/test/EntityFramework.DynamicLinq.Tests.net452/packages.config index 53ee1a6e..cd072130 100644 --- a/test/EntityFramework.DynamicLinq.Tests.net452/packages.config +++ b/test/EntityFramework.DynamicLinq.Tests.net452/packages.config @@ -1,5 +1,6 @@  + @@ -7,10 +8,13 @@ + - + + + diff --git a/test/EntityFramework.DynamicLinq.Tests/EntityFramework.DynamicLinq.Tests.csproj b/test/EntityFramework.DynamicLinq.Tests/EntityFramework.DynamicLinq.Tests.csproj index 6eaa392d..6eee6e5f 100644 --- a/test/EntityFramework.DynamicLinq.Tests/EntityFramework.DynamicLinq.Tests.csproj +++ b/test/EntityFramework.DynamicLinq.Tests/EntityFramework.DynamicLinq.Tests.csproj @@ -1,51 +1,52 @@  - - Stef Heyenrath - net461 - EF - EntityFramework.DynamicLinq.Tests - EntityFramework.DynamicLinq.Tests - true - false - false - false - {BF97CB1B-5043-4256-8F42-CF3A4F3863BE} - + + Stef Heyenrath + net461 + EF + EntityFramework.DynamicLinq.Tests + EntityFramework.DynamicLinq.Tests + true + false + false + false + {BF97CB1B-5043-4256-8F42-CF3A4F3863BE} + - - - - - - - - - - - + + + + + + + + + + + - - - - + + + + - - - - - - - - - - - - + + + + + + + + + + + + + - - - - + + + + \ No newline at end of file diff --git a/test/System.Linq.Dynamic.Core.Tests/DynamicExpressionParserTests.cs b/test/System.Linq.Dynamic.Core.Tests/DynamicExpressionParserTests.cs index fe0fd788..36697269 100644 --- a/test/System.Linq.Dynamic.Core.Tests/DynamicExpressionParserTests.cs +++ b/test/System.Linq.Dynamic.Core.Tests/DynamicExpressionParserTests.cs @@ -88,7 +88,7 @@ public static class StaticHelper } } - private class TestCustomTypeProvider : AbstractDynamicLinqCustomTypeProvider, IDynamicLinkCustomTypeProvider + public class TestCustomTypeProvider : AbstractDynamicLinqCustomTypeProvider, IDynamicLinkCustomTypeProvider { private HashSet _customTypes; diff --git a/test/System.Linq.Dynamic.Core.Tests/ExpressionPromoterTests.cs b/test/System.Linq.Dynamic.Core.Tests/ExpressionPromoterTests.cs index f381d679..ef6974b0 100644 --- a/test/System.Linq.Dynamic.Core.Tests/ExpressionPromoterTests.cs +++ b/test/System.Linq.Dynamic.Core.Tests/ExpressionPromoterTests.cs @@ -1,6 +1,57 @@ -namespace System.Linq.Dynamic.Core.Tests +using Moq; +using System.Collections.Generic; +using System.Linq.Dynamic.Core.CustomTypeProviders; +using System.Linq.Dynamic.Core.Parser; +using System.Linq.Expressions; +using Xunit; + +namespace System.Linq.Dynamic.Core.Tests { - class ExpressionPromoterTests + public class ExpressionPromoterTests { + public class SampleDto + { + public Guid data { get; set; } + } + + private readonly Mock _expressionPromoterMock; + private readonly Mock _dynamicLinkCustomTypeProviderMock; + + public ExpressionPromoterTests() + { + _dynamicLinkCustomTypeProviderMock = new Mock(); + _dynamicLinkCustomTypeProviderMock.Setup(d => d.GetCustomTypes()).Returns(new HashSet()); + _dynamicLinkCustomTypeProviderMock.Setup(d => d.ResolveType(It.IsAny())).Returns(typeof(SampleDto)); + + _expressionPromoterMock = new Mock(); + _expressionPromoterMock.Setup(e => e.Promote(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny())).Returns(Expression.Constant(Guid.NewGuid())); + } + + [Fact] + public void DynamicExpressionParser_ParseLambda_WithCustomExpressionPromoter() + { + // Assign + var parsingConfig = new ParsingConfig() + { + AllowNewToEvaluateAnyType = true, + CustomTypeProvider = _dynamicLinkCustomTypeProviderMock.Object, + ExpressionPromoter = _expressionPromoterMock.Object + }; + + // Act + string query = $"new {typeof(SampleDto).FullName}(@0 as data)"; + LambdaExpression expression = DynamicExpressionParser.ParseLambda(parsingConfig, null, query, new object[] { Guid.NewGuid().ToString() }); + Delegate del = expression.Compile(); + SampleDto result = (SampleDto)del.DynamicInvoke(); + + // Assert + Assert.NotNull(result); + + // Verify + _dynamicLinkCustomTypeProviderMock.Verify(d => d.GetCustomTypes(), Times.Once); + _dynamicLinkCustomTypeProviderMock.Verify(d => d.ResolveType($"{typeof(SampleDto).FullName}"), Times.Once); + + _expressionPromoterMock.Verify(e => e.Promote(It.IsAny(), typeof(Guid), true, true), Times.Once); + } } } diff --git a/test/System.Linq.Dynamic.Core.Tests/System.Linq.Dynamic.Core.Tests.csproj b/test/System.Linq.Dynamic.Core.Tests/System.Linq.Dynamic.Core.Tests.csproj index f09d1ec2..fe1a38a2 100644 --- a/test/System.Linq.Dynamic.Core.Tests/System.Linq.Dynamic.Core.Tests.csproj +++ b/test/System.Linq.Dynamic.Core.Tests/System.Linq.Dynamic.Core.Tests.csproj @@ -35,6 +35,7 @@ 2.3.1 +