Search code examples
c#mongodbmongodb-.net-driver

How can I use BsonClassMap to change the way a MongoDB C# Driver class is deserialized?


Due to the filter I'm applying to my changestream (discussed at SO: How do you filter updates to specific fields from ChangeStream in MongoDB), I am getting a BsonDocument back instead of a ChangeStreamDocument object. The only thing different about this BsonDocument from a ChangeStreamDocument is that it contains an extra element called "tmpfields".

In my scenario, I still need the ResumeToken and other elements in the document, so I'd like to convert this BsonDocument to a ChangeStreamDocument object. My first attempt was to use BsonSerializer.Deserialize<ChangeStreamDocument<BsonDocument>>( doc) where doc was the BsonDocument I got back. However, since it had the extra tmpfields element, this isn't allowed.

I attempted to register a BsonClassMap since the ChangeStreamDocument class is part of the C# driver and I couldn't add the [BsonIgnoreExtraElements] attribute to the class, but I wasn't successful:

BsonClassMap.RegisterClassMap<ChangeStreamDocument<BsonDocument>>(cm =>
{
    cm.AutoMap();
    cm.SetIgnoreExtraElements(true);
});

The AutoMap() didn't work though and I got an exception about "no matching creator found". I tried to cm.MapCreator(...), but wasn't succesffuly there either. I took the AutoMap() call out (only leaving the SetIgnoreExtraElements line) and got errors about it not being able to match the properties (_id, etc). So I tried lines like cm.MapProperty(c => c.DocumentKey).SetElementName("documentKey") for each of the properties, but they were never set when I used the Deserialize() method - they were left as null.

For now, I've reverted to using doc["field"].AsXYZ method to get the values that I need from the BsonDocument, but I'd like to learn a better way to do this.

Is using the RegisterClassMap the correct approach? If so, what did I miss?


Solution

  • I couldn't add the [BsonIgnoreExtraElements] attribute to the class

    If you just want to ignore the extra field. You could just add an extra aggregation pipeline $project to remove the field.

    For example

    var options = new ChangeStreamOptions { FullDocument = ChangeStreamFullDocumentOption.UpdateLookup };
    var addFields = new BsonDocument { { "$addFields", new BsonDocument { { "tmpfields", new BsonDocument { { "$objectToArray", "$updateDescription.updatedFields" } } } } } };
    var match = new BsonDocument { { "$match", new BsonDocument { { "tmpfields.k", new BsonDocument { { "$nin", new BsonArray{"a", "b"} } } } } } };
    
    // Remove the unwanted field. 
    var project = new BsonDocument { {"$project", new BsonDocument { {"tmpfields", 0 } } } };
    var pipeline = new[] { addFields, match, project };
    
    var cursor = collection.Watch<ChangeStreamDocument<BsonDocument>>(pipeline, options);
    
    var enumerator = cursor.ToEnumerable().GetEnumerator();
    while(enumerator.MoveNext())
    {
         ChangeStreamDocument<BsonDocument> doc = enumerator.Current;
         Console.WriteLine(doc.DocumentKey); 
    
     }