Steve H
Steve H

Reputation: 81

Querying derived type values using MongoDB C# driver

I'm porting some code from the old legacy MongoDB driver to use the new driver and have hit a problem. I have a collection which contains multiple derived types from a common base class. Previously I was able to query the collection (which is declared using the base type) using derived class properties and just retrieve the derived class documents. So given these classes :

[BsonDiscriminator(RootClass = true)]
[BsonKnownTypes(typeof(Cat),typeof(Dog))]
class Animal
{
    [BsonId(IdGenerator = typeof(StringObjectIdGenerator))]
    public string Id { get; set; }

    public string Name { get; set; }
}

class Cat : Animal
{
    public bool LikesFish { get; set; }
}

class Dog : Animal
{
    public string FavouriteBone { get; set; }
}

I could then do something like :

MongoCollection<Animal> animals = db.GetCollection<Animal>("Animals");
var q = Query<Cat>.EQ(c => c.LikesFish, true);
var catsThatLikeFish = animals.FindAs<Animal>(q).ToList();

which worked fine.

Now however I have to type the filter and can no longer compile :

IMongoCollection<Animal> animals = db.GetCollection<Animal>("Animals");
var query = Builders<Cat>.Filter.Eq(c => c.LikesFish, true);
var catsThatLikeFish = animals.FindSync(query);

and get this error :

Error CS0411 The type arguments for method 'IMongoCollection<Animal>.FindSync<TProjection>(FilterDefinition<Animal>, FindOptions<Animal, TProjection>, CancellationToken)' cannot be inferred from the usage. Try specifying the type arguments explicitly.

Is this no longer possible using the new driver? We have classes that allow the generic querying of this collection and I can't see any elegant way around this right now.

EDIT :

Sadly separate collections is a non-starter as we mix filter expressions to pull back different types using the same query. In the "cats and dogs" example from above like this :

var catQuery = Query<Cat>.EQ(c => c.LikesFish, true);
var dogQuery = Query<Dog>.EQ(c => c.FavouriteBone, "Beef");
var q = Query.Or(catQuery, dogQuery);

var catsThatLikeFishOrDogsThatLikeBeef = animals.FindAs<Animal>(q).ToList();

I'll look at the "nameof" method above - that may work, but it seems to lack the elegance of the old way to me...

Any help much appreciated!

Thanks,

Steve

Upvotes: 5

Views: 5344

Answers (2)

Steve H
Steve H

Reputation: 81

OK, this does seem to work :

var catQuery = Builders<Animal>.Filter.Eq(nameof(Cat.LikesFish), true);
var dogQuery = Builders<Animal>.Filter.Eq(nameof(Dog.FavouriteBone), "Beef");
var query = Builders<Animal>.Filter.Or(catQuery, dogQuery);
var catsThatLikeFishOrDogsThatLikeBeef = animals.FindSync(query).ToList();

although I do think it lacks the elegance of the old driver method.

Upvotes: 0

Maksim Simkin
Maksim Simkin

Reputation: 9679

You have a collection of cats and dogs togetehr and you want to filter just cats? Or do you want to get all dogs and only that cats that like fish?

In first case i would suggest to query only cats or only dogs from the collection:

var collectionAsCats = db.GetCollection<Cat>("Animal");
var collectionAsDogs = db.GetCollection<Dog>("Animal");

var catsDoesntlikeFish = collectionAsCats.Find(c => c.LikesFish == false).ToList();
var dogs = collectionAsDogs.Find(c => c.FavouriteBone == "bla").ToList();

Or you could have one collection of Animals and query your data with strings:

var collectionAll = db.GetCollection<Animal>("Animal");
var filter = Builders<Animal>.Filter.Eq(nameof(Cat.LikesFish), false);
var catsDoesntlikeFish = collectionAll.Find(filter).As<Animal>().ToList();

You could extend this filter if you want to get dogs together with cats:

var collectionAll = db.GetCollection<Animal>("Animal");
var filter = Builders<Animal>.Filter.Eq(nameof(Cat.LikesFish), false);
var exists = Builders<Animal>.Filter.Exists(nameof(Cat.LikesFish), false);
var orFilter = Builders<Animal>.Filter.Or(filter, exists);
var catsDoesntlikeFishAndDogs = collectionAll.Find(orFilter).ToList();

EDIT

I add here a comment from Craig Wilson, very interest information (thanks, Craig):

in the new API there is an OfType() method...

IMongoCollection<Dog> dogs = db.GetCollection<Animal>("Animals").OfType<Dog>()

Upvotes: 8

Related Questions