Skip to content

Commit

Permalink
Add config setting for PrioritizePropertyOrFieldOverTheType (#664)
Browse files Browse the repository at this point in the history
* ...

* Add config setting for PrioritizePropertyOrFieldOverTheType
  • Loading branch information
StefH committed Feb 4, 2023
1 parent f4bfa99 commit f7b42ed
Show file tree
Hide file tree
Showing 10 changed files with 542 additions and 438 deletions.
22 changes: 15 additions & 7 deletions src/System.Linq.Dynamic.Core/Parser/ExpressionParser.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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)
{
Expand Down Expand Up @@ -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));
Expand Down Expand Up @@ -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)
{
Expand Down
9 changes: 9 additions & 0 deletions src/System.Linq.Dynamic.Core/ParsingConfig.cs
Original file line number Diff line number Diff line change
Expand Up @@ -211,5 +211,14 @@ public IQueryableAnalyzer QueryableAnalyzer
/// Default value is <c>true</c>.
/// </summary>
public bool SupportCastingToFullyQualifiedTypeAsString { get; set; } = true;

/// <summary>
/// When the type and property have the same name the parser takes the property instead of type when this setting is set to <c>true</c>.
///
/// The value from this setting should also be set to <c>true</c> when ExtensionMethods are used.
///
/// Default value is <c>false</c>.
/// </summary>
public bool PrioritizePropertyOrFieldOverTheType { get; set; }
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand Down
17 changes: 9 additions & 8 deletions test/System.Linq.Dynamic.Core.Tests/Entities/Company.cs
Original file line number Diff line number Diff line change
@@ -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<Employee> Employees { get; set; }

public ICollection<Employee> Employees { get; set; }
}
public DateTime DateTime { get; set; }
}
15 changes: 12 additions & 3 deletions test/System.Linq.Dynamic.Core.Tests/ExpressionTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -290,14 +290,18 @@ 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<SimpleValuesModel>
{
new SimpleValuesModel { DateTime = DateTime.Now }
};

// 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<DateTime>());
Expand All @@ -307,14 +311,18 @@ 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<SimpleValuesModel>
{
new SimpleValuesModel { DateTime = DateTime.Now }
};

// 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<DateTime?>());
Expand Down Expand Up @@ -1502,7 +1510,8 @@ public void ExpressionTests_MethodCall_GenericExtension()
{
var config = new ParsingConfig
{
CustomTypeProvider = new DefaultDynamicLinqCustomTypeProviderForStaticTesting()
CustomTypeProvider = new DefaultDynamicLinqCustomTypeProviderForStaticTesting(),
PrioritizePropertyOrFieldOverTheType = true
};

// Arrange
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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\"")]
Expand Down
Loading

0 comments on commit f7b42ed

Please sign in to comment.