From f7b42edb06de35f1768c808dcdaf12d21ff15d7a Mon Sep 17 00:00:00 2001 From: Stef Heyenrath Date: Sat, 4 Feb 2023 11:16:05 +0100 Subject: [PATCH] Add config setting for PrioritizePropertyOrFieldOverTheType (#664) * ... * Add config setting for PrioritizePropertyOrFieldOverTheType --- .../Parser/ExpressionParser.cs | 22 +- src/System.Linq.Dynamic.Core/ParsingConfig.cs | 9 + .../DynamicExpressionParserTests.cs | 6 +- .../Entities/Company.cs | 17 +- .../ExpressionTests.cs | 15 +- .../ExpressionParserTests.TypeAccess.cs | 15 + .../Parser/ExpressionParserTests.cs | 383 +++++++------ .../QueryableTests.GroupBy.cs | 2 +- .../QueryableTests.Select.cs | 1 - .../QueryableTests.Where.cs | 510 +++++++++--------- 10 files changed, 542 insertions(+), 438 deletions(-) diff --git a/src/System.Linq.Dynamic.Core/Parser/ExpressionParser.cs b/src/System.Linq.Dynamic.Core/Parser/ExpressionParser.cs index add203e1..dad76ebd 100644 --- a/src/System.Linq.Dynamic.Core/Parser/ExpressionParser.cs +++ b/src/System.Linq.Dynamic.Core/Parser/ExpressionParser.cs @@ -857,9 +857,12 @@ private Expression ParseIdentifier() { _textParser.ValidateToken(TokenId.Identifier); - if (_keywordsHelper.TryGetValue(_textParser.CurrentToken.Text, out object? value) && - // Prioritize property or field over the type - !(value is Type && _it != null && FindPropertyOrField(_it.Type, _textParser.CurrentToken.Text, false) != null)) + var isValidKeyWord = _keywordsHelper.TryGetValue(_textParser.CurrentToken.Text, out var value); + + var extraCondition = !_parsingConfig.PrioritizePropertyOrFieldOverTheType || + (_parsingConfig.PrioritizePropertyOrFieldOverTheType && !(value is Type && _it != null && FindPropertyOrField(_it.Type, _textParser.CurrentToken.Text, false) != null)); + + if (isValidKeyWord && extraCondition) { if (value is Type typeValue) { @@ -1711,9 +1714,13 @@ private Expression ParseMemberAccess(Type? type, Expression? expression) return Expression.Field(expression, field); } - if (!_parsingConfig.DisableMemberAccessToIndexAccessorFallback && expression != null) + // #357 #662 + var extraCheck = !_parsingConfig.PrioritizePropertyOrFieldOverTheType || + _parsingConfig.PrioritizePropertyOrFieldOverTheType && expression != null; + + if (!_parsingConfig.DisableMemberAccessToIndexAccessorFallback && extraCheck) { - var indexerMethod = expression.Type.GetMethod("get_Item", new[] { typeof(string) }); + var indexerMethod = expression?.Type.GetMethod("get_Item", new[] { typeof(string) }); if (indexerMethod != null) { return Expression.Call(expression, indexerMethod, Expression.Constant(id)); @@ -2139,11 +2146,12 @@ private static Exception IncompatibleOperandsError(string opName, Expression lef private MemberInfo? FindPropertyOrField(Type type, string memberName, bool staticAccess) { #if !(NETFX_CORE || WINDOWS_APP || UAP10_0 || NETSTANDARD) - BindingFlags flags = BindingFlags.Public | BindingFlags.DeclaredOnly | (staticAccess ? BindingFlags.Static : BindingFlags.Instance); + var extraBindingFlag = _parsingConfig.PrioritizePropertyOrFieldOverTheType && staticAccess ? BindingFlags.Static : BindingFlags.Instance; + var bindingFlags = BindingFlags.Public | BindingFlags.DeclaredOnly | extraBindingFlag; foreach (Type t in TypeHelper.GetSelfAndBaseTypes(type)) { var findMembersType = _parsingConfig?.IsCaseSensitive == true ? Type.FilterName : Type.FilterNameIgnoreCase; - var members = t.FindMembers(MemberTypes.Property | MemberTypes.Field, flags, findMembersType, memberName); + var members = t.FindMembers(MemberTypes.Property | MemberTypes.Field, bindingFlags, findMembersType, memberName); if (members.Length != 0) { diff --git a/src/System.Linq.Dynamic.Core/ParsingConfig.cs b/src/System.Linq.Dynamic.Core/ParsingConfig.cs index aa4c1a6d..66f41fab 100644 --- a/src/System.Linq.Dynamic.Core/ParsingConfig.cs +++ b/src/System.Linq.Dynamic.Core/ParsingConfig.cs @@ -211,5 +211,14 @@ public IQueryableAnalyzer QueryableAnalyzer /// 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; } } } \ 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 297768d8..3bd1332c 100644 --- a/test/System.Linq.Dynamic.Core.Tests/DynamicExpressionParserTests.cs +++ b/test/System.Linq.Dynamic.Core.Tests/DynamicExpressionParserTests.cs @@ -1449,9 +1449,10 @@ public void DynamicExpressionParser_ParseLambda_GenericExtensionMethod() { // Arrange var testList = User.GenerateSampleModels(51); - var config = new ParsingConfig() + var config = new ParsingConfig { - CustomTypeProvider = new DefaultDynamicLinqCustomTypeProviderForGenericExtensionMethod() + CustomTypeProvider = new DefaultDynamicLinqCustomTypeProviderForGenericExtensionMethod(), + PrioritizePropertyOrFieldOverTheType = true }; // Act @@ -1461,7 +1462,6 @@ public void DynamicExpressionParser_ParseLambda_GenericExtensionMethod() var result = Enumerable.Where(testList, del); - var expected = testList.Where(x => new string[] { "User4", "User2" }.Contains(x.UserName)).ToList(); // Assert diff --git a/test/System.Linq.Dynamic.Core.Tests/Entities/Company.cs b/test/System.Linq.Dynamic.Core.Tests/Entities/Company.cs index a6a6e5c3..459dbca8 100644 --- a/test/System.Linq.Dynamic.Core.Tests/Entities/Company.cs +++ b/test/System.Linq.Dynamic.Core.Tests/Entities/Company.cs @@ -1,15 +1,16 @@ using System.Collections.Generic; -namespace System.Linq.Dynamic.Core.Tests.Entities +namespace System.Linq.Dynamic.Core.Tests.Entities; + +public class Company : Entity { - public class Company : Entity - { - public string Name { get; set; } + public string Name { get; set; } + + public long? MainCompanyId { get; set; } - public long? MainCompanyId { get; set; } + public MainCompany MainCompany { get; set; } - public MainCompany MainCompany { get; set; } + public ICollection Employees { get; set; } - public ICollection Employees { get; set; } - } + public DateTime DateTime { get; set; } } \ No newline at end of file diff --git a/test/System.Linq.Dynamic.Core.Tests/ExpressionTests.cs b/test/System.Linq.Dynamic.Core.Tests/ExpressionTests.cs index f5537a6c..b9b8341a 100644 --- a/test/System.Linq.Dynamic.Core.Tests/ExpressionTests.cs +++ b/test/System.Linq.Dynamic.Core.Tests/ExpressionTests.cs @@ -290,6 +290,10 @@ public void ExpressionTests_Cast_To_Enum_Using_DynamicLinqType() public void ExpressionTests_Cast_To_FullTypeDateTime_Using_DynamicLinqType() { // Arrange + var config = new ParsingConfig + { + PrioritizePropertyOrFieldOverTheType = true + }; var list = new List { new SimpleValuesModel { DateTime = DateTime.Now } @@ -297,7 +301,7 @@ public void ExpressionTests_Cast_To_FullTypeDateTime_Using_DynamicLinqType() // Act var expectedResult = list.Select(x => x.DateTime); - var result = list.AsQueryable().Select($"\"{typeof(DateTime).FullName}\"(DateTime)"); + var result = list.AsQueryable().Select(config, $"\"{typeof(DateTime).FullName}\"(DateTime)"); // Assert Assert.Equal(expectedResult.ToArray(), result.ToDynamicArray()); @@ -307,6 +311,10 @@ public void ExpressionTests_Cast_To_FullTypeDateTime_Using_DynamicLinqType() public void ExpressionTests_Cast_To_FullTypeDateTimeNullable_Using_DynamicLinqType() { // Arrange + var config = new ParsingConfig + { + PrioritizePropertyOrFieldOverTheType = true + }; var list = new List { new SimpleValuesModel { DateTime = DateTime.Now } @@ -314,7 +322,7 @@ public void ExpressionTests_Cast_To_FullTypeDateTimeNullable_Using_DynamicLinqTy // Act var expectedResult = list.Select(x => (DateTime?)x.DateTime); - var result = list.AsQueryable().Select($"\"{typeof(DateTime).FullName}\"?(DateTime)"); + var result = list.AsQueryable().Select(config, $"\"{typeof(DateTime).FullName}\"?(DateTime)"); // Assert Assert.Equal(expectedResult.ToArray(), result.ToDynamicArray()); @@ -1502,7 +1510,8 @@ public void ExpressionTests_MethodCall_GenericExtension() { var config = new ParsingConfig { - CustomTypeProvider = new DefaultDynamicLinqCustomTypeProviderForStaticTesting() + CustomTypeProvider = new DefaultDynamicLinqCustomTypeProviderForStaticTesting(), + PrioritizePropertyOrFieldOverTheType = true }; // Arrange 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 c5bed90c..d79ca134 100644 --- a/test/System.Linq.Dynamic.Core.Tests/Parser/ExpressionParserTests.TypeAccess.cs +++ b/test/System.Linq.Dynamic.Core.Tests/Parser/ExpressionParserTests.TypeAccess.cs @@ -53,6 +53,21 @@ public void ParseTypeAccess_Via_Constructor_String_To_DateTime_Valid() 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\"")] diff --git a/test/System.Linq.Dynamic.Core.Tests/Parser/ExpressionParserTests.cs b/test/System.Linq.Dynamic.Core.Tests/Parser/ExpressionParserTests.cs index 3015401a..b32c151c 100644 --- a/test/System.Linq.Dynamic.Core.Tests/Parser/ExpressionParserTests.cs +++ b/test/System.Linq.Dynamic.Core.Tests/Parser/ExpressionParserTests.cs @@ -1,224 +1,257 @@ -using FluentAssertions; -using Moq; -using NFluent; -using System.Collections.Generic; -using System.Globalization; +using System.Collections.Generic; using System.Linq.Dynamic.Core.CustomTypeProviders; using System.Linq.Dynamic.Core.Exceptions; using System.Linq.Dynamic.Core.Parser; using System.Linq.Dynamic.Core.Tests.Entities; using System.Linq.Expressions; +using FluentAssertions; +using Moq; +using NFluent; using Xunit; -namespace System.Linq.Dynamic.Core.Tests.Parser +namespace System.Linq.Dynamic.Core.Tests.Parser; + +public partial class ExpressionParserTests { - public partial class ExpressionParserTests - { - private readonly ParsingConfig _parsingConfig; - private readonly Mock _dynamicTypeProviderMock; + private readonly Mock _dynamicTypeProviderMock; - public ExpressionParserTests() - { - _dynamicTypeProviderMock = new Mock(); - _dynamicTypeProviderMock.Setup(dt => dt.GetCustomTypes()).Returns(new HashSet() { typeof(Company), typeof(MainCompany) }); - _dynamicTypeProviderMock.Setup(dt => dt.ResolveType(typeof(Company).FullName)).Returns(typeof(Company)); - _dynamicTypeProviderMock.Setup(dt => dt.ResolveType(typeof(MainCompany).FullName)).Returns(typeof(MainCompany)); - _dynamicTypeProviderMock.Setup(dt => dt.ResolveTypeBySimpleName("Company")).Returns(typeof(Company)); - _dynamicTypeProviderMock.Setup(dt => dt.ResolveTypeBySimpleName("MainCompany")).Returns(typeof(MainCompany)); - - _parsingConfig = new ParsingConfig - { - CustomTypeProvider = _dynamicTypeProviderMock.Object - }; - } + private readonly ParsingConfig _parsingConfig; - [Fact] - public void Parse_ParseBinaryInteger() + public ExpressionParserTests() + { + _dynamicTypeProviderMock = new Mock(); + _dynamicTypeProviderMock.Setup(dt => dt.GetCustomTypes()).Returns(new HashSet() { typeof(Company), typeof(MainCompany) }); + _dynamicTypeProviderMock.Setup(dt => dt.ResolveType(typeof(Company).FullName!)).Returns(typeof(Company)); + _dynamicTypeProviderMock.Setup(dt => dt.ResolveType(typeof(MainCompany).FullName!)).Returns(typeof(MainCompany)); + _dynamicTypeProviderMock.Setup(dt => dt.ResolveTypeBySimpleName("Company")).Returns(typeof(Company)); + _dynamicTypeProviderMock.Setup(dt => dt.ResolveTypeBySimpleName("MainCompany")).Returns(typeof(MainCompany)); + + _parsingConfig = new ParsingConfig { - // Arrange - var expression = "0b1100000011101"; - ParameterExpression[] parameters = { ParameterExpressionHelper.CreateParameterExpression(typeof(int), "x") }; - var sut = new ExpressionParser(parameters, expression, null, null); + CustomTypeProvider = _dynamicTypeProviderMock.Object + }; + } - // Act - var parsedExpression = sut.Parse(null).ToString(); + [Fact] + public void Parse_ParseBinaryInteger() + { + // Arrange + var expression = "0b1100000011101"; + ParameterExpression[] parameters = { ParameterExpressionHelper.CreateParameterExpression(typeof(int), "x") }; + var sut = new ExpressionParser(parameters, expression, null, null); - // Assert - parsedExpression.Should().Be("6173"); - } + // Act + var parsedExpression = sut.Parse(null).ToString(); - [Fact] - public void Parse_ParseHexadecimalInteger() - { - // Arrange - var expression = "0xFF"; - ParameterExpression[] parameters = { ParameterExpressionHelper.CreateParameterExpression(typeof(int), "x") }; - var sut = new ExpressionParser(parameters, expression, null, null); + // Assert + parsedExpression.Should().Be("6173"); + } - // Act - var parsedExpression = sut.Parse(null).ToString(); + [Fact] + public void Parse_ParseHexadecimalInteger() + { + // Arrange + var expression = "0xFF"; + ParameterExpression[] parameters = { ParameterExpressionHelper.CreateParameterExpression(typeof(int), "x") }; + var sut = new ExpressionParser(parameters, expression, null, null); - // Assert - parsedExpression.Should().Be("255"); - } + // Act + var parsedExpression = sut.Parse(null).ToString(); - [Theory] - [InlineData("it == 1", "(x == 1)")] - [InlineData("it eq 1", "(x == 1)")] - [InlineData("it equal 1", "(x == 1)")] - [InlineData("it != 1", "(x != 1)")] - [InlineData("it ne 1", "(x != 1)")] - [InlineData("it neq 1", "(x != 1)")] - [InlineData("it notequal 1", "(x != 1)")] - [InlineData("it lt 1", "(x < 1)")] - [InlineData("it LessThan 1", "(x < 1)")] - [InlineData("it le 1", "(x <= 1)")] - [InlineData("it LessThanEqual 1", "(x <= 1)")] - [InlineData("it gt 1", "(x > 1)")] - [InlineData("it GreaterThan 1", "(x > 1)")] - [InlineData("it ge 1", "(x >= 1)")] - [InlineData("it GreaterThanEqual 1", "(x >= 1)")] - public void Parse_ParseComparisonOperator(string expression, string result) - { - // Arrange - ParameterExpression[] parameters = { ParameterExpressionHelper.CreateParameterExpression(typeof(int), "x") }; - var sut = new ExpressionParser(parameters, expression, null, null); + // Assert + parsedExpression.Should().Be("255"); + } - // Act - var parsedExpression = sut.Parse(null).ToString(); + [Theory] + [InlineData("it == 1", "(x == 1)")] + [InlineData("it eq 1", "(x == 1)")] + [InlineData("it equal 1", "(x == 1)")] + [InlineData("it != 1", "(x != 1)")] + [InlineData("it ne 1", "(x != 1)")] + [InlineData("it neq 1", "(x != 1)")] + [InlineData("it notequal 1", "(x != 1)")] + [InlineData("it lt 1", "(x < 1)")] + [InlineData("it LessThan 1", "(x < 1)")] + [InlineData("it le 1", "(x <= 1)")] + [InlineData("it LessThanEqual 1", "(x <= 1)")] + [InlineData("it gt 1", "(x > 1)")] + [InlineData("it GreaterThan 1", "(x > 1)")] + [InlineData("it ge 1", "(x >= 1)")] + [InlineData("it GreaterThanEqual 1", "(x >= 1)")] + public void Parse_ParseComparisonOperator(string expression, string result) + { + // Arrange + ParameterExpression[] parameters = { ParameterExpressionHelper.CreateParameterExpression(typeof(int), "x") }; + var sut = new ExpressionParser(parameters, expression, null, null); - // Assert - Check.That(parsedExpression).Equals(result); - } + // Act + var parsedExpression = sut.Parse(null).ToString(); - [Theory] - [InlineData("it || true", "(x OrElse True)")] - [InlineData("it or true", "(x OrElse True)")] - [InlineData("it OrElse true", "(x OrElse True)")] - public void Parse_ParseOrOperator(string expression, string result) - { - // Arrange - ParameterExpression[] parameters = { ParameterExpressionHelper.CreateParameterExpression(typeof(bool), "x") }; - var sut = new ExpressionParser(parameters, expression, null, null); + // Assert + Check.That(parsedExpression).Equals(result); + } - // Act - var parsedExpression = sut.Parse(null).ToString(); + [Theory] + [InlineData("it || true", "(x OrElse True)")] + [InlineData("it or true", "(x OrElse True)")] + [InlineData("it OrElse true", "(x OrElse True)")] + public void Parse_ParseOrOperator(string expression, string result) + { + // Arrange + ParameterExpression[] parameters = { ParameterExpressionHelper.CreateParameterExpression(typeof(bool), "x") }; + var sut = new ExpressionParser(parameters, expression, null, null); - // Assert - Check.That(parsedExpression).Equals(result); - } + // Act + var parsedExpression = sut.Parse(null).ToString(); - [Theory] - [InlineData("it && true", "(x AndAlso True)")] - [InlineData("it and true", "(x AndAlso True)")] - [InlineData("it AndAlso true", "(x AndAlso True)")] - public void Parse_ParseAndOperator(string expression, string result) - { - // Arrange - ParameterExpression[] parameters = { ParameterExpressionHelper.CreateParameterExpression(typeof(bool), "x") }; - var sut = new ExpressionParser(parameters, expression, null, null); + // Assert + Check.That(parsedExpression).Equals(result); + } - // Act - var parsedExpression = sut.Parse(null).ToString(); + [Theory] + [InlineData("it && true", "(x AndAlso True)")] + [InlineData("it and true", "(x AndAlso True)")] + [InlineData("it AndAlso true", "(x AndAlso True)")] + public void Parse_ParseAndOperator(string expression, string result) + { + // Arrange + ParameterExpression[] parameters = { ParameterExpressionHelper.CreateParameterExpression(typeof(bool), "x") }; + var sut = new ExpressionParser(parameters, expression, null, null); - // Assert - Check.That(parsedExpression).Equals(result); - } + // Act + var parsedExpression = sut.Parse(null).ToString(); - [Fact] - public void Parse_ParseMultipleInOperators() - { - // Arrange - ParameterExpression[] parameters = { ParameterExpressionHelper.CreateParameterExpression(typeof(Company), "x") }; - var sut = new ExpressionParser(parameters, "MainCompanyId in (1, 2) and Name in (\"A\", \"B\")", null, null); + // Assert + Check.That(parsedExpression).Equals(result); + } - // Act - var parsedExpression = sut.Parse(null).ToString(); + [Fact] + public void Parse_ParseMultipleInOperators() + { + // Arrange + ParameterExpression[] parameters = { ParameterExpressionHelper.CreateParameterExpression(typeof(Company), "x") }; + var sut = new ExpressionParser(parameters, "MainCompanyId in (1, 2) and Name in (\"A\", \"B\")", null, null); - // Assert - Check.That(parsedExpression).Equals("(((x.MainCompanyId == 1) OrElse (x.MainCompanyId == 2)) AndAlso ((x.Name == \"A\") OrElse (x.Name == \"B\")))"); - } + // Act + var parsedExpression = sut.Parse(null).ToString(); - [Fact] - public void Parse_ParseInWrappedInParenthesis() - { - // Arrange - ParameterExpression[] parameters = { ParameterExpressionHelper.CreateParameterExpression(typeof(Company), "x") }; - var sut = new ExpressionParser(parameters, "(MainCompanyId in @0)", new object[] { new long?[] { 1, 2 } }, null); + // Assert + Check.That(parsedExpression).Equals("(((x.MainCompanyId == 1) OrElse (x.MainCompanyId == 2)) AndAlso ((x.Name == \"A\") OrElse (x.Name == \"B\")))"); + } - // Act - var parsedExpression = sut.Parse(null).ToString(); + [Fact] + public void Parse_ParseInWrappedInParenthesis() + { + // Arrange + ParameterExpression[] parameters = { ParameterExpressionHelper.CreateParameterExpression(typeof(Company), "x") }; + var sut = new ExpressionParser(parameters, "(MainCompanyId in @0)", new object[] { new long?[] { 1, 2 } }, null); - // Assert - Check.That(parsedExpression).Equals("value(System.Nullable`1[System.Int64][]).Contains(x.MainCompanyId)"); - } + // Act + var parsedExpression = sut.Parse(null).ToString(); - [Theory] - [InlineData("string(\"\")", "")] - [InlineData("string(\"a\")", "a")] - [InlineData("int(42)", 42)] - public void Parse_CastStringIntShouldReturnConstantExpression(string expression, object result) - { - // Arrange - ParameterExpression[] parameters = { ParameterExpressionHelper.CreateParameterExpression(typeof(bool), "x") }; - var sut = new ExpressionParser(parameters, expression, null, null); + // Assert + Check.That(parsedExpression).Equals("value(System.Nullable`1[System.Int64][]).Contains(x.MainCompanyId)"); + } - // Act - var constantExpression = (ConstantExpression)sut.Parse(null); + [Theory] + [InlineData("string(\"\")", "")] + [InlineData("string(\"a\")", "a")] + [InlineData("int(42)", 42)] + public void Parse_CastStringIntShouldReturnConstantExpression(string expression, object result) + { + // Arrange + ParameterExpression[] parameters = { ParameterExpressionHelper.CreateParameterExpression(typeof(bool), "x") }; + var sut = new ExpressionParser(parameters, expression, null, null); - // Assert - Check.That(constantExpression.Value).Equals(result); - } + // Act + var constantExpression = (ConstantExpression)sut.Parse(null); + + // Assert + Check.That(constantExpression.Value).Equals(result); + } - [Theory] + [Theory] #if NET452 [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)")] - [InlineData("string(null)", typeof(string), "Convert(null, String)")] + [InlineData("int?(5)", typeof(int?), "Convert(5, Nullable`1)")] + [InlineData("int?(null)", typeof(int?), "Convert(null, Nullable`1)")] + [InlineData("string(null)", typeof(string), "Convert(null, String)")] #endif - public void Parse_NullableShouldReturnNullable(string expression, object resultType, object result) - { - // Arrange - ParameterExpression[] parameters = { ParameterExpressionHelper.CreateParameterExpression(typeof(bool), "x") }; - var sut = new ExpressionParser(parameters, expression, null, null); + public void Parse_NullableShouldReturnNullable(string expression, object resultType, object result) + { + // Arrange + ParameterExpression[] parameters = { ParameterExpressionHelper.CreateParameterExpression(typeof(bool), "x") }; + var sut = new ExpressionParser(parameters, expression, null, null); - // Act - var unaryExpression = (UnaryExpression)sut.Parse(null); + // Act + var unaryExpression = (UnaryExpression)sut.Parse(null); - // Assert - Check.That(unaryExpression.Type).Equals(resultType); - Check.That(unaryExpression.ToString()).Equals(result); + // Assert + Check.That(unaryExpression.Type).Equals(resultType); + Check.That(unaryExpression.ToString()).Equals(result); + } + + [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", "company.MainCompany.Name")] + [InlineData("Company.Name", "No property or field 'Name' exists in type 'Company'")] + [InlineData("DateTime", "company.DateTime")] + public void Parse_When_PrioritizePropertyOrFieldOverTheType_IsTrue(string expression, string result) + { + // Arrange + var config = new ParsingConfig + { + CustomTypeProvider = _dynamicTypeProviderMock.Object, + PrioritizePropertyOrFieldOverTheType = true + }; + ParameterExpression[] parameters = { ParameterExpressionHelper.CreateParameterExpression(typeof(Company), "company") }; + var sut = new ExpressionParser(parameters, expression, null, config); + + // Act + string parsedExpression; + try + { + parsedExpression = sut.Parse(null).ToString(); } + catch (ParseException e) + { + parsedExpression = e.Message; + } + + // Assert + parsedExpression.Should().Be(result); + } + + [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("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 + ParameterExpression[] parameters = { ParameterExpressionHelper.CreateParameterExpression(typeof(Company), "company") }; - [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", "company.MainCompany.Name")] - [InlineData("Company.Name", "No property or field 'Name' exists in type 'Company'")] - public void Parse_PrioritizePropertyOrFieldOverTheType(string expression, string result) + // Act + string parsedExpression; + try { - // Arrange - ParameterExpression[] parameters = { ParameterExpressionHelper.CreateParameterExpression(typeof(Company), "company") }; var sut = new ExpressionParser(parameters, expression, null, _parsingConfig); - - // Act - string parsedExpression; - try - { - parsedExpression = sut.Parse(null).ToString(); - } - catch (ParseException e) - { - parsedExpression = e.Message; - } - - // Assert - Check.That(parsedExpression).Equals(result); + parsedExpression = sut.Parse(null).ToString(); } + catch (Exception e) + { + parsedExpression = e.Message; + } + + // Assert + parsedExpression.Should().StartWith(result); } -} +} \ No newline at end of file diff --git a/test/System.Linq.Dynamic.Core.Tests/QueryableTests.GroupBy.cs b/test/System.Linq.Dynamic.Core.Tests/QueryableTests.GroupBy.cs index 55b9a1af..46384adb 100644 --- a/test/System.Linq.Dynamic.Core.Tests/QueryableTests.GroupBy.cs +++ b/test/System.Linq.Dynamic.Core.Tests/QueryableTests.GroupBy.cs @@ -13,7 +13,7 @@ public partial class QueryableTests { public class DateTimeTest { - public DateTimeTest Test { get; set; } + public DateTimeTest? Test { get; set; } public DateTime? D { get; set; } } diff --git a/test/System.Linq.Dynamic.Core.Tests/QueryableTests.Select.cs b/test/System.Linq.Dynamic.Core.Tests/QueryableTests.Select.cs index ee365db3..82e854d7 100644 --- a/test/System.Linq.Dynamic.Core.Tests/QueryableTests.Select.cs +++ b/test/System.Linq.Dynamic.Core.Tests/QueryableTests.Select.cs @@ -3,7 +3,6 @@ using System.IO; using System.Linq.Dynamic.Core.Exceptions; using System.Linq.Dynamic.Core.Tests.Helpers.Models; -using FluentAssertions; using Linq.PropertyTranslator.Core; using QueryInterceptor.Core; using Xunit; diff --git a/test/System.Linq.Dynamic.Core.Tests/QueryableTests.Where.cs b/test/System.Linq.Dynamic.Core.Tests/QueryableTests.Where.cs index 6ce39e54..5a340319 100644 --- a/test/System.Linq.Dynamic.Core.Tests/QueryableTests.Where.cs +++ b/test/System.Linq.Dynamic.Core.Tests/QueryableTests.Where.cs @@ -7,283 +7,313 @@ using FluentAssertions; using Xunit; -namespace System.Linq.Dynamic.Core.Tests +namespace System.Linq.Dynamic.Core.Tests; + +public partial class QueryableTests { - public partial class QueryableTests + [Fact] + public void Where_Dynamic() { - [Fact] - public void Where_Dynamic() - { - // Arrange - var testList = User.GenerateSampleModels(100, allowNullableProfiles: true); - var qry = testList.AsQueryable(); - - // Act - var userById = qry.Where("Id=@0", testList[10].Id); - var userByUserName = qry.Where("UserName=\"User5\""); - var nullProfileCount = qry.Where("Profile=null"); - var userByFirstName = qry.Where("Profile!=null && Profile.FirstName=@0", testList[1].Profile.FirstName); - - // Assert - Assert.Equal(testList[10], userById.Single()); - Assert.Equal(testList[5], userByUserName.Single()); - Assert.Equal(testList.Count(x => x.Profile == null), nullProfileCount.Count()); - Assert.Equal(testList[1], userByFirstName.Single()); - } - - [Fact] - public void Where_Dynamic_CheckCastToObject() - { - // Arrange - var testList = User.GenerateSampleModels(100, allowNullableProfiles: true); - var qry = testList.AsQueryable(); - - // Act - string dynamicExpression = qry.Where("Profile == null").Expression.ToDebugView(); - string expresion = qry.Where(var1 => var1.Profile == null).Expression.ToDebugView(); - - // Assert - NFluent.Check.That(dynamicExpression).Equals(expresion); - } - - [Theory] - [InlineData("Fri, 10 May 2019 11:03:17 GMT", 11)] - [InlineData("Fri, 10 May 2019 11:03:17 -07:00", 18)] - public void Where_Dynamic_DateTimeIsParsedAsUTC(string time, int hours) - { - // Arrange - var queryable = new List { - new Example - { - TimeNull = new DateTime(2019, 5, 10, hours, 3, 17, DateTimeKind.Utc) - } - }.AsQueryable(); - - // Act - var parsingConfig = new ParsingConfig + // Arrange + var testList = User.GenerateSampleModels(100, allowNullableProfiles: true); + var qry = testList.AsQueryable(); + + // Act + var userById = qry.Where("Id=@0", testList[10].Id); + var userByUserName = qry.Where("UserName=\"User5\""); + var nullProfileCount = qry.Where("Profile=null"); + var userByFirstName = qry.Where("Profile!=null && Profile.FirstName=@0", testList[1].Profile.FirstName); + + // Assert + Assert.Equal(testList[10], userById.Single()); + Assert.Equal(testList[5], userByUserName.Single()); + Assert.Equal(testList.Count(x => x.Profile == null), nullProfileCount.Count()); + Assert.Equal(testList[1], userByFirstName.Single()); + } + + [Fact] + public void Where_Dynamic_CheckCastToObject() + { + // Arrange + var testList = User.GenerateSampleModels(100, allowNullableProfiles: true); + var qry = testList.AsQueryable(); + + // Act + string dynamicExpression = qry.Where("Profile == null").Expression.ToDebugView(); + string expresion = qry.Where(var1 => var1.Profile == null).Expression.ToDebugView(); + + // Assert + NFluent.Check.That(dynamicExpression).Equals(expresion); + } + + [Theory] + [InlineData("Fri, 10 May 2019 11:03:17 GMT", 11)] + [InlineData("Fri, 10 May 2019 11:03:17 -07:00", 18)] + public void Where_Dynamic_DateTimeIsParsedAsUTC(string time, int hours) + { + // Arrange + var queryable = new List { + new Example { - DateTimeIsParsedAsUTC = true - }; - var result = queryable.Where(parsingConfig, $"it.TimeNull >= \"{time}\""); - - // Assert - Assert.Equal(1, result.Count()); - } - - /// - /// https://github.com/StefH/System.Linq.Dynamic.Core/issues/19 - /// - [Fact] - public void Where_Dynamic_DateTime_NotEquals_Null() + TimeNull = new DateTime(2019, 5, 10, hours, 3, 17, DateTimeKind.Utc) + } + }.AsQueryable(); + + // Act + var parsingConfig = new ParsingConfig { - //Arrange - IQueryable queryable = new[] { new Post() }.AsQueryable(); + DateTimeIsParsedAsUTC = true + }; + var result = queryable.Where(parsingConfig, $"it.TimeNull >= \"{time}\""); - //Act - var expected = queryable.Where(p => p.PostDate != null).ToArray(); - var result1 = queryable.Where("PostDate != null").ToArray(); - var result2 = queryable.Where("null != PostDate").ToArray(); + // Assert + Assert.Equal(1, result.Count()); + } - //Assert - Assert.Equal(expected, result1); - Assert.Equal(expected, result2); - } + /// + /// https://github.com/StefH/System.Linq.Dynamic.Core/issues/19 + /// + [Fact] + public void Where_Dynamic_DateTime_NotEquals_Null() + { + //Arrange + IQueryable queryable = new[] { new Post() }.AsQueryable(); - [Fact] - public void Where_Dynamic_DateTime_Equals_Null() - { - //Arrange - IQueryable queryable = new[] { new Post() }.AsQueryable(); + //Act + var expected = queryable.Where(p => p.PostDate != null).ToArray(); + var result1 = queryable.Where("PostDate != null").ToArray(); + var result2 = queryable.Where("null != PostDate").ToArray(); - //Act - var expected = queryable.Where(p => p.PostDate == null).ToArray(); - var result1 = queryable.Where("PostDate == null").ToArray(); - var result2 = queryable.Where("null == PostDate").ToArray(); + //Assert + Assert.Equal(expected, result1); + Assert.Equal(expected, result2); + } - //Assert - Assert.Equal(expected, result1); - Assert.Equal(expected, result2); - } + [Fact] + public void Where_Dynamic_DateTime_Equals_Null() + { + //Arrange + IQueryable queryable = new[] { new Post() }.AsQueryable(); - [Fact] - public void Where_Dynamic_IQueryable_LambdaExpression() - { - // Arrange - var queryable = (IQueryable)new[] { new User { Income = 5 } }.AsQueryable(); + //Act + var expected = queryable.Where(p => p.PostDate == null).ToArray(); + var result1 = queryable.Where("PostDate == null").ToArray(); + var result2 = queryable.Where("null == PostDate").ToArray(); - Expression> userExpression = u => u.Income > 1; - LambdaExpression lambdaExpression = userExpression; + //Assert + Assert.Equal(expected, result1); + Assert.Equal(expected, result2); + } - // Act - var result = queryable.Where(lambdaExpression).ToDynamicArray(); + [Fact] + public void Where_Dynamic_IQueryable_LambdaExpression() + { + // Arrange + var queryable = (IQueryable)new[] { new User { Income = 5 } }.AsQueryable(); - // Assert - result.Should().HaveCount(1); - } + Expression> userExpression = u => u.Income > 1; + LambdaExpression lambdaExpression = userExpression; - [Fact] - public void Where_Dynamic_IQueryableT_LambdaExpression() - { - // Arrange - var queryable = new[] { new User { Income = 5 } }.AsQueryable(); + // Act + var result = queryable.Where(lambdaExpression).ToDynamicArray(); - Expression> userExpression = u => u.Income > 1; - LambdaExpression lambdaExpression = userExpression; + // Assert + result.Should().HaveCount(1); + } - // Act - var result = queryable.Where(lambdaExpression); + [Fact] + public void Where_Dynamic_IQueryableT_LambdaExpression() + { + // Arrange + var queryable = new[] { new User { Income = 5 } }.AsQueryable(); - // Assert - result.Should().HaveCount(1); - } + Expression> userExpression = u => u.Income > 1; + LambdaExpression lambdaExpression = userExpression; - [Fact] - public void Where_Dynamic_Exceptions() - { - //Arrange - var testList = User.GenerateSampleModels(100, allowNullableProfiles: true); - var qry = testList.AsQueryable(); - - //Act - Assert.Throws(() => qry.Where("Id")); - Assert.Throws(() => qry.Where("Bad=3")); - Assert.Throws(() => qry.Where("Id=123")); - - Assert.Throws(() => DynamicQueryableExtensions.Where(null, "Id=1")); - Assert.Throws(() => qry.Where((string)null)); - Assert.Throws(() => qry.Where("")); - Assert.Throws(() => qry.Where(" ")); - } - - [Fact] - public void Where_Dynamic_StringQuoted() - { - // Arrange - var testList = User.GenerateSampleModels(2, allowNullableProfiles: true); - testList[0].UserName = @"This \""is\"" a test."; - var qry = testList.AsQueryable(); - - // Act - // var result1a = qry.Where(@"UserName == ""This \\""is\\"" a test.""").ToArray(); - var result1b = qry.Where("UserName == \"This \\\\\\\"is\\\\\\\" a test.\"").ToArray(); - var result2a = qry.Where("UserName == @0", @"This \""is\"" a test.").ToArray(); - var result2b = qry.Where("UserName == @0", "This \\\"is\\\" a test.").ToArray(); - - var expected = qry.Where(x => x.UserName == @"This \""is\"" a test.").ToArray(); - - // Assert - Assert.Single(expected); - // Assert.Equal(expected, result1a); - Assert.Equal(expected, result1b); - Assert.Equal(expected, result2a); - Assert.Equal(expected, result2b); - } - - [Fact] - public void Where_Dynamic_EmptyString() - { - // Arrange - var testList = User.GenerateSampleModels(2, allowNullableProfiles: true); - var qry = testList.AsQueryable(); - - // Act - var expected1 = qry.Where(u => u.UserName != string.Empty).ToArray(); - var expected2 = qry.Where(u => u.UserName != "").ToArray(); - var resultDynamic1 = qry.Where("UserName != @0", string.Empty).ToArray(); - var resultDynamic2 = qry.Where("UserName != @0", "").ToArray(); - - // Assert - resultDynamic1.Should().Contain(expected1); - resultDynamic2.Should().Contain(expected2); - } - - [Fact] - public void Where_Dynamic_SelectNewObjects() - { - //Arrange - var testList = User.GenerateSampleModels(100, allowNullableProfiles: true); - var qry = testList.AsQueryable(); + // Act + var result = queryable.Where(lambdaExpression); - //Act - var expectedResult = testList.Where(x => x.Income > 4000).Select(x => new { Id = x.Id, Income = x.Income + 1111 }); - var dynamicList = qry.Where("Income > @0", 4000).ToDynamicList(); + // Assert + result.Should().HaveCount(1); + } - var newUsers = dynamicList.Select(x => new { Id = x.Id, Income = x.Income + 1111 }); - Assert.Equal(newUsers.Cast().ToList(), expectedResult); - } + [Fact] + public void Where_Dynamic_Exceptions() + { + //Arrange + var testList = User.GenerateSampleModels(100, allowNullableProfiles: true); + var qry = testList.AsQueryable(); + + //Act + Assert.Throws(() => qry.Where("Id")); + Assert.Throws(() => qry.Where("Bad=3")); + Assert.Throws(() => qry.Where("Id=123")); + + Assert.Throws(() => DynamicQueryableExtensions.Where(null, "Id=1")); + Assert.Throws(() => qry.Where((string)null)); + Assert.Throws(() => qry.Where("")); + Assert.Throws(() => qry.Where(" ")); + } - [Fact] - public void Where_Dynamic_ExpandoObject_As_Dictionary_Is_Null_Should_Throw_InvalidOperationException() - { - // Arrange - var productsQuery = new[] { new ProductDynamic { ProductId = 1 } }.AsQueryable(); + [Fact] + public void Where_Dynamic_StringQuoted() + { + // Arrange + var testList = User.GenerateSampleModels(2, allowNullableProfiles: true); + testList[0].UserName = @"This \""is\"" a test."; + var qry = testList.AsQueryable(); + + // Act + // var result1a = qry.Where(@"UserName == ""This \\""is\\"" a test.""").ToArray(); + var result1b = qry.Where("UserName == \"This \\\\\\\"is\\\\\\\" a test.\"").ToArray(); + var result2a = qry.Where("UserName == @0", @"This \""is\"" a test.").ToArray(); + var result2b = qry.Where("UserName == @0", "This \\\"is\\\" a test.").ToArray(); + + var expected = qry.Where(x => x.UserName == @"This \""is\"" a test.").ToArray(); + + // Assert + Assert.Single(expected); + // Assert.Equal(expected, result1a); + Assert.Equal(expected, result1b); + Assert.Equal(expected, result2a); + Assert.Equal(expected, result2b); + } - // Act - Action action = () => productsQuery.Where("Properties.Name == @0", "First Product").ToDynamicList(); + [Fact] + public void Where_Dynamic_EmptyString() + { + // Arrange + var testList = User.GenerateSampleModels(2, allowNullableProfiles: true); + var qry = testList.AsQueryable(); + + // Act + var expected1 = qry.Where(u => u.UserName != string.Empty).ToArray(); + var expected2 = qry.Where(u => u.UserName != "").ToArray(); + var resultDynamic1 = qry.Where("UserName != @0", string.Empty).ToArray(); + var resultDynamic2 = qry.Where("UserName != @0", "").ToArray(); + + // Assert + resultDynamic1.Should().Contain(expected1); + resultDynamic2.Should().Contain(expected2); + } - // Assert - action.Should().Throw(); - } + [Fact] + public void Where_Dynamic_SelectNewObjects() + { + //Arrange + var testList = User.GenerateSampleModels(100, allowNullableProfiles: true); + var qry = testList.AsQueryable(); - [Fact(Skip = "NP does not work here")] - public void Where_Dynamic_ExpandoObject_As_Dictionary_Is_Null_With_NullPropagating() - { - // Arrange - var productsQuery = new[] { new ProductDynamic { ProductId = 1 } }.AsQueryable(); + //Act + var expectedResult = testList.Where(x => x.Income > 4000).Select(x => new { Id = x.Id, Income = x.Income + 1111 }); + var dynamicList = qry.Where("Income > @0", 4000).ToDynamicList(); - // Act - var results = productsQuery.Where("np(Properties.Name, \"no\") == @0", "First Product").ToDynamicList(); + var newUsers = dynamicList.Select(x => new { Id = x.Id, Income = x.Income + 1111 }); + Assert.Equal(newUsers.Cast().ToList(), expectedResult); + } - // Assert - results.Should().HaveCount(0); - } + [Fact] + public void Where_Dynamic_ExpandoObject_As_Dictionary_Is_Null_Should_Throw_InvalidOperationException() + { + // Arrange + var productsQuery = new[] { new ProductDynamic { ProductId = 1 } }.AsQueryable(); - [Fact] - public void Where_Dynamic_ExpandoObject_As_Dictionary() - { - // Arrange - var productsQuery = new[] { new ProductDynamic { ProductId = 1, Properties = new Dictionary { { "Name", "test" } } } }.AsQueryable(); + // Act + Action action = () => productsQuery.Where("Properties.Name == @0", "First Product").ToDynamicList(); - // Act - var results = productsQuery.Where("Properties.Name == @0", "test").ToDynamicList(); + // Assert + action.Should().Throw(); + } - // Assert - results.Should().HaveCount(1); - } + [Fact(Skip = "NP does not work here")] + public void Where_Dynamic_ExpandoObject_As_Dictionary_Is_Null_With_NullPropagating() + { + // Arrange + var productsQuery = new[] { new ProductDynamic { ProductId = 1 } }.AsQueryable(); - [Fact] - public void Where_Dynamic_Object_As_Dictionary() - { - // Arrange - var productsQuery = new[] { new ProductDynamic { ProductId = 1, PropertiesAsObject = new Dictionary { { "Name", "test" } } } }.AsQueryable(); + // Act + var results = productsQuery.Where("np(Properties.Name, \"no\") == @0", "First Product").ToDynamicList(); - // Act - var results = productsQuery.Where("PropertiesAsObject.Name == @0", "test").ToDynamicList(); + // Assert + results.Should().HaveCount(0); + } + + [Fact] + public void Where_Dynamic_ExpandoObject_As_Dictionary() + { + // Arrange + var productsQuery = new[] { new ProductDynamic { ProductId = 1, Properties = new Dictionary { { "Name", "test" } } } }.AsQueryable(); - // Assert - results.Should().HaveCount(1); - } + // Act + var results = productsQuery.Where("Properties.Name == @0", "test").ToDynamicList(); - [Fact] - public void Where_Dynamic_ExpandoObject_As_AnonymousType() - { - // Arrange - var productsQuery = new[] { new ProductDynamic { ProductId = 1, Properties = new { Name = "test" } } }.AsQueryable(); + // Assert + results.Should().HaveCount(1); + } + + [Fact] + public void Where_Dynamic_Object_As_Dictionary() + { + // Arrange + var productsQuery = new[] { new ProductDynamic { ProductId = 1, PropertiesAsObject = new Dictionary { { "Name", "test" } } } }.AsQueryable(); + + // Act + var results = productsQuery.Where("PropertiesAsObject.Name == @0", "test").ToDynamicList(); - // Act - var results = productsQuery.Where("Properties.Name == @0", "test").ToDynamicList(); + // Assert + results.Should().HaveCount(1); + } + + [Fact] + public void Where_Dynamic_ExpandoObject_As_AnonymousType() + { + // Arrange + var productsQuery = new[] { new ProductDynamic { ProductId = 1, Properties = new { Name = "test" } } }.AsQueryable(); - // Assert - results.Should().HaveCount(1); - } + // Act + var results = productsQuery.Where("Properties.Name == @0", "test").ToDynamicList(); + + // Assert + results.Should().HaveCount(1); + } - public class ProductDynamic + [Fact] + public void Where_Dynamic_DateTimeConstructor_Issue662() + { + // Arrange + var date = new DateTime(2023, 1, 13, 12, 0, 0); + var queryable = new List { - public int ProductId { get; set; } + new() { DateTime = date }, + new() { DT = date } + }.AsQueryable(); + + // Act 1 + //var result1 = queryable.Where("DT > DateTime(2022, 1, 1, 0, 0, 0)").ToArray(); + + // Assert 1 + //result1.Should().HaveCount(1); + + // Act 2 + var result2 = queryable.Where("it.DateTime > DateTime(2022, 1, 1, 0, 0, 0)").ToArray(); - public dynamic Properties { get; set; } + // Assert 2 + result2.Should().HaveCount(1); + } + + public class ProductDynamic + { + public int ProductId { get; set; } + + public dynamic Properties { get; set; } + + public object PropertiesAsObject { get; set; } + } + + public class Foo + { + public DateTime DT { get; set; } - public object PropertiesAsObject { get; set; } - } + public DateTime DateTime { get; set; } } -} +} \ No newline at end of file