6

I have a single MongoDB collection holding documents of three different classes (A,B,C) which all inherit from a common class D.

Using the official C# driver, I have inserted documents of all three types (A,B,C) and they all appear correctly with the _t discriminator, and in my code their class maps are registered.

If I issue a LINQ query such as the following (using VB):

dim Result = database.GetCollection("mycol").AsQueryable(Of C).Where(some where clause)

If I count the results of this, I am getting an error "Element 'an element name from class A' does not match any field or property of class C."

Isn't the discriminator meant to kick in here in the AsQueryable(Of C) code? It appears that when I issue .Count my Where clause, which is specific to elements of class C, is being applied to documents of A,B, and C.

I have tried adding .OfType(Of C) with no effect, have tried converting to a List first with .ToList, but I continue to get the same error. Any ideas?

As background, my client code will usually deal with objects of type D. A, B, C share a lot of common properties inherited from D which I want to put indexes on, hence I put them in a single collection. However, occassionally I need to directly reference an object of types A, B, or C in special circumstances.

C R
  • 2,182
  • 5
  • 32
  • 41
Greg May
  • 124
  • 1
  • 10

5 Answers5

17

When working with a polymorphic type hierarchy your collection variable and your LINQ queries should start in terms of the base class. For example, to read all the documents of type A back from the database you would write:

var collection = database.GetCollection<D>("mycol");
var query = collection.AsQueryable<D>().OfType<A>();
foreach (var a in query)
{
    // process document of type A
}

For diagnostic purposes you can see the corresponding native MongoDB query using:

var json = ((MongoQueryable<A>)query).GetMongoQuery().ToJson();

Note that you have to cast query to a MongoQueryable<A> (not MongoQueryable<D>) because the OfType() call changed the type of the IQueryable.

Robert Stam
  • 12,039
  • 2
  • 39
  • 36
  • 2
    Fantastic nugget of information. I'd encourage you to add this to the Polymorphism section in the mongodb docs (or point to it if it is already there) -- I know I could have used it. – Jeremy Smith Sep 20 '12 at 22:33
3

Use .AsQueryable<D>().OfType<C>. This should effectively include the discriminator automatically. The issue is that we don't necessarily know that you are using the same collection for A, B, and C and therefore don't know that when you do AsQueryable<C>(), we actually need to add a discriminator. We'll be looking into how to make this more seamless in the future.

Craig Wilson
  • 12,174
  • 3
  • 41
  • 45
  • Hi Craig, thanks so much for the quick answer. Unfortunately no difference. As mentioned in my original post I had tried that but it doesn't seem to change anything. Are there any diagnostics which come with the driver, e.g. is it possible to see the underlying query? Much like you can do with a Query object (i.e. Query.ToString) – Greg May Jul 22 '12 at 03:21
  • I also tried `AsQueryable().OfType(Of C)` as opposed to `AsQueryable(Of C).OfType(Of C)` and got "No coercion operator is defined between types 'MongoDB.Bson.BsonDocument' and 'C' – Greg May Jul 22 '12 at 03:26
  • One more comment :) The reason why I ask about diagnostics is that I can issue a virtually identical command using an IMongoQuery and I get the correct result. I am trying to compose a common query method for my non-MongoDB aware classes using Generics hence LINQ fits the bill. – Greg May Jul 22 '12 at 03:30
  • Yeah, sorry... looks like the formatting killed the greater than and less than signs in my answer (I should have checked that). Robert formatted his answer better. – Craig Wilson Jul 25 '12 at 05:17
  • Hi Craig, thank you - your amended answer and that of Robert have gotten me back on track. – Greg May Jul 28 '12 at 02:09
1

In my case, I needed to be able to retrieve A, B, as well as D, the base class. All of these were stored in the same collection. This is what I ended up doing in my repository:

    Shared Sub New()
        BsonClassMap.RegisterClassMap(Of D)(
            Sub(f)
                f.AutoMap()
                f.SetIsRootClass(True)
            End Sub)

        BsonClassMap.RegisterClassMap(Of A)()
        BsonClassMap.RegisterClassMap(Of B)()

    End Sub

This effectively adds the both the base class and the subclass to the discriminator .

_t: D, A

Which then allows me to query both types.

Dim collection = _db.GetCollection(of D)("Items")

Dim resultA = collection.AsQueryable().OfType(of A)  ' Only type A's
Dim resultB = collection.AsQueryable().OfType(of B)  ' Only type B's
Dim resultD = collection.AsQueryable().OfType(of D)  ' Both A's and B's as the base class
Jeremy Smith
  • 1,349
  • 8
  • 15
1

I have ran into the same issue: neither .AsQueryable() nor OfType() would produce a "_t" discriminator in the query. Especially when you have a high-level generic OpenQuery() method so that you can expose IQueryable interface to any plugins without them requiring Mongo driver library reference. Anyway, if using a static class for accessing your collections, telling BsonClassMap that the specific class is root will resolve this problem.

// Data Repository class
public static class MyDataRepositoryMethods
{


    static MyDataRepositoryMethods()
    {
        BsonClassMap.RegisterClassMap<MyBaseClassOfStuff>(x =>
        {
            x.AutoMap();
            x.SetIsRootClass(true);
        });
    }

    /// <summary>
    /// Returns a queryable data sample collection
    /// </summary>
    /// <typeparam name="TMyBaseClassOfStuff"></typeparam>
    /// <returns></returns>
    public static IQueryable<TMyBaseClassOfStuff> OpenQuery<TMyBaseClassOfStuff>() where TMyBaseClassOfStuff: MyBaseClassOfStuff
    {
        using (var storage = new MongoDataStorageStuff())
        {
            // _t discriminator will reflect the final class, not the base class (unless you pass generic type info of the base class
            var dataItems = storage.GetCollection<TMyBaseClassOfStuff>("abcdef").OfType<TMyBaseClassOfStuff>(); 
            return dataItems ;
        }
    }
}
test-in-prod
  • 498
  • 5
  • 19
0

I'm using C# driver v1.9.2 but seems this was introduced back in v1.4.1.

collection = MongoDatabase.GetCollection<MyBaseClass>("MyCollectionName");
long count = collection.AsQueryable().OfType<MyDerivedClass>().Count();

Profile contains this:

command: {
  "count" : "MyCollectionName",
  "query" : {
    "_t" : "D"  // MyDerivedClass discriminator value
  }
}
pero
  • 4,169
  • 26
  • 27