2 / 10
Oct 2024

When you have a class MyClassA and MyClassB, both implement from IMyInterface and decorated with [BsonDiscriminator], and then try to GetCollection and OfType, the resulting query ignores _t.
To illustrate, quick not-really-pseudo-code, as actual code is a tad more complex, but this should work just as well:

public interface IMyInterface { } [BsonDiscriminator("MyClassA")] public class MyClassA : IMyInterface { } [BsonDiscriminator("MyClassB")] public class MyClassB : IMyInterface { } await LoadAsync<MyClassB>(); public async Task<T> LoadAsync() where T : IMyInterface { IMongoCollection<T> collection = database.GetCollection<T>(collectionName); string query = collection.Aggregate().OfType<TSetting>().ToString(); // query in 3.0 = aggregate([{ "$match" : { } }]) // query in 2.30 = aggregate([{ "$match" : { "_t" : "MyClassB" } }]) T result = await collection.Aggregate().OfType<TSetting>().FirstOrDefaultAsync(); // result in 3.0 is MyClassA // result in 2.30 is MyClassB }

Same happens when using Aggregate().Match(item => item is MyClassB).
In a different place, I use a base class that is also mapped with IsRootClass true - and there the query seems to be correct.

This is a significant breaking change that has not been documented and caused serious bugs in my application. I am forced to rollback to 2.30 until this is resolved, or a workaround is provided.

Hi @TehGM,

It looks like we’ll need some more information here to be able to deduce the issue. What is TSetting in this context? If you can have a self contained simple project to reproduce the issue, we’ll be more than happy to look at it. Feel free to create a ticket for the same here.

Thanks,

Rishit.

Sorry, TSetting is a mere copy issue as I copied these 2 lines from actual project code. It should just be T, as in method signature. My bad.

We are facing the same exact issue in our entrerprise level software that uses mongo. OfType is the fundamental way our software communicates with mongo.

Hi @C_F ,

Welcome to the MongoDB Community Forums. What type of discriminator convention are you using (Scalar/Hierarchical/Custom)? The OfType and Is were intended to work when the _t is an array of values listing all classes in the hierarchy as described in this ticket. If you can share a self contained repro of the issue as mentioned above, we’ll be happy to investigate further!

Thanks,

Rishit.

Thank you for linking this ticket. This explains the issue we’re seeing and provides a workaround. .Where(item => item.GetType() == typeof(T)) seems to work and is easy to use. I might even create myself a small extension method for this.

This change should’ve been documented with the release notes, though.

We are using scalar, as described in the docs:
According to this convention, the .NET/C# Driver sets the value of the _t field to the name of the class from which the document was serialized.
The example objects are below.
Note that “[BsonDiscriminator(RootClass = true)]” is required in our use case, even with MongoDB.Driver 2.17.1, to get the correct result with collection.OfType().AsQueryable().FirstOrDefault();

namespace Service.Core.Entities { ... [BsonDiscriminator(RootClass = true)] public abstract class BaseEntity { [BsonId] [BsonRepresentation(BsonType.ObjectId)] public string Id { get; set; } } [BsonIgnoreExtraElements] public class MyEntityA: BaseEntity { public List<MyTypeA> PropertyA { get; set; } = new List(); } [BsonIgnoreExtraElements] public class MyEntityB: BaseEntity { public List<MyTypeB> PropertyB { get; set; } = new List(); } public T Get<T>(string collectionName) where T : BaseEntity { … var collection = _database.GetCollection(collectionName); returnValue = collection.OfType<T>().AsQueryable().FirstOrDefault(); }

Incidentally, MyEntityA is the first document listed in the collection when viewed with Compass.

In 2.17.1, the above code returns the correct documents.
However, with 3.0, Get<MyEntityB>(“myCollection”) produces a returnValue with an Id matching MyEntityA and PropertyB is null. Clearly, it matched the MyEntityA document instead.

Using the .Where method with 3.0 as shown in link, the below code produces returnValue = null for both MyEntityA and MyEntityB !

public T Get<T>(string collectionName) where T : BaseEntity { … var collection = _database.GetCollection(collectionName); returnValue = collection.AsQueryable().Where(t => t.GetType() == typeof(T)).FirstOrDefault(); }

@C_F Root class only makes sense with hierarchical discriminators. If you annotate a root class that should automatically trigger using a hierarchical discriminator. Does using Where(item => item.GetType() == typeof(T)) instead work?