diff --git a/src/System.Linq.Dynamic.Core/ParsingConfig.cs b/src/System.Linq.Dynamic.Core/ParsingConfig.cs index 66f41fab..96f6d46c 100644 --- a/src/System.Linq.Dynamic.Core/ParsingConfig.cs +++ b/src/System.Linq.Dynamic.Core/ParsingConfig.cs @@ -4,221 +4,220 @@ using System.Linq.Dynamic.Core.CustomTypeProviders; using System.Linq.Dynamic.Core.Parser; -namespace System.Linq.Dynamic.Core +namespace System.Linq.Dynamic.Core; + +/// +/// Configuration class for System.Linq.Dynamic.Core. +/// +public class ParsingConfig { /// - /// Configuration class for System.Linq.Dynamic.Core. + /// Default ParsingConfig /// - public class ParsingConfig + public static ParsingConfig Default { get; } = new ParsingConfig(); + + /// + /// Default ParsingConfig for EntityFramework Core 2.1 and higher + /// + public static ParsingConfig DefaultEFCore21 { get; } = new ParsingConfig { - /// - /// Default ParsingConfig - /// - public static ParsingConfig Default { get; } = new ParsingConfig(); - - /// - /// Default ParsingConfig for EntityFramework Core 2.1 and higher - /// - public static ParsingConfig DefaultEFCore21 { get; } = new ParsingConfig - { - EvaluateGroupByAtDatabase = true - }; + EvaluateGroupByAtDatabase = true + }; - /// Gets or sets if parameter, method, and properties resolution should be case sensitive or not (false by default). - public bool IsCaseSensitive { get; set; } + /// Gets or sets if parameter, method, and properties resolution should be case sensitive or not (false by default). + public bool IsCaseSensitive { get; set; } - /// - /// Default ParsingConfig for CosmosDb - /// - public static ParsingConfig DefaultCosmosDb { get; } = new ParsingConfig - { - RenameEmptyParameterExpressionNames = true - }; + /// + /// Default ParsingConfig for CosmosDb + /// + public static ParsingConfig DefaultCosmosDb { get; } = new ParsingConfig + { + RenameEmptyParameterExpressionNames = true + }; - private IDynamicLinkCustomTypeProvider? _customTypeProvider; + private IDynamicLinkCustomTypeProvider? _customTypeProvider; - private IExpressionPromoter? _expressionPromoter; + private IExpressionPromoter? _expressionPromoter; - private IQueryableAnalyzer? _queryableAnalyzer; + private IQueryableAnalyzer? _queryableAnalyzer; - /// - /// Gets or sets the . - /// - public IDynamicLinkCustomTypeProvider CustomTypeProvider + /// + /// Gets or sets the . + /// + public IDynamicLinkCustomTypeProvider CustomTypeProvider + { + get { - get - { #if !( WINDOWS_APP || UAP10_0 || NETSTANDARD) - // only use DefaultDynamicLinqCustomTypeProvider for full .NET Framework and NET Core App 2.x - return _customTypeProvider ??= new DefaultDynamicLinqCustomTypeProvider(); + // only use DefaultDynamicLinqCustomTypeProvider for full .NET Framework and NET Core App 2.x + return _customTypeProvider ??= new DefaultDynamicLinqCustomTypeProvider(); #else - return _customTypeProvider; + return _customTypeProvider; #endif - } + } - set + set + { + // ReSharper disable once RedundantCheckBeforeAssignment + if (_customTypeProvider != value) { - // ReSharper disable once RedundantCheckBeforeAssignment - if (_customTypeProvider != value) - { - _customTypeProvider = value; - } + _customTypeProvider = value; } } + } - /// - /// Gets or sets the . - /// - public IExpressionPromoter ExpressionPromoter - { - get => _expressionPromoter ??= new ExpressionPromoter(this); + /// + /// Gets or sets the . + /// + public IExpressionPromoter ExpressionPromoter + { + get => _expressionPromoter ??= new ExpressionPromoter(this); - set + set + { + // ReSharper disable once RedundantCheckBeforeAssignment + if (_expressionPromoter != value) { - // ReSharper disable once RedundantCheckBeforeAssignment - if (_expressionPromoter != value) - { - _expressionPromoter = value; - } + _expressionPromoter = value; } } + } - /// - /// Gets or sets the . - /// - public IQueryableAnalyzer QueryableAnalyzer + /// + /// Gets or sets the . + /// + public IQueryableAnalyzer QueryableAnalyzer + { + get { - get - { - return _queryableAnalyzer ??= new DefaultQueryableAnalyzer(); - } + return _queryableAnalyzer ??= new DefaultQueryableAnalyzer(); + } - set + set + { + // ReSharper disable once RedundantCheckBeforeAssignment + if (_queryableAnalyzer != value) { - // ReSharper disable once RedundantCheckBeforeAssignment - if (_queryableAnalyzer != value) - { - _queryableAnalyzer = value; - } + _queryableAnalyzer = 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 ~). - /// - /// Default value is false. - /// - public bool AreContextKeywordsEnabled { get; set; } = true; - - /// - /// Gets or sets a value indicating whether the EntityFramework version supports evaluating GroupBy at database level. - /// See https://docs.microsoft.com/en-us/ef/core/what-is-new/ef-core-2.1#linq-groupby-translation - /// - /// Remark: when this setting is set to 'true', make sure to supply this ParsingConfig as first parameter on the extension methods. - /// - /// Default value is false. - /// - public bool EvaluateGroupByAtDatabase { get; set; } - - /// - /// Use Parameterized Names in generated dynamic SQL query. - /// See https://github.com/graeme-hill/gblog/blob/master/source_content/articles/2014.139_entity-framework-dynamic-queries-and-parameterization.mkd - /// - /// Default value is false. - /// - public bool UseParameterizedNamesInDynamicQuery { get; set; } = false; - - /// - /// 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; - - /// - /// Prevents any System.Linq.Expressions.ParameterExpression.Name value from being empty by substituting a random 16 character word. - /// - /// Default value is false. - /// - public bool RenameEmptyParameterExpressionNames { get; set; } - - /// - /// By default when a member is not found in a type and the type has a string based index accessor it will be parsed as an index accessor. Use - /// this flag to disable this behaviour and have parsing fail when parsing an expression - /// where a member access on a non existing member happens. - /// - /// Default value is false. - /// - public bool DisableMemberAccessToIndexAccessorFallback { get; set; } = false; - - /// - /// By default finding types by a simple name is not supported. - /// Use this flag to use the CustomTypeProvider to resolve types by a simple name like "Employee" instead of "MyDatabase.Entities.Employee". - /// Note that a first matching type is returned and this functionality needs to scan all types from all assemblies, so use with caution. - /// - /// Default value is false. - /// - public bool ResolveTypesBySimpleName { get; set; } = false; - - /// - /// Support enumeration-types from the System namespace in mscorlib. An example could be "StringComparison". - /// - /// Default value is true. - /// - public bool SupportEnumerationsFromSystemNamespace { get; set; } = true; - - /// - /// By default DateTime (like 'Fri, 10 May 2019 11:03:17 GMT') is parsed as local time. - /// Use this flag to parse all DateTime strings as UTC. - /// - /// Default value is false. - /// - public bool DateTimeIsParsedAsUTC { get; set; } = false; - - /// - /// The number parsing culture. - /// - /// Default value is CultureInfo.InvariantCulture - /// - public CultureInfo? NumberParseCulture { get; set; } = CultureInfo.InvariantCulture; - - /// - /// Additional TypeConverters - /// - public IDictionary? TypeConverters { get; set; } - - /// - /// When using the NullPropagating function np(...), use a "default value" for non-nullable value types instead of "null value". - /// - /// Default value is false. - /// - public bool NullPropagatingUseDefaultValueForNonNullableValueTypes { get; set; } = false; - - /// - /// Support casting to a full qualified type using a string (double quoted value). - /// - /// var result = queryable.Select($"\"System.DateTime\"(LastUpdate)"); - /// - /// - /// Default value is true. - /// - public bool SupportCastingToFullyQualifiedTypeAsString { get; set; } = true; - - /// - /// When the type and property have the same name the parser takes the property instead of type when this setting is set to true. - /// - /// The value from this setting should also be set to true when ExtensionMethods are used. - /// - /// Default value is false. - /// - public bool PrioritizePropertyOrFieldOverTheType { get; set; } } + + /// + /// 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 false. + /// + public bool AreContextKeywordsEnabled { get; set; } = true; + + /// + /// Gets or sets a value indicating whether the EntityFramework version supports evaluating GroupBy at database level. + /// See https://docs.microsoft.com/en-us/ef/core/what-is-new/ef-core-2.1#linq-groupby-translation + /// + /// Remark: when this setting is set to 'true', make sure to supply this ParsingConfig as first parameter on the extension methods. + /// + /// Default value is false. + /// + public bool EvaluateGroupByAtDatabase { get; set; } + + /// + /// Use Parameterized Names in generated dynamic SQL query. + /// See https://github.com/graeme-hill/gblog/blob/master/source_content/articles/2014.139_entity-framework-dynamic-queries-and-parameterization.mkd + /// + /// Default value is false. + /// + public bool UseParameterizedNamesInDynamicQuery { get; set; } = false; + + /// + /// 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; + + /// + /// Prevents any System.Linq.Expressions.ParameterExpression.Name value from being empty by substituting a random 16 character word. + /// + /// Default value is false. + /// + public bool RenameEmptyParameterExpressionNames { get; set; } + + /// + /// By default when a member is not found in a type and the type has a string based index accessor it will be parsed as an index accessor. Use + /// this flag to disable this behaviour and have parsing fail when parsing an expression + /// where a member access on a non existing member happens. + /// + /// Default value is false. + /// + public bool DisableMemberAccessToIndexAccessorFallback { get; set; } = false; + + /// + /// By default finding types by a simple name is not supported. + /// Use this flag to use the CustomTypeProvider to resolve types by a simple name like "Employee" instead of "MyDatabase.Entities.Employee". + /// Note that a first matching type is returned and this functionality needs to scan all types from all assemblies, so use with caution. + /// + /// Default value is false. + /// + public bool ResolveTypesBySimpleName { get; set; } = false; + + /// + /// Support enumeration-types from the System namespace in mscorlib. An example could be "StringComparison". + /// + /// Default value is true. + /// + public bool SupportEnumerationsFromSystemNamespace { get; set; } = true; + + /// + /// By default DateTime (like 'Fri, 10 May 2019 11:03:17 GMT') is parsed as local time. + /// Use this flag to parse all DateTime strings as UTC. + /// + /// Default value is false. + /// + public bool DateTimeIsParsedAsUTC { get; set; } = false; + + /// + /// The number parsing culture. + /// + /// Default value is CultureInfo.InvariantCulture + /// + public CultureInfo? NumberParseCulture { get; set; } = CultureInfo.InvariantCulture; + + /// + /// Additional TypeConverters + /// + public IDictionary? TypeConverters { get; set; } + + /// + /// When using the NullPropagating function np(...), use a "default value" for non-nullable value types instead of "null value". + /// + /// Default value is false. + /// + public bool NullPropagatingUseDefaultValueForNonNullableValueTypes { get; set; } = false; + + /// + /// Support casting to a full qualified type using a string (double quoted value). + /// + /// var result = queryable.Select($"\"System.DateTime\"(LastUpdate)"); + /// + /// + /// Default value is true. + /// + public bool SupportCastingToFullyQualifiedTypeAsString { get; set; } = true; + + /// + /// When the type and property have the same name the parser takes the property instead of type when this setting is set to true. + /// + /// This setting is also used for calling ExtensionMethods. + /// + /// Default value is true. + /// + public bool PrioritizePropertyOrFieldOverTheType { get; set; } = true; } \ 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 83d99980..17309c5b 100644 --- a/test/System.Linq.Dynamic.Core.Tests/DynamicExpressionParserTests.cs +++ b/test/System.Linq.Dynamic.Core.Tests/DynamicExpressionParserTests.cs @@ -1082,6 +1082,20 @@ public void DynamicExpressionParser_ParseLambda_With_DateTime_Equals_String() Assert.True(result); } + [Fact] + public void DynamicExpressionParser_ParseLambda_With_DateTime_UtcNow_AddHours_ToString_Issue675() + { + // Arrange + var expressionText = "DateTime.UtcNow.AddHours(2).ToString(\"o\")"; + var parameterExpression = Expression.Parameter(typeof(string)); + + // Act + var lambda = DynamicExpressionParser.ParseLambda(new[] { parameterExpression }, typeof(object), expressionText); + + // Assert + lambda.Should().NotBeNull(); + } + [Fact] public void DynamicExpressionParser_ParseLambda_With_Guid_Equals_String() { @@ -1459,7 +1473,7 @@ public void DynamicExpressionParser_ParseLambda_String_TrimEnd_1_Parameter() result.Should().BeTrue(); } - public class DefaultDynamicLinqCustomTypeProviderForGenericExtensionMethod : CustomTypeProviders.DefaultDynamicLinqCustomTypeProvider + public class DefaultDynamicLinqCustomTypeProviderForGenericExtensionMethod : DefaultDynamicLinqCustomTypeProvider { public override HashSet GetCustomTypes() => new HashSet(base.GetCustomTypes()) { typeof(Methods), typeof(MethodsItemExtension) }; } diff --git a/test/System.Linq.Dynamic.Core.Tests/Parser/ExpressionParserTests.TypeAccess.cs b/test/System.Linq.Dynamic.Core.Tests/Parser/ExpressionParserTests.TypeAccess.cs index 0129e36e..9c92c1b3 100644 --- a/test/System.Linq.Dynamic.Core.Tests/Parser/ExpressionParserTests.TypeAccess.cs +++ b/test/System.Linq.Dynamic.Core.Tests/Parser/ExpressionParserTests.TypeAccess.cs @@ -4,229 +4,228 @@ using FluentAssertions; using Xunit; -namespace System.Linq.Dynamic.Core.Tests.Parser +namespace System.Linq.Dynamic.Core.Tests.Parser; + +partial class ExpressionParserTests { - partial class ExpressionParserTests + [Fact] + public void ParseTypeAccess_Via_Constructor_CharAndInt_To_String() + { + // Arrange + var parameter = Expression.Parameter(typeof(string)); + + // Act + var parser = new ExpressionParser(new[] { parameter }, "string('c', 3)", new object[] { }, ParsingConfig.Default); + var expression = parser.Parse(typeof(string)); + + // Assert + expression.ToString().Should().Be("new String(c, 3)"); + } + + [Theory] + [InlineData(123, "new DateTime(123)")] + [InlineData(633979008000000000, "new DateTime(633979008000000000)")] + public void ParseTypeAccess_Via_Constructor_Ticks_To_DateTime(object ticks, string result) + { + // Arrange + var parameter = Expression.Parameter(typeof(DateTime)); + + // Act + var parser = new ExpressionParser(new[] { parameter }, $"DateTime({ticks})", new object[] { }, ParsingConfig.Default); + var expression = parser.Parse(typeof(DateTime)); + + // Assert + expression.ToString().Should().Be(result); + } + + [Fact] + public void ParseTypeAccess_Via_Constructor_String_To_DateTime_Valid() + { + // Arrange + string str = "\"2020-10-31 09:15:11\""; + var parameter = Expression.Parameter(typeof(DateTime)); + + // Act + var parser = new ExpressionParser(new[] { parameter }, $"DateTime({str})", new object[] { }, ParsingConfig.Default); + var expression = parser.Parse(typeof(DateTime)); + + // Assert + expression.ToString().Should().NotBeEmpty(); + } + + [Fact] + public void ParseTypeAccess_Via_Constructor_Arguments_To_DateTime_Valid() { - [Fact] - public void ParseTypeAccess_Via_Constructor_CharAndInt_To_String() - { - // Arrange - var parameter = Expression.Parameter(typeof(string)); - - // Act - var parser = new ExpressionParser(new[] { parameter }, $"string('c', 3)", new object[] { }, ParsingConfig.Default); - var expression = parser.Parse(typeof(string)); - - // Assert - expression.ToString().Should().Be("new String(c, 3)"); - } - - [Theory] - [InlineData(123, "new DateTime(123)")] - [InlineData(633979008000000000, "new DateTime(633979008000000000)")] - public void ParseTypeAccess_Via_Constructor_Ticks_To_DateTime(object ticks, string result) - { - // Arrange - var parameter = Expression.Parameter(typeof(DateTime)); - - // Act - var parser = new ExpressionParser(new[] { parameter }, $"DateTime({ticks})", new object[] { }, ParsingConfig.Default); - var expression = parser.Parse(typeof(DateTime)); - - // Assert - expression.ToString().Should().Be(result); - } - - [Fact] - public void ParseTypeAccess_Via_Constructor_String_To_DateTime_Valid() - { - // Arrange - string str = "\"2020-10-31 09:15:11\""; - var parameter = Expression.Parameter(typeof(DateTime)); - - // Act - var parser = new ExpressionParser(new[] { parameter }, $"DateTime({str})", new object[] { }, ParsingConfig.Default); - var expression = parser.Parse(typeof(DateTime)); - - // Assert - expression.ToString().Should().NotBeEmpty(); - } - - [Fact] - public void ParseTypeAccess_Via_Constructor_Arguments_To_DateTime_Valid() - { - // Arrange - var arguments = "2022, 10, 31, 9, 15, 11"; - var parameter = Expression.Parameter(typeof(DateTime)); - - // Act - var parser = new ExpressionParser(new[] { parameter }, $"DateTime({arguments})", new object[] { }, ParsingConfig.Default); - var expression = parser.Parse(typeof(DateTime)); - - // Assert - expression.ToString().Should().NotBeEmpty(); - } - - [Theory] - [InlineData(null)] - [InlineData("\"abc\"")] - public void ParseTypeAccess_Via_Constructor_Any_To_DateTime_Invalid(object any) - { - // Arrange - var parameter = Expression.Parameter(typeof(DateTime)); - - // Act - var parser = new ExpressionParser(new[] { parameter }, $"DateTime({any})", new object[] { }, ParsingConfig.Default); - Action a = () => parser.Parse(typeof(DateTime)); - - // Assert - a.Should().Throw(); - } + // Arrange + var arguments = "2022, 10, 31, 9, 15, 11"; + var parameter = Expression.Parameter(typeof(DateTime)); + + // Act + var parser = new ExpressionParser(new[] { parameter }, $"DateTime({arguments})", new object[] { }, ParsingConfig.Default); + var expression = parser.Parse(typeof(DateTime)); + + // Assert + expression.ToString().Should().NotBeEmpty(); + } + + [Theory] + [InlineData(null)] + [InlineData("\"abc\"")] + public void ParseTypeAccess_Via_Constructor_Any_To_DateTime_Invalid(object any) + { + // Arrange + var parameter = Expression.Parameter(typeof(DateTime)); + + // Act + var parser = new ExpressionParser(new[] { parameter }, $"DateTime({any})", new object[] { }, ParsingConfig.Default); + Action a = () => parser.Parse(typeof(DateTime)); + + // Assert + a.Should().Throw(); + } #if NET6_0_OR_GREATER - [Fact] - public void ParseTypeAccess_Via_Constructor_String_To_DateOnly_Valid() - { - // Arrange - string str = "\"2020-10-31\""; - var parameter = Expression.Parameter(typeof(DateOnly)); - - // Act - var parser = new ExpressionParser(new[] { parameter }, $"DateOnly({str})", new object[] { }, ParsingConfig.Default); - var expression = parser.Parse(typeof(DateOnly)); - - // Assert - expression.ToString().Should().NotBeEmpty(); - } - - [Fact] - public void ParseTypeAccess_Via_Constructor_Arguments_To_DateOnly_Valid() - { - // Arrange - var arguments = "2022, 10, 31"; - var parameter = Expression.Parameter(typeof(DateOnly)); - - // Act - var parser = new ExpressionParser(new[] { parameter }, $"DateOnly({arguments})", new object[] { }, ParsingConfig.Default); - var expression = parser.Parse(typeof(DateOnly)); - - // Assert - expression.ToString().Should().NotBeEmpty(); - } - - [Theory] - [InlineData(null)] - [InlineData("\"abc\"")] - public void ParseTypeAccess_Via_Constructor_Any_To_DateOnly_Invalid(object any) - { - // Arrange - var parameter = Expression.Parameter(typeof(DateOnly)); - - // Act - var parser = new ExpressionParser(new[] { parameter }, $"DateOnly({any})", new object[] { }, ParsingConfig.Default); - Action a = () => parser.Parse(typeof(DateOnly)); - - // Assert - a.Should().Throw(); - } - - [Fact] - public void ParseTypeAccess_Via_Constructor_String_To_TimeOnly_Valid() - { - // Arrange - string str = "\"09:15:11\""; - var parameter = Expression.Parameter(typeof(TimeOnly)); - - // Act - var parser = new ExpressionParser(new[] { parameter }, $"TimeOnly({str})", new object[] { }, ParsingConfig.Default); - var expression = parser.Parse(typeof(TimeOnly)); - - // Assert - expression.ToString().Should().NotBeEmpty(); - } - - [Fact] - public void ParseTypeAccess_Via_Constructor_Arguments_To_TimeOnly_Valid() - { - // Arrange - var arguments = "9, 15, 11"; - var parameter = Expression.Parameter(typeof(TimeOnly)); - - // Act - var parser = new ExpressionParser(new[] { parameter }, $"TimeOnly({arguments})", new object[] { }, ParsingConfig.Default); - var expression = parser.Parse(typeof(TimeOnly)); - - // Assert - expression.ToString().Should().NotBeEmpty(); - } - - [Theory] - [InlineData(null)] - [InlineData("\"abc\"")] - public void ParseTypeAccess_Via_Constructor_Any_To_TimeOnly_Invalid(object any) - { - // Arrange - var parameter = Expression.Parameter(typeof(TimeOnly)); - - // Act - var parser = new ExpressionParser(new[] { parameter }, $"TimeOnly({any})", new object[] { }, ParsingConfig.Default); - Action a = () => parser.Parse(typeof(TimeOnly)); - - // Assert - a.Should().Throw(); - } + [Fact] + public void ParseTypeAccess_Via_Constructor_String_To_DateOnly_Valid() + { + // Arrange + string str = "\"2020-10-31\""; + var parameter = Expression.Parameter(typeof(DateOnly)); + + // Act + var parser = new ExpressionParser(new[] { parameter }, $"DateOnly({str})", new object[] { }, ParsingConfig.Default); + var expression = parser.Parse(typeof(DateOnly)); + + // Assert + expression.ToString().Should().NotBeEmpty(); + } + + [Fact] + public void ParseTypeAccess_Via_Constructor_Arguments_To_DateOnly_Valid() + { + // Arrange + var arguments = "2022, 10, 31"; + var parameter = Expression.Parameter(typeof(DateOnly)); + + // Act + var parser = new ExpressionParser(new[] { parameter }, $"DateOnly({arguments})", new object[] { }, ParsingConfig.Default); + var expression = parser.Parse(typeof(DateOnly)); + + // Assert + expression.ToString().Should().NotBeEmpty(); + } + + [Theory] + [InlineData(null)] + [InlineData("\"abc\"")] + public void ParseTypeAccess_Via_Constructor_Any_To_DateOnly_Invalid(object any) + { + // Arrange + var parameter = Expression.Parameter(typeof(DateOnly)); + + // Act + var parser = new ExpressionParser(new[] { parameter }, $"DateOnly({any})", new object[] { }, ParsingConfig.Default); + Action a = () => parser.Parse(typeof(DateOnly)); + + // Assert + a.Should().Throw(); + } + + [Fact] + public void ParseTypeAccess_Via_Constructor_String_To_TimeOnly_Valid() + { + // Arrange + string str = "\"09:15:11\""; + var parameter = Expression.Parameter(typeof(TimeOnly)); + + // Act + var parser = new ExpressionParser(new[] { parameter }, $"TimeOnly({str})", new object[] { }, ParsingConfig.Default); + var expression = parser.Parse(typeof(TimeOnly)); + + // Assert + expression.ToString().Should().NotBeEmpty(); + } + + [Fact] + public void ParseTypeAccess_Via_Constructor_Arguments_To_TimeOnly_Valid() + { + // Arrange + var arguments = "9, 15, 11"; + var parameter = Expression.Parameter(typeof(TimeOnly)); + + // Act + var parser = new ExpressionParser(new[] { parameter }, $"TimeOnly({arguments})", new object[] { }, ParsingConfig.Default); + var expression = parser.Parse(typeof(TimeOnly)); + + // Assert + expression.ToString().Should().NotBeEmpty(); + } + + [Theory] + [InlineData(null)] + [InlineData("\"abc\"")] + public void ParseTypeAccess_Via_Constructor_Any_To_TimeOnly_Invalid(object any) + { + // Arrange + var parameter = Expression.Parameter(typeof(TimeOnly)); + + // Act + var parser = new ExpressionParser(new[] { parameter }, $"TimeOnly({any})", new object[] { }, ParsingConfig.Default); + Action a = () => parser.Parse(typeof(TimeOnly)); + + // Assert + a.Should().Throw(); + } #endif - [Fact] - public void ParseTypeAccess_Via_Constructor_String_To_Uri() - { - // Arrange - string selector = "Uri(\"https://www.example.com/\")"; - var parameter = Expression.Parameter(typeof(Uri)); - - // Act - var parser = new ExpressionParser(new[] { parameter }, selector, new object[] { }, ParsingConfig.Default); - var expression = parser.Parse(typeof(Uri)); - - // Assert - expression.ToString().Should().Be("https://www.example.com/"); - } - - [Fact] - public void ParseTypeAccess_Via_Constructor_String_And_UriKind_To_Uri() - { - // Arrange - string selector = "Uri(\"https://www.example.com/\", UriKind.Absolute)"; - var parameter = Expression.Parameter(typeof(Uri)); - - // Act - var parser = new ExpressionParser(new[] { parameter }, selector, new object[] { }, ParsingConfig.Default); - var expression = parser.Parse(typeof(Uri)); - - // Assert - expression.ToString().Should().Be("new Uri(\"https://www.example.com/\", Absolute)"); - } - - [Theory] - [InlineData("new(1 as a, 2 as b)", "new*(a = 1, b = 2)")] - [InlineData("new(2 as b, 1 as a)", "new*(a = 1, b = 2)")] - public void ParseTypeAccess_Via_Constructor_DynamicType_To_String(string newExpression, string newExpression2) - { - // Arrange - var parameter = Expression.Parameter(typeof(int)); - var parameter2 = Expression.Parameter(typeof(int)); - var returnType = DynamicClassFactory.CreateType(new List { - new DynamicProperty("a", typeof(int)), - new DynamicProperty("b", typeof(int)) - }); - - // Act - var parser = new ExpressionParser(new[] { parameter, parameter2 }, newExpression, new object[] { }, ParsingConfig.Default); - - var expression = parser.Parse(returnType); - // Assert - expression.ToString().Should().Match(newExpression2); - } + [Fact] + public void ParseTypeAccess_Via_Constructor_String_To_Uri() + { + // Arrange + string selector = "Uri(\"https://www.example.com/\")"; + var parameter = Expression.Parameter(typeof(Uri)); + + // Act + var parser = new ExpressionParser(new[] { parameter }, selector, new object[] { }, ParsingConfig.Default); + var expression = parser.Parse(typeof(Uri)); + + // Assert + expression.ToString().Should().Be("https://www.example.com/"); + } + + [Fact] + public void ParseTypeAccess_Via_Constructor_String_And_UriKind_To_Uri() + { + // Arrange + string selector = "Uri(\"https://www.example.com/\", UriKind.Absolute)"; + var parameter = Expression.Parameter(typeof(Uri)); + + // Act + var parser = new ExpressionParser(new[] { parameter }, selector, new object[] { }, ParsingConfig.Default); + var expression = parser.Parse(typeof(Uri)); + + // Assert + expression.ToString().Should().Be("new Uri(\"https://www.example.com/\", Absolute)"); + } + + [Theory] + [InlineData("new(1 as a, 2 as b)", "new*(a = 1, b = 2)")] + [InlineData("new(2 as b, 1 as a)", "new*(a = 1, b = 2)")] + public void ParseTypeAccess_Via_Constructor_DynamicType_To_String(string newExpression, string newExpression2) + { + // Arrange + var parameter = Expression.Parameter(typeof(int)); + var parameter2 = Expression.Parameter(typeof(int)); + var returnType = DynamicClassFactory.CreateType(new List { + new DynamicProperty("a", typeof(int)), + new DynamicProperty("b", typeof(int)) + }); + + // Act + var parser = new ExpressionParser(new[] { parameter, parameter2 }, newExpression, new object[] { }, ParsingConfig.Default); + + var expression = parser.Parse(returnType); + // Assert + expression.ToString().Should().Match(newExpression2); } -} +} \ No newline at end of file diff --git a/test/System.Linq.Dynamic.Core.Tests/Parser/ExpressionParserTests.cs b/test/System.Linq.Dynamic.Core.Tests/Parser/ExpressionParserTests.cs index b32c151c..d246327a 100644 --- a/test/System.Linq.Dynamic.Core.Tests/Parser/ExpressionParserTests.cs +++ b/test/System.Linq.Dynamic.Core.Tests/Parser/ExpressionParserTests.cs @@ -172,9 +172,9 @@ public void Parse_CastStringIntShouldReturnConstantExpression(string expression, [Theory] #if NET452 - [InlineData("int?(5)", typeof(int?), "Convert(5)")] - [InlineData("int?(null)", typeof(int?), "Convert(null)")] - [InlineData("string(null)", typeof(string), "Convert(null)")] + [InlineData("int?(5)", typeof(int?), "Convert(5)")] + [InlineData("int?(null)", typeof(int?), "Convert(null)")] + [InlineData("string(null)", typeof(string), "Convert(null)")] #else [InlineData("int?(5)", typeof(int?), "Convert(5, Nullable`1)")] [InlineData("int?(null)", typeof(int?), "Convert(null, Nullable`1)")] @@ -199,15 +199,14 @@ public void Parse_NullableShouldReturnNullable(string expression, object resultT [InlineData("@MainCompany.Companies.Count() > 0", "(company.MainCompany.Companies.Count() > 0)")] [InlineData("Company.Equals(null, null)", "Equals(null, null)")] [InlineData("MainCompany.Name", "company.MainCompany.Name")] - [InlineData("Company.Name", "No property or field 'Name' exists in type 'Company'")] + [InlineData("Name", "company.Name")] [InlineData("DateTime", "company.DateTime")] public void Parse_When_PrioritizePropertyOrFieldOverTheType_IsTrue(string expression, string result) { // Arrange var config = new ParsingConfig { - CustomTypeProvider = _dynamicTypeProviderMock.Object, - PrioritizePropertyOrFieldOverTheType = true + CustomTypeProvider = _dynamicTypeProviderMock.Object }; ParameterExpression[] parameters = { ParameterExpressionHelper.CreateParameterExpression(typeof(Company), "company") }; var sut = new ExpressionParser(parameters, expression, null, config); @@ -230,20 +229,25 @@ public void Parse_When_PrioritizePropertyOrFieldOverTheType_IsTrue(string expres [Theory] [InlineData("it.MainCompany.Name != null", "(company.MainCompany.Name != null)")] [InlineData("@MainCompany.Companies.Count() > 0", "(company.MainCompany.Companies.Count() > 0)")] - [InlineData("Company.Equals(null, null)", "Equals(null, null)")] - [InlineData("MainCompany.Name", "Static property requires null instance, non-static property requires non-null instance.")] // Exception + [InlineData("Company.Equals(null, null)", "No applicable method 'Equals' exists in type 'Company'")] // Exception + [InlineData("MainCompany.Name", "company.MainCompany.Name")] + [InlineData("Name", "company.Name")] + [InlineData("it.DateTime", "company.DateTime")] [InlineData("DateTime", "'.' or '(' or string literal expected")] // Exception - [InlineData("Company.Name", "Static property requires null instance, non-static property requires non-null instance.")] // Exception public void Parse_When_PrioritizePropertyOrFieldOverTheType_IsFalse(string expression, string result) { // Arrange + var config = new ParsingConfig + { + PrioritizePropertyOrFieldOverTheType = false + }; ParameterExpression[] parameters = { ParameterExpressionHelper.CreateParameterExpression(typeof(Company), "company") }; // Act string parsedExpression; try { - var sut = new ExpressionParser(parameters, expression, null, _parsingConfig); + var sut = new ExpressionParser(parameters, expression, null, config); parsedExpression = sut.Parse(null).ToString(); } catch (Exception e) diff --git a/test/System.Linq.Dynamic.Core.Tests/QueryableTests.Where.cs b/test/System.Linq.Dynamic.Core.Tests/QueryableTests.Where.cs index 5a340319..0e48885f 100644 --- a/test/System.Linq.Dynamic.Core.Tests/QueryableTests.Where.cs +++ b/test/System.Linq.Dynamic.Core.Tests/QueryableTests.Where.cs @@ -281,6 +281,10 @@ public void Where_Dynamic_ExpandoObject_As_AnonymousType() public void Where_Dynamic_DateTimeConstructor_Issue662() { // Arrange + var config = new ParsingConfig + { + PrioritizePropertyOrFieldOverTheType = false + }; var date = new DateTime(2023, 1, 13, 12, 0, 0); var queryable = new List { @@ -289,13 +293,13 @@ public void Where_Dynamic_DateTimeConstructor_Issue662() }.AsQueryable(); // Act 1 - //var result1 = queryable.Where("DT > DateTime(2022, 1, 1, 0, 0, 0)").ToArray(); + var result1 = queryable.Where(config, "DT > DateTime(2022, 1, 1, 0, 0, 0)").ToArray(); // Assert 1 - //result1.Should().HaveCount(1); + result1.Should().HaveCount(1); // Act 2 - var result2 = queryable.Where("it.DateTime > DateTime(2022, 1, 1, 0, 0, 0)").ToArray(); + var result2 = queryable.Where(config, "it.DateTime > DateTime(2022, 1, 1, 0, 0, 0)").ToArray(); // Assert 2 result2.Should().HaveCount(1);