MongoDb c# driver find item in array by field value

60,206

Solution 1

There is ElemMatch

var filter = Builders<Post>.Filter.ElemMatch(x => x.Tags, x => x.Name == "test");
var res = await collection.Find(filter).ToListAsync()

Solution 2

Here's an example that returns a single complex item from an array (using MongoDB.Driver v2.5.0):

Simple Data Model

public class Zoo
{
    public List<Animal> Animals { get; set; }
}

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

Option 1 (Aggregation)

public Animal FindAnimalInZoo(string animalName)
{
    var zooWithAnimalFilter = Builders<Zoo>.Filter
        .ElemMatch(z => z.Animals, a => a.Name == animalName);

    return _db.GetCollection<Zoo>("zoos").Aggregate()
        .Match(zooWithAnimalFilter)
        .Project<Animal>(
            Builders<Zoo>.Projection.Expression<Animal>(z => 
                z.Animals.FirstOrDefault(a => a.Name == animalName)))
        .FirstOrDefault(); // or .ToList() to return multiple
}

Option 2 (Filter & Linq) This was about 5x slower for me

public Animal FindAnimalInZoo(string animalName)
{
    // Same as above
    var zooWithAnimalFilter = Builders<Zoo>.Filter
        .ElemMatch(z => z.Animals, a => a.Name == animalName);

    var zooWithAnimal = _db.GetCollection<Zoo>("zoos")
        .Find(zooWithAnimalFilter)
        .FirstOrDefault();

    return zooWithAnimal.Animals.FirstOrDefault(a => a.Name == animalName);
}

Solution 3

You need the $elemMatch operator. You could use Builders<T>.Filter.ElemMatch or an Any expression:

Find(x => x.Tags.Any(t => t.Name == "test")).ToListAsync()

http://mongodb.github.io/mongo-csharp-driver/2.0/reference/driver/expressions/#elemmatch

Solution 4

As of the 2.4.2 release of the C# drivers, the IFindFluent interface can be used for querying on array element. ElemMatch cannot be used on an array of strings directly, whereas the find interface will work on either simple or complex types (e.g. 'Tags.Name') and is strongly typed.

            FilterDefinitionBuilder<Post> tcBuilder = Builders<Post>.Filter;
            FilterDefinition<Post> tcFilter = tcBuilder.Eq("Tags","mongodb") & tcBuilder.Eq("Tags","asp.net");
               ...
            await myCollection.FindAsync(tcFilter);

Linq driver uses the aggregation framework, but for a query with no aggregation operators a find is faster.

Note that this has been broken in previous versions of the driver so the answer was not available at the time of original posting.

Share:
60,206
Vladyslav Furdak
Author by

Vladyslav Furdak

Updated on March 17, 2020

Comments

  • Vladyslav Furdak
    Vladyslav Furdak about 4 years

    i found the way to check is the value contains in simple array :

    var filter = Builders<Post>.Filter.AnyEq(x => x.Tags, "mongodb");
    

    But how to find a complex item with many fields by a concrete field ? I found the way to write it via the dot notation approach with BsonDocument builder, but how can i do it with typed lambda notations ?

    upd

    i think it some kind of

    builderInst.AnyIn(p => p.ComplexCollection.Select(ml => ml.Id), mlIds)
    

    but can't check right now, is anyone could help ?

  • shiva8
    shiva8 over 7 years
    This is not an optimal solution. The best solution is to use api.mongodb.com/csharp/current/html/…
  • rnofenko
    rnofenko over 7 years
    @shiva8 You can not user AnyIn when you have array of complex objects.
  • LMK
    LMK over 5 years
    Warning, Any() doesn't work when targeting Azure CosmosDB - see feedback.azure.com/forums/263030-azure-cosmos-db/suggestions‌​/…
  • Surya Deepak
    Surya Deepak over 5 years
    Hello Aaron, your solution had fixed my problem.However, i would like to know the performance of this, if there are close to a lakh records.
  • Geraldo Neto
    Geraldo Neto about 4 years
    Any idea on why the second option is (about 5x) slower than the first one?