diff --git a/src/System.Linq.Dynamic.Core/DynamicClass.cs b/src/System.Linq.Dynamic.Core/DynamicClass.cs
index 33f06aee..64685f5e 100644
--- a/src/System.Linq.Dynamic.Core/DynamicClass.cs
+++ b/src/System.Linq.Dynamic.Core/DynamicClass.cs
@@ -123,6 +123,16 @@ public object? this[string name]
}
}
+ ///
+ /// Determines whether a property with the specified name exists in the collection.
+ ///
+ /// The name of the property to locate. Cannot be null.
+ /// true if a property with the specified name exists; otherwise, false.
+ public bool ContainsProperty(string name)
+ {
+ return Properties.ContainsKey(name);
+ }
+
///
/// Returns the enumeration of all dynamic member names.
///
diff --git a/src/System.Linq.Dynamic.Core/DynamicClass.uap.cs b/src/System.Linq.Dynamic.Core/DynamicClass.uap.cs
index 8778de98..cf28afd3 100644
--- a/src/System.Linq.Dynamic.Core/DynamicClass.uap.cs
+++ b/src/System.Linq.Dynamic.Core/DynamicClass.uap.cs
@@ -12,7 +12,7 @@ public class DynamicClass : DynamicObject
{
internal const string IndexerName = "System_Linq_Dynamic_Core_DynamicClass_Indexer";
- private readonly Dictionary _properties = new();
+ private readonly Dictionary _properties = new();
///
/// Initializes a new instance of the class.
@@ -35,7 +35,7 @@ public DynamicClass(params KeyValuePair[] propertylist)
/// The name.
/// Value from the property.
[IndexerName(IndexerName)]
- public object this[string name]
+ public object? this[string name]
{
get
{
@@ -59,6 +59,16 @@ public object this[string name]
}
}
+ ///
+ /// Determines whether a property with the specified name exists in the collection.
+ ///
+ /// The name of the property to locate. Cannot be null.
+ /// true if a property with the specified name exists; otherwise, false.
+ public bool ContainsProperty(string name)
+ {
+ return _properties.ContainsKey(name);
+ }
+
///
/// Returns the enumeration of all dynamic member names.
///
diff --git a/src/System.Linq.Dynamic.Core/Parser/ExpressionHelper.cs b/src/System.Linq.Dynamic.Core/Parser/ExpressionHelper.cs
index fefd5b66..8222d4d7 100644
--- a/src/System.Linq.Dynamic.Core/Parser/ExpressionHelper.cs
+++ b/src/System.Linq.Dynamic.Core/Parser/ExpressionHelper.cs
@@ -11,6 +11,7 @@ namespace System.Linq.Dynamic.Core.Parser;
internal class ExpressionHelper : IExpressionHelper
{
+ private static readonly Expression _nullExpression = Expression.Constant(null);
private readonly IConstantExpressionWrapper _constantExpressionWrapper = new ConstantExpressionWrapper();
private readonly ParsingConfig _parsingConfig;
@@ -340,7 +341,7 @@ public bool TryGenerateAndAlsoNotNullExpression(Expression sourceExpression, boo
// Convert all expressions into '!= null' expressions (only if the type can be null)
var binaryExpressions = expressions
.Where(expression => TypeHelper.TypeCanBeNull(expression.Type))
- .Select(expression => Expression.NotEqual(expression, Expression.Constant(null)))
+ .Select(expression => Expression.NotEqual(expression, _nullExpression))
.ToArray();
// Convert all binary expressions into `AndAlso(...)`
@@ -393,16 +394,46 @@ public bool TryConvertTypes(ref Expression left, ref Expression right)
if (left.Type == typeof(object))
{
+ if (TryGetAsIndexerExpression(left, out var ce))
+ {
+ var rightTypeAsNullableType = TypeHelper.GetNullableType(right.Type);
+
+ right = Expression.Convert(right, rightTypeAsNullableType);
+
+ left = Expression.Condition(
+ ce.Test,
+ Expression.Convert(ce.IfTrue, rightTypeAsNullableType),
+ Expression.Convert(_nullExpression, rightTypeAsNullableType)
+ );
+
+ return true;
+ }
+
left = Expression.Condition(
- Expression.Equal(left, Expression.Constant(null, typeof(object))),
+ Expression.Equal(left, _nullExpression),
GenerateDefaultExpression(right.Type),
Expression.Convert(left, right.Type)
);
}
else if (right.Type == typeof(object))
{
+ if (TryGetAsIndexerExpression(right, out var ce))
+ {
+ var leftTypeAsNullableType = TypeHelper.GetNullableType(left.Type);
+
+ left = Expression.Convert(left, leftTypeAsNullableType);
+
+ right = Expression.Condition(
+ ce.Test,
+ Expression.Convert(ce.IfTrue, leftTypeAsNullableType),
+ Expression.Convert(_nullExpression, leftTypeAsNullableType)
+ );
+
+ return true;
+ }
+
right = Expression.Condition(
- Expression.Equal(right, Expression.Constant(null, typeof(object))),
+ Expression.Equal(right, _nullExpression),
GenerateDefaultExpression(left.Type),
Expression.Convert(right, left.Type)
);
@@ -546,4 +577,17 @@ private static object[] ConvertIfIEnumerableHasValues(IEnumerable? input)
return [];
}
+
+ private static bool TryGetAsIndexerExpression(Expression expression, [NotNullWhen(true)] out ConditionalExpression? indexerExpresion)
+ {
+ indexerExpresion = expression as ConditionalExpression;
+ if (indexerExpresion == null)
+ {
+ return false;
+ }
+
+ return
+ indexerExpresion.IfTrue.ToString().Contains(DynamicClass.IndexerName) &&
+ indexerExpresion.Test.ToString().Contains("ContainsProperty");
+ }
}
\ No newline at end of file
diff --git a/src/System.Linq.Dynamic.Core/Parser/ExpressionParser.cs b/src/System.Linq.Dynamic.Core/Parser/ExpressionParser.cs
index b0fb8157..b8091f55 100644
--- a/src/System.Linq.Dynamic.Core/Parser/ExpressionParser.cs
+++ b/src/System.Linq.Dynamic.Core/Parser/ExpressionParser.cs
@@ -1921,11 +1921,35 @@ private Expression ParseMemberAccess(Type? type, Expression? expression, string?
if (!_parsingConfig.DisableMemberAccessToIndexAccessorFallback && extraCheck)
{
- var indexerName = TypeHelper.IsDynamicClass(type!) ? DynamicClass.IndexerName : "Item";
+ var isDynamicClass = TypeHelper.IsDynamicClass(type!);
+ var indexerName = isDynamicClass ? DynamicClass.IndexerName : "Item";
+
+ // Try to get the indexer property "Item" or "DynamicClass_Indexer" which takes a string as parameter
var indexerMethod = expression?.Type.GetMethod($"get_{indexerName}", [typeof(string)]);
if (indexerMethod != null)
{
- return Expression.Call(expression, indexerMethod, Expression.Constant(id));
+ if (!isDynamicClass)
+ {
+ return Expression.Call(expression, indexerMethod, Expression.Constant(id));
+ }
+
+ var containsPropertyMethod = typeof(DynamicClass).GetMethod("ContainsProperty");
+ if (containsPropertyMethod == null)
+ {
+ return Expression.Call(expression, indexerMethod, Expression.Constant(id));
+ }
+
+ var callContainsPropertyExpression = Expression.Call(
+ expression!,
+ containsPropertyMethod,
+ Expression.Constant(id)
+ );
+
+ return Expression.Condition(
+ Expression.Equal(callContainsPropertyExpression, Expression.Constant(true)),
+ Expression.Call(expression, indexerMethod, Expression.Constant(id)),
+ Expression.Constant(null)
+ );
}
}
diff --git a/test/System.Linq.Dynamic.Core.NewtonsoftJson.Tests/NewtonsoftJsonTests.cs b/test/System.Linq.Dynamic.Core.NewtonsoftJson.Tests/NewtonsoftJsonTests.cs
index d07d8052..759f156f 100644
--- a/test/System.Linq.Dynamic.Core.NewtonsoftJson.Tests/NewtonsoftJsonTests.cs
+++ b/test/System.Linq.Dynamic.Core.NewtonsoftJson.Tests/NewtonsoftJsonTests.cs
@@ -517,12 +517,14 @@ public void Where_With_Select()
[InlineData("notExisting == \"1\"")]
[InlineData("notExisting == \"something\"")]
[InlineData("notExisting > 1")]
+ [InlineData("notExisting < 1")]
[InlineData("true == notExisting")]
[InlineData("\"true\" == notExisting")]
[InlineData("1 == notExisting")]
[InlineData("\"1\" == notExisting")]
[InlineData("\"something\" == notExisting")]
[InlineData("1 < notExisting")]
+ [InlineData("1 > notExisting")]
public void Where_NonExistingMember_EmptyResult(string predicate)
{
// Arrange
diff --git a/test/System.Linq.Dynamic.Core.SystemTextJson.Tests/SystemTextJsonTests.cs b/test/System.Linq.Dynamic.Core.SystemTextJson.Tests/SystemTextJsonTests.cs
index dd5e62ee..9a81f76a 100644
--- a/test/System.Linq.Dynamic.Core.SystemTextJson.Tests/SystemTextJsonTests.cs
+++ b/test/System.Linq.Dynamic.Core.SystemTextJson.Tests/SystemTextJsonTests.cs
@@ -546,12 +546,14 @@ public void Where_With_Select()
[InlineData("notExisting == \"1\"")]
[InlineData("notExisting == \"something\"")]
[InlineData("notExisting > 1")]
+ [InlineData("notExisting < 1")]
[InlineData("true == notExisting")]
[InlineData("\"true\" == notExisting")]
[InlineData("1 == notExisting")]
[InlineData("\"1\" == notExisting")]
[InlineData("\"something\" == notExisting")]
[InlineData("1 < notExisting")]
+ [InlineData("1 > notExisting")]
public void Where_NonExistingMember_EmptyResult(string predicate)
{
// Act