Search code examples
c#arraysmongodbsearchmongodb-.net-driver

Mongo C# Driver how to do nested ElemMatch


If I have following architecture - how do I find zoos with noisy animals? Example Data Model

public class Zoo
{
    public List<Cage> Cages { get; set; }
}

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


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

public class Feline : Animal
{
    public int MeowsCount { get; set; }
}

public class Canine: Animal
{
    public int BarksCount { get; set; }
}

For now I do extract whole db and find necessary with LINQ

public IEnumerable<Zoo> FindNoisyZoos(int soundsCount)
{
    var allZoos = await zooCollection.Find(_ => true).ToListAsync();

    List<Zoo> noisyZoos = allZoos.Where(z => 
        z.Cages.Any(c => 
            c.Animals.Where(a => ((Feline)a).MeowsCount == soundsCount || ((Canine)a).BarksCount == soundsCount))))
        .ToList();

    return noisyZoos;
}

And thats terribly inefficient. I would like being able to do nested ElemMatch, but I can't wrap my head around how ElemMatch works. I expect it to be something like this:

public IEnumerable<Zoo> FindNoisyZoos(int soundsCount)
{
    var noisyFelineFilter = new FilterDefinitionBuilder<Animal>().And(new FilterDefinitionBuilder<Animal>().OfType<Feline>(), new FilterDefinitionBuilder<Animal>().Eq(a => ((Feline)a).MeowsCount == soundsCount));
    var noisyCanineFilter = new FilterDefinitionBuilder<Animal>().And(new FilterDefinitionBuilder<Animal>().OfType<Canine>(), new FilterDefinitionBuilder<Animal>().Eq(a => ((Canine)a).BarksCount == soundsCount));

    var noisyAnimalFilter = new FilterDefinitionBuilder<Animal>().Or(noisyFelineFilter, noisyCanineFilter);
        

    // smth like this: new FilterDefinitionBuilder<Zoo>().ElemMatch(z => z.Cages.ElemMatch(c => c.Animals.ElemMatch(noisyAnimalFilter)));
    var noisyFilter;
    
    var zoos = await zooCollection.Find(noisyFilter).ToListAsync();

    return zoos;
}

Solution

  • MongoDB .NET driver can process some LINQ queries server-side. It translates them under the hood into MongoDB native ones. Did you try something like zooCollection.AsQueriable().Where(z => z...).ToList(). .ToList() comes last and sends data to the client. If this works, the filtering will be performed server-side, if it doesn't the driver will tell you directly that it is not supported.