[CSHARP-3140] PartialEvaluator should not evaluate unnecessary clauses for AndAlso, Conditional and OrElse Created: 19/Jun/20  Updated: 28/Oct/23  Resolved: 25/Apr/22

Status: Closed
Project: C# Driver
Component/s: Linq, LINQ3
Affects Version/s: 2.10.4, 2.11.0-beta2
Fix Version/s: 2.15.1

Type: Bug Priority: Major - P3
Reporter: Анатолий Крыжановский Assignee: Robert Stam
Resolution: Fixed Votes: 0
Labels: triaged
Remaining Estimate: Not Specified
Time Spent: Not Specified
Original Estimate: Not Specified
Environment:

.net core 3.1


Epic Link: CSHARP-3615

 Description   

I found case then LINQ filter fail with exception if one of expression member is null.

For example we have orders which bounded to factory (view FactoryId field):

class Order
{    
    public int Id {get;set;}
    public int FactoryId {get;set;}
 
 }
 

We have current user, each user can be bound to factory or can be system wide (factory = null):

class User
{    
    public int Id {get;set;}
    public Factory Factory {get;set;}
}
 
class Factory
{
    public int Id {get;set;}
}

Now i want to get all orders which current user can access to:

var orders = db
    .GetCollection<Order>("order1").AsQueryable()
    .Where(x => currentUser.Factory == null || x.FactoryId == currentUser.Factory.Id);

In case of system wide user (currentUser.Factory = null) i get exception of type System.Reflection.TargetException: Non-static method requires a target with following stacktrace:

System.Reflection.TargetException: Non-static method requires a target.
 at System.Reflection.RuntimeMethodInfo.CheckConsistency(Object target)
 at System.Reflection.RuntimeMethodInfo.InvokeArgumentsCheck(Object obj, BindingFlags invokeAttr, Binder binder, Object[] parameters, CultureInfo culture)
 at System.Reflection.RuntimeMethodInfo.Invoke(Object obj, BindingFlags invokeAttr, Binder binder, Object[] parameters, CultureInfo culture)
 at System.Reflection.RuntimePropertyInfo.GetValue(Object obj, BindingFlags invokeAttr, Binder binder, Object[] index, CultureInfo culture)
 at System.Reflection.RuntimePropertyInfo.GetValue(Object obj, Object[] index)
 at System.Reflection.PropertyInfo.GetValue(Object obj)
 at MongoDB.Driver.Linq.Processors.PartialEvaluator.ReflectionEvaluator.VisitMember(MemberExpression node)
 at System.Linq.Expressions.MemberExpression.Accept(ExpressionVisitor visitor)
 at System.Linq.Expressions.ExpressionVisitor.Visit(Expression node)
 at MongoDB.Driver.Linq.Processors.PartialEvaluator.ReflectionEvaluator.Evaluate(Expression node)
 at MongoDB.Driver.Linq.Processors.PartialEvaluator.EvaluateSubtree(Expression subtree)
 at MongoDB.Driver.Linq.Processors.PartialEvaluator.Visit(Expression node)
 at System.Linq.Expressions.ExpressionVisitor.VisitBinary(BinaryExpression node)
 at System.Linq.Expressions.BinaryExpression.Accept(ExpressionVisitor visitor)
 at System.Linq.Expressions.ExpressionVisitor.Visit(Expression node)
 at MongoDB.Driver.Linq.Processors.PartialEvaluator.Visit(Expression node)
 at System.Linq.Expressions.ExpressionVisitor.VisitBinary(BinaryExpression node)
 at System.Linq.Expressions.BinaryExpression.Accept(ExpressionVisitor visitor)
 at System.Linq.Expressions.ExpressionVisitor.Visit(Expression node)
 at MongoDB.Driver.Linq.Processors.PartialEvaluator.Visit(Expression node)
 at System.Linq.Expressions.ExpressionVisitor.VisitLambda[T](Expression`1 node)
 at System.Linq.Expressions.Expression`1.Accept(ExpressionVisitor visitor)
 at System.Linq.Expressions.ExpressionVisitor.Visit(Expression node)
 at MongoDB.Driver.Linq.Processors.PartialEvaluator.Visit(Expression node)
 at System.Linq.Expressions.ExpressionVisitor.VisitUnary(UnaryExpression node)
 at System.Linq.Expressions.UnaryExpression.Accept(ExpressionVisitor visitor)
 at System.Linq.Expressions.ExpressionVisitor.Visit(Expression node)
 at MongoDB.Driver.Linq.Processors.PartialEvaluator.Visit(Expression node)
 at System.Dynamic.Utils.ExpressionVisitorUtils.VisitArguments(ExpressionVisitor visitor, IArgumentProvider nodes)
 at System.Linq.Expressions.ExpressionVisitor.VisitMethodCall(MethodCallExpression node)
 at System.Linq.Expressions.MethodCallExpression.Accept(ExpressionVisitor visitor)
 at System.Linq.Expressions.ExpressionVisitor.Visit(Expression node)
 at MongoDB.Driver.Linq.Processors.PartialEvaluator.Visit(Expression node)
 at MongoDB.Driver.Linq.Processors.PartialEvaluator.Evaluate(Expression node)
 at MongoDB.Driver.Linq.MongoQueryProviderImpl`1.Prepare(Expression expression)
 at MongoDB.Driver.Linq.MongoQueryProviderImpl`1.Translate(Expression expression)
 at MongoDB.Driver.Linq.MongoQueryProviderImpl`1.Execute(Expression expression)
 at MongoDB.Driver.Linq.MongoQueryableImpl`2.GetEnumerator()
 at System.Collections.Generic.LargeArrayBuilder`1.AddRange(IEnumerable`1 items)
 at System.Collections.Generic.EnumerableHelpers.ToArray[T](IEnumerable`1 source)
 at System.Linq.Enumerable.ToArray[TSource](IEnumerable`1 source)
 at ConsoleApp1.Program.Main(String[] args) in D:\ConsoleApp1\ConsoleApp1\Program.cs:line 56

If current user bounded to factory all work fine.

i try to rewrite to condition operator:

var orders = db
    .GetCollection<Order>("order1").AsQueryable()
    .Where(x => currentUser.Factory == null ? true : x.FactoryId == currentUser.Factory.Id)

But with no luck - IIF operator does not work.

I create fiddle with sample code that reproduce this behaviour: https://dotnetfiddle.net/iFIkrA



 Comments   
Comment by Githook User [ 04/May/22 ]

Author:

{'name': 'rstam', 'email': 'robert@robertstam.org', 'username': 'rstam'}

Message: CSHARP-3140: PartialEvaluator should not evaluate unnecessary clauses for AndAlso, Conditional and OrElse.
Branch: v2.15.x
https://github.com/mongodb/mongo-csharp-driver/commit/157e47948101ec19ea5b823f34a0a5b8de55567c

Comment by Githook User [ 25/Apr/22 ]

Author:

{'name': 'rstam', 'email': 'robert@robertstam.org', 'username': 'rstam'}

Message: CSHARP-3140: PartialEvaluator should not evaluate unnecessary clauses for AndAlso, Conditional and OrElse.
Branch: master
https://github.com/mongodb/mongo-csharp-driver/commit/8472e0201e1c33ce38d6d68002b707da738381d0

Comment by Robert Stam [ 20/Apr/22 ]

This is a bug (or limitation) in the PartialEvaluator class. It doesn't handle the `||` operator properly. Once it encounters a clause that partially evaluates to `true` it should ignore the remaining clauses and simplify the entire expression to `true`.

Similar considerations would apply for the `&&` and `?:` operators.

 

Comment by James Kovacs [ 24/Nov/20 ]

Hi, Alexey,

Thank you for your interest in the progress on CSHARP-3140. We are currently working on a new implementation of our LINQ translator and will consider this issue in the context of that work.

James

Comment by Alexey N/A [ 24/Nov/20 ]

@rstam Is this in progress? Can we expect to see this in the next stable version?

Comment by Robert Stam [ 13/Jul/20 ]

I'm not sure yet how easy this might turn out to fix, and we might not get to it for awhile, but I can offer you a workaround that you can start using right away.

var currentUser = new User { Id = 1, Factory = null };
 
var orders = (IQueryable<Order>)database.GetCollection<Order>("order1").AsQueryable();
if (currentUser.Factory != null)
{
    orders = orders.Where(x => x.FactoryId == currentUser.Factory.Id);
}
var result = orders.ToList();

The idea of the workaround is to not even use .Where when currentUser.Factory == null.

Comment by Robert Stam [ 13/Jul/20 ]

Full sample program to reproduce:

using MongoDB.Driver;
using System.Linq;
 
namespace TestCSharp3140
{
    public class Order
    {
        public int Id { get; set; }
        public int FactoryId { get; set; }
    }
 
    public class User
    {
        public int Id { get; set; }
        public Factory Factory { get; set; }
    }
 
    public class Factory
    {
        public int Id { get; set; }
    }
 
    public static class Program
    {
        public static void Main(string[] args)
        {
            var client = new MongoClient("mongodb://localhost");
            var database = client.GetDatabase("test");
 
            var currentUser = new User { Id = 1, Factory = null };
            var orders = database
                .GetCollection<Order>("order1").AsQueryable()
                .Where(x => currentUser.Factory == null || x.FactoryId == currentUser.Factory.Id)
                .ToList();
        }
    }
}

 

Generated at Wed Feb 07 21:44:29 UTC 2024 using Jira 9.7.1#970001-sha1:2222b88b221c4928ef0de3161136cc90c8356a66.