[CSHARP-2355] OfType doesn't consider known derived classes Created: 12/Aug/18  Updated: 27/Oct/23  Resolved: 04/Jan/19

Status: Closed
Project: C# Driver
Component/s: Linq
Affects Version/s: 2.7.0
Fix Version/s: None

Type: Task Priority: Minor - P4
Reporter: Oleksii Bespalov Assignee: Robert Stam
Resolution: Works as Designed Votes: 0
Labels: LINQ, OfType, Queryable, question
Remaining Estimate: Not Specified
Time Spent: Not Specified
Original Estimate: Not Specified


 Description   

For the following inheritance chain:

[BsonKnownTypes(typeof(B))]
public class A
{
    public ObjectId Id { get; set; }
}
 
[BsonKnownTypes(typeof(C))]
public class B: A { }
 
public class C: B { }

querying with OfType wih the base type B doesn't consider derived classes added via BsonKnownTypes:

 

var collection = new MongoClient("mongodb://localhost/")
    .GetDatabase("Test")
    .GetCollection<A>(Guid.NewGuid().ToString());
collection.InsertOne(new C());
 
Assert.Single(collection.AsQueryable().OfType<B>().AsEnumerable());

while for regular IEnumerable it does:

var collection = new List<A> { new C() };
 
Assert.Single(collection.OfType<B>());

 

 

 



 Comments   
Comment by Robert Stam [ 04/Jan/19 ]

This is an interesting issue. It raises the question of whether OfType<B> should only match documents of type B or also subclasses of B.

I hesitate to change the current behavior of OfType<> because it could be considered a breaking change.

If you want of OfType<B> to also match subclasses of B also you could configure your POCOs so that the discriminator is an array of all types (starting from the class identified as the root of the polymorphic hierarchy).

You configure the driver to use array discriminators by tagging which class should be considered as the root of the polymorphic hierarchy. So using your example (but changing the Id type to int for convenience):

[BsonDiscriminator(RootClass = true)] // identify A as the root class
[BsonKnownTypes(typeof(B))]
public class A
{
    public int Id { get; set; }
}
 
[BsonKnownTypes(typeof(C))]
public class B : A { }
 
public class C : B { }

If you then insert the following documents:

var client = new MongoClient("mongodb://localhost");
var database = client.GetDatabase("test");
var collection = database.GetCollection<A>("test");
database.DropCollection("test");
 
collection.InsertOne(new A { Id = 1 });
collection.InsertOne(new B { Id = 2 });
collection.InsertOne(new C { Id = 3 });

We can see the resulting discriminators using the MongoDB shell:

> db.test.find()
{ "_id" : 1, "_t" : "A" }
{ "_id" : 2, "_t" : [ "A", "B" ] }
{ "_id" : 3, "_t" : [ "A", "B", "C" ] }
>

When the discriminator is configured this way OfType<B> will also match documents of type C because of the way MongoDB handles queries against array fields. Look at these results in the MongoDB shell to see that:

> db.test.find({ _t : "A" })
{ "_id" : 1, "_t" : "A" }
{ "_id" : 2, "_t" : [ "A", "B" ] }
{ "_id" : 3, "_t" : [ "A", "B", "C" ] }
>
> db.test.find({ _t : "B" })
{ "_id" : 2, "_t" : [ "A", "B" ] }
{ "_id" : 3, "_t" : [ "A", "B", "C" ] }
>
> db.test.find({ _t : "C" })
{ "_id" : 3, "_t" : [ "A", "B", "C" ] }
>

To verify the desired behavior when the disciminators are configure this way (using C# code) try:

var a = collection.Find("{}").ToList();
var b = collection.OfType<B>().Find("{}").ToList();
var c = collection.OfType<C>().Find("{}").ToList();

a should contain 3 items. b should contain 2 items. c should contain 1 item.

Due to the reluctance to make a breaking change to the behavior of OfType<> and the availability of a convenient workaround I'm going to close this as "Works as Designed".

 

 

 

 

 

 

 

 

Generated at Wed Feb 07 21:42:19 UTC 2024 using Jira 9.7.1#970001-sha1:2222b88b221c4928ef0de3161136cc90c8356a66.