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)))))
}
}
};