diff --git a/Microsoft.Azure.Cosmos/src/Linq/SubtreeEvaluator.cs b/Microsoft.Azure.Cosmos/src/Linq/SubtreeEvaluator.cs index aaaf31d839..8ecb2ea9a2 100644 --- a/Microsoft.Azure.Cosmos/src/Linq/SubtreeEvaluator.cs +++ b/Microsoft.Azure.Cosmos/src/Linq/SubtreeEvaluator.cs @@ -45,7 +45,7 @@ protected override Expression VisitMemberInit(MemberInitExpression node) private Expression EvaluateMemberAccess(Expression expression) { - while (expression.CanReduce) + while (expression?.CanReduce ?? false) { expression = expression.Reduce(); } @@ -57,7 +57,7 @@ private Expression EvaluateMemberAccess(Expression expression) // This is done because the compilation of a delegate takes a global lock which causes highly // threaded clients to exhibit async-over-sync thread exhaustion behaviour on this call path // even when doing relatively straightforward queries. - if (!(expression is MemberExpression memberExpression)) + if (expression is not MemberExpression memberExpression) { return expression; } @@ -66,19 +66,26 @@ private Expression EvaluateMemberAccess(Expression expression) // nested property access (x.y.z) without needing to fall back on delegate compilation. Expression targetExpression = this.EvaluateMemberAccess(memberExpression.Expression); - if (!(targetExpression is ConstantExpression targetConstant)) + // NOTE: When evaluating static field or property access, we may have a null targetExpression. + // In this situation, we should pass the null value to the GetValue(...) methods below to + // indicate that we are accessing a static member. + ConstantExpression targetConstant = targetExpression as ConstantExpression; + + // If we have a target expression but it cannot be resolved to a constant, then we should skip + // using reflectoin here and instead rely on the fallback delegate compilation approach. + if (targetExpression is not null && targetConstant is null) { return expression; } if (memberExpression.Member is FieldInfo fieldInfo) { - return Expression.Constant(fieldInfo.GetValue(targetConstant.Value), memberExpression.Type); + return Expression.Constant(fieldInfo.GetValue(targetConstant?.Value), memberExpression.Type); } if (memberExpression.Member is PropertyInfo propertyInfo) { - return Expression.Constant(propertyInfo.GetValue(targetConstant.Value), memberExpression.Type); + return Expression.Constant(propertyInfo.GetValue(targetConstant?.Value), memberExpression.Type); } return expression; diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/BaselineTest/TestBaseline/LinqTranslationBaselineTests.TestMemberAccess.xml b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/BaselineTest/TestBaseline/LinqTranslationBaselineTests.TestMemberAccess.xml index 9b5974fe37..820a10efd1 100644 --- a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/BaselineTest/TestBaseline/LinqTranslationBaselineTests.TestMemberAccess.xml +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/BaselineTest/TestBaseline/LinqTranslationBaselineTests.TestMemberAccess.xml @@ -35,4 +35,40 @@ FROM root WHERE (root["NumericField"] = 3)]]> + + + + (doc.NumericField == AmbientContextObject.StaticFieldAccess))]]> + + + + + + + + + (doc.NumericField == AmbientContextObject.StaticPropertyAccess))]]> + + + + + + + + + (doc.NumericField == 6))]]> + + + + + \ No newline at end of file diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/LinqTranslationBaselineTests.cs b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/LinqTranslationBaselineTests.cs index a2eff4baa2..a3b62a1ff1 100644 --- a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/LinqTranslationBaselineTests.cs +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/LinqTranslationBaselineTests.cs @@ -151,7 +151,16 @@ internal class AmbientContextObject public double PropertyAccess { get; set; } - public double MethodAccess() => 1.0; + public double MethodAccess() + { + return 1.0; + } + + public static double StaticFieldAccess = 4.0; + + public static double StaticPropertyAccess => 5.0; + + public const double ConstAccess = 6.0; } [TestMethod] @@ -560,6 +569,9 @@ public void TestMemberAccess() // performance boost, especially under highly concurrent workloads). new LinqTestInput("Filter on Field value", b => getQuery(b).Where(doc => doc.NumericField == ambientContext.FieldAccess)), new LinqTestInput("Filter on Property value", b => getQuery(b).Where(doc => doc.NumericField == ambientContext.PropertyAccess)), + new LinqTestInput("Filter on Static Field value", b => getQuery(b).Where(doc => doc.NumericField == AmbientContextObject.StaticFieldAccess)), + new LinqTestInput("Filter on Static Property value", b => getQuery(b).Where(doc => doc.NumericField == AmbientContextObject.StaticPropertyAccess)), + new LinqTestInput("Filter on Const value", b => getQuery(b).Where(doc => doc.NumericField == AmbientContextObject.ConstAccess)), }; this.ExecuteTestSuite(inputs); }