Home > Net >  How to filter a collection for nested objects with different types
How to filter a collection for nested objects with different types

Time:01-30

In the Vehicle collection I have two different types of documents - Suv and Truck. My models look like this:

public abstract class BaseCar 
{
   public Guid Id { get; set; }
   public Guid Title { get; set; }
   public int YearOfProduction { get; set; }
   ...
}

public class Suv : BaseVehicle 
{
   ...
   public List<Engine> Engines { get; set; }
}

public class Truck : BaseVehicle 
{
   ...
   public List<TruckPart> Parts { get; set; }
}

public class TruckPart 
{
   ...
   public List<Engine> Engines { get; set; }
}

public class Engine 
{
   ...
   public int HorsePower { get; set; }
}

Now I would like to filter the collection of Vehicles by their YearOfProduction and HorsePower and get all id's of such vehicles. Is it possible to do it in other way than getting documents of each type separately and then sorting each document of a given type?

var suv = database.GetCollection<Suv>("vehicles");
var trucks = database.GetCollection<Trucks>("vehicles");

//filters for suvs, trucks and then displaying a list of ids of all matching vehicles

CodePudding user response:

Pre-requisites

To implement the polymorphic concept in MongoDB classes, you need to apply the BsonKnownTypes attribute to let the serializer know which inherit/sub-class should be mapped to.

References: Polymorphism in MongoDB .NET Driver

[BsonDiscriminator(RootClass = true)]
[BsonKnownTypes(typeof(Suv), typeof(Truck))]
public abstract class BaseVehicle
{
    public Guid Id { get; set; }
    public string Title { get; set; }
    public int YearOfProduction { get; set; }
}

Your BSON Document is required to contain _t field which is a discriminator specifying its type. Example:

{
  "_id": "4bb69277-f61d-46f2-91ed-1e9b65ddf66d",
  "YearOfProduction": 2020,
  "Engines": [
    {
      "HorsePower": 800
    }
  ],
  "Title": "Suv 2",
  "_t": "Suv"
}

Approach 1: With MongoDB .NET Driver Fluent to BsonDocument

With the MongoDB .NET Driver, it is hard to implement the query filter with fluent API (due to strong type), hence the below extension method is to convert the FilterDefinition<T> to BsonDocument.

public static class IMongoCollectionExtensions
{
    public static BsonDocument QueryToBson<T>(this IMongoCollection<T> collection,
        FilterDefinition<T> filter)
    {
        return filter.Render(
            collection.DocumentSerializer,
            collection.Settings.SerializerRegistry);
    }
}

A bunch of Fluent queries, required to combine them into one as BsonDocument.

IMongoCollection<BaseVehicle> _collection = _db.GetCollection<BaseVehicle>("vehicle");
IMongoCollection<Suv> _suvCollection = _db.GetCollection<Suv>("vehicle");
IMongoCollection<Truck> _truckCollection = _db.GetCollection<Truck>("vehicle");
IMongoCollection<BsonDocument> _bsonCollection = _db.GetCollection<BsonDocument>("vehicle");

FilterDefinition<Suv> suvFilter = Builders<Suv>.Filter.ElemMatch(x => x.Engines,
    Builders<Engine>.Filter.Eq(y => y.HorsePower, 1000));

FilterDefinition<Truck> truckFilter = Builders<Truck>.Filter.ElemMatch(x => x.Parts,
    Builders<TruckPart>.Filter.ElemMatch(y => y.Engines,
        Builders<Engine>.Filter.Eq(z => z.HorsePower, 1000)));

FilterDefinition<BaseVehicle> baseFilter = Builders<BaseVehicle>.Filter.Eq(x => x.YearOfProduction, 2022);

FilterDefinition<BsonDocument> filter = _collection.QueryToBson(baseFilter);
filter &= (FilterDefinition<BsonDocument>)_suvCollection.QueryToBson(suvFilter)
    | (FilterDefinition<BsonDocument>)_truckCollection.QueryToBson(truckFilter);

var result = await _collection.Find(_bsonCollection.QueryToBson(filter))
    .ToListAsync();

Approach 2: Pass the query as BsonDocument

This approach will be much simple without the Fluent API. As always, you can build your query and convert it into BsonDocument with MongoDB Compass (Export to Specific Language).

db.collection.find({
  "YearOfProduction": 2022,
  $or: [
    {
      "Engines": {
        "$elemMatch": {
          "HorsePower": 1000
        }
      }
    },
    {
      "Parts": {
        "$elemMatch": {
          "Engines": {
            "$elemMatch": {
              "HorsePower": 1000
            }
          }
        }
      }
    }
  ]
})

Your query should be as below:

FilterDefinition<BaseVehicle> filter = new BsonDocument
{
    { "YearOfProduction", 2022 },
    { "$or", new BsonArray
        {
            new BsonDocument("Engines",
                new BsonDocument("$elemMatch",
                    new BsonDocument("HorsePower", 1000))),
            new BsonDocument("Parts",
                new BsonDocument("$elemMatch",
                    new BsonDocument("Engines",
                        new BsonDocument("$elemMatch",
                            new BsonDocument("HorsePower", 1000)))))
        } 
    }
};
  • Related