Skip to content

Useless Expression.Condition when parsing IsOf #1490

@rpallares

Description

@rpallares

Assemblies affected

  • ASP.NET Core OData 8.x
  • ASP.NET Core OData 9.x

Describe the bug
The translation of the isof method adds an unnecessary conditional expression.
This additional expression can have important side effects depending on the underlying IQueryableProvider.

I identified this bug by seeing very inefficient and abnormally complex queries using MongoDb. I suppose other providers optimize the case and get rid of this issue (I hope).

Reproduce steps

  1. Considering the data model below
  2. Have OData controller with:
    [EnableQuery]
    public IQueryable<Zoo> Get()
    {
        return zooCollection.AsQueryable();
    }
  3. Odata filter: Animals/any(a:isof(a,'MyCompany.Cat'))
  4. Expression parsed: z => z.Animals.Any(a => a is Cat ? true : false)
  5. Generated mongo query: { "$expr" : { "$anyElementTrue" : { "$map" : { "input" : "$Animals", "as" : "se", "in" : { "$cond" : { "if" : { "$eq" : ["$$se._t", "Cat"] }, "then" : true, "else" : false } } } } } }
    Note: The real problem is that query cannot use database indexes
  6. Expected mongo query: { "Animals" : { "$elemMatch" : { "_t" : "Cat" } } }

Data Model

namespace MyCompany;

public sealed record Zoo
{
    public ObjectId Id { get; set; }
    public ICollection<Animal> Animals { get; set; } = [];
}

public abstract class Animal
{
    public required string Name { get; set; }
}

public sealed class Cat : Animal
{
    public required bool HasMustaches { get; set; }
}

public sealed class Dog : Animal
{
    public required string Color { get; set; }
}

Additional context
I have a workaround that simplify the expression after all using a ExpressionVisitor similarly as done in MongoDB.AspNetCore.OData/MongoEnableQueryAttribute.cs

internal sealed class SimplifyExpressionVisitor : ExpressionVisitor
{
    protected override Expression VisitConditional(ConditionalExpression node)
    {
        if (node.Type == typeof(bool) && node.IfTrue.NodeType == ExpressionType.Constant && node.IfFalse.NodeType == ExpressionType.Constant)
        {
            bool ifTrueValue = Convert.ToBoolean(((ConstantExpression)node.IfTrue).Value);
            bool ifFalseValue = Convert.ToBoolean(((ConstantExpression)node.IfFalse).Value);

            return (ifTrueValue, ifFalseValue) switch
            {
                (true, true) => node.IfTrue,
                (false, false) => node.IfTrue,
                (true, false) => Visit(node.Test),
                (false, true) => Expression.Not(Visit(node.Test))
            };
        }
        
        return base.VisitConditional(node);
    }
}

Metadata

Metadata

Assignees

Labels

bugSomething isn't working

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions