Search code examples
c#.netmongodbmongodb-querymongodb-.net-driver

Projecting to another type using the C# SDK


I want to be able to project to another type when querying MongoDB using the C# SDK.

For example, below I want to query the collection using builder filters (or LINQ expressions) for MyType, but I want to project the result to MySubType.

var mySubTypes = Database
    .GetCollection<MyType>("MyCollection")
    .Find(Builders<MyType>.Filter.AnyIn(x => x.Documents, documentId))
    .ProjectTo<MySubType>() // Project to another type??
    .ToList();

One could imagine MySubType to be a subset of MyType, represented using inheritance:

public class MyType : MySubType
{
    [BsonElement("documents")]
    public List<string> Documents { get; set; }
}

public class MySubType
{
    [BsonElement("name")]
    public string Name { get; set; }
}

Why do I want to do this?

Because the Documents array is very large and used purely during querying (i.e. to filter) within the database engine. Retrieving and serializing this array will be an unnecessary cost.


Solution

  • I have found the way to execute mapping you want:

    collection
        .Find(Builders<MyType>.Filter.AnyIn(x => x.Documents, new[] { "c" }))
        .Project(Builders<MyType>.Projection.Exclude(c => c.Documents))
        .As<MySubType>()
        .ToList();
    

    But first you should register mapping for your SubType with ignoring extra element. I don't understand it 100%, seems to be a bug of the driver, it doesn't get Documents from mongo, but knows, that MyType has such property. Note, you should register your class mapping, before you first create collection of this type.

    if (!BsonClassMap.IsClassMapRegistered(typeof(MySubType)))
    {
        BsonClassMap.RegisterClassMap<MySubType>(cm =>
        {
            cm.AutoMap();
            cm.SetIgnoreExtraElements(true);
        });
    }
    

    I did it with sample data :

    var toInsert = new List<MyType>
    {
        new MyType {Id = 1, Name = "bla", Documents =new List<string> {"a", "b", "v"}},
        new MyType {Id = 2, Name = "ada", Documents =new List<string> {"c", "d", "r"}},
    };
    

    And could get the expected output:

    collection
        .Find(Builders<MyType>.Filter.AnyIn(x => x.Documents, new[] { "c" }))
        .Project(Builders<MyType>.Projection.Exclude(c => c.Documents))
        .As<MySubType>()
        .ToList()
        .Dump();
    

    enter image description here