[CSHARP-2348] Search by condition with method Any (Linq) returns 0 documents when filter has OR condition Created: 01/Aug/18  Updated: 17/Nov/22

Status: Backlog
Project: C# Driver
Component/s: Linq, LINQ3
Affects Version/s: None
Fix Version/s: None

Type: Bug Priority: Major - P3
Reporter: Roman Buchyn Assignee: Robert Stam
Resolution: Unresolved Votes: 0
Labels: rfw
Remaining Estimate: Not Specified
Time Spent: Not Specified
Original Estimate: Not Specified

Issue Links:
Duplicate
is duplicated by CSHARP-2574 $elemMatch query contains gaps when I... Closed

 Description   

1) List<PartyUser> users1 = userRepository.GetList(c => c.Roles.Any(r => r == UserRoles.Admin));

2) List<PartyUser> users2 = userRepository.GetList(c => c.Roles.Any(r => r == UserRoles.Admin || r == UserRoles.Editor));

 

First line returns 3 elements, but second line is 0, even it has wider filter and there a lot of documents with UserRoles.Editor.

 

public List<T> GetList(Expression<Func<T, bool>> where)

{ return Db.GetCollection<T>(CollectionName).Find(where).ToList(); }

Where Db is IMongoDatabase

2.7.0 Beta1



 Comments   
Comment by Robert Stam [ 22/Apr/22 ]

I don't think the MQL generated in the second case should be called bad MQL:

find({ "Roles" : { "$elemMatch" : { "$or" : [{ "$eq" : 42 }, { "$eq" : 99 }] } } }) 

The server should support this, but currently does not.

Note that if Roles was an array of documents containing an Id field the following almost identical query is supported by the server:

find({ "Roles" : { "$elemMatch" : { "$or" : [{ "Id" : { "$eq" : 42 } }, { "Id" : { "$eq" : 99 } }] } } }) 

The only difference is that `{ $eq : 42 }` references the implied current element (which has no name) where as `{ Id :

{ $eq : 42 }

}` references the Id property of the implied current element (which still has no name but now refers to a document instead of an int).

 

Comment by Wan Bachtiar [ 22/Feb/19 ]

Hi Roman,

I tried to reproduce this issue with the following simple class:

public class PartyUser
{
    public ObjectId Id { get; set; }     
    public List<String> Roles {get; set;}
}

First line returns 3 elements, but second line is 0, even it has wider filter and there a lot of documents with UserRoles.Editor.

The following example code snippet:

    String adminRole = "admin";
    String editorRole = "editor";
    var query2 = collection.Find(c => c.Roles.Any(r => (r == adminRole || r == editorRole))); 

Does not return both admin and editor roles as you have observed, because currently the LINQ is being translated into:

find({ "Roles" : { "$elemMatch" : { "$or" : [{ "" : "admin" }, { "" : "editor" }] } } })

So Any is being translated into $elemMatch, but currently the operator supports documents and not array values.

However there are few workarounds for achieve what you're after:

    var query1 = collection.Find(c => c.Roles.Any(r => r == adminRole) || c.Roles.Any(r => r == editorRole)); 
    // find({ "$or" : [{ "Roles" : "admin" }, { "Roles" : "editor" }] })
 
    var query2 = collection.Find(c => c.Roles.Contains(adminRole) || c.Roles.Contains(editorRole)); 
    // find({ "$or" : [{ "Roles" : "admin" }, { "Roles" : "editor" }] })
 
    var wantedRoles = new[]{adminRole, editorRole};
    var query3 = collection.Find(c => c.Roles.Any(r => wantedRoles.Contains(r))); 
    // find({ "Roles" : { "$in" : ["admin", "editor"] } })

We'll keep this ticket open to support LINQ Any with multi conditions predicate on array values.

Regards,
Wan.

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