[CSHARP-756] Breaking change in serialization from 1.7.1 to 1.8.1 Created: 11/Jun/13  Updated: 05/Apr/19  Resolved: 01/Jul/13

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

Type: Task Priority: Critical - P2
Reporter: Stefano Ricciardi Assignee: Robert Stam
Resolution: Done Votes: 0
Labels: question
Remaining Estimate: Not Specified
Time Spent: Not Specified
Original Estimate: Not Specified
Environment:

Windows client, Windows server


Backwards Compatibility: Major Change

 Description   

We are in the process of upgrading our code to use driver 1.8.1.

Code now compiles fine after removing the warning for obsoletes function calls. However our acceptance tests are raising exceptions of the following kind:

"MongoDB.Bson.Serialization.Serializers.BsonDocumentSerializer can only be used with type MongoDB.Bson.BsonDocument, not with type xxx.Resource."

Resource is a custom class of ours extending BsonDocument and implementing a custom interface.

For example the exception is thrown by the second line in the following snippet:

MongoCollection<Resource> Entities = mongo.DB.GetCollection<Resource>("mycollection");
Resource found = Entities.FindOneByIdAs<Resource>("abc");

This used to work fine in 1.7.1 and all previous versions.

Any tip?



 Comments   
Comment by Stefano Ricciardi [ 14/Jun/13 ]

Just to confirm that we were able to implement something along the lines of your solution and now everything work fines with the latest driver. Thank you and keep up the good work.

Comment by Stefano Ricciardi [ 11/Jun/13 ]

Yes,

that's similar to something we have been discussing about within our team
today. We'll have a try tomorrow and let you know how it goes.

Thank you.

Stefano


Stefano Ricciardi
http://www.stefanoricciardi.com

Comment by Robert Stam [ 11/Jun/13 ]

Would you be willing to use delegation instead of inheritance? You could define your Resource class something like this:

public class Resource : DynamicObject
{
    private BsonDocument _backingDocument;
 
    public Resource()
    {
        _backingDocument = new BsonDocument();
    }
 
    public Resource(BsonDocument backingDocument)
    {
        _backingDocument = backingDocument;
    }
 
    public BsonDocument BackingDocument
    {
        get { return _backingDocument; }
    }
 
    public override bool TryGetMember(GetMemberBinder binder, out object result)
    {
        var name = binder.Name;
        BsonValue value;
        if (_backingDocument.TryGetValue(name, out value))
        {
            result = BsonTypeMapper.MapToDotNetValue(_backingDocument[name]);
            return true;
        }
        else
        {
            result = null;
            return false;
        }
    }
 
    public override bool TrySetMember(SetMemberBinder binder, object value)
    {
        var name = binder.Name;
        _backingDocument[name] = BsonTypeMapper.MapToBsonValue(value);
        return true;
    }
}

and write a very simple serializer for it:

public class ResourceSerializer : BsonBaseSerializer
{
    public override object Deserialize(BsonReader bsonReader, Type nominalType, Type actualType, IBsonSerializationOptions options)
    {
        var backingDocument = (BsonDocument)BsonDocumentSerializer.Instance.Deserialize(bsonReader, typeof(BsonDocument), typeof(BsonDocument), null);
        return new Resource(backingDocument);
    }
 
    public override void Serialize(BsonWriter bsonWriter, Type nominalType, object value, IBsonSerializationOptions options)
    {
        var resource = (Resource)value;
        var backingDocument = resource.BackingDocument;
        BsonDocumentSerializer.Instance.Serialize(bsonWriter, typeof(BsonDocument), backingDocument, null);
    }
}

I used the following code to test the concept:

BsonSerializer.RegisterSerializer(typeof(Resource), new ResourceSerializer());
 
dynamic resource = new Resource();
resource.x = 1;
resource.y = 2;
var json = BsonExtensionMethods.ToJson(resource);
 
dynamic rehydrated = BsonSerializer.Deserialize<Resource>(json);
var x = rehydrated.x;
var y = rehydrated.y;

Comment by Stefano Ricciardi [ 11/Jun/13 ]

Robert,

thank you for your prompt response.

Our Resource class basically encapsulates the concept of a dynamic object. This object contains fields which can be set and read by the client applications and are not known in advance to us as a library. Think of it like a json object or a bag of properties.

Extending BsonDocument we were able to exploit its dynamic behavior and to expose it to the domain model (which is Mongo agnostic) as some kind of json object via a custom interface of ours that Resource implemented.

Comment by Robert Stam [ 11/Jun/13 ]

Hi Stefano,

Thanks for reporting this issue. Sorry for the trouble it has caused you.

In v1.8 we changed how the BsonDocument object model is serialized and deserialized. One of the changes was to enforce that the BsonDocument related serializers don't get called with objects of the wrong type. It's this new check that you are encountering.

As a general rule each class should have its own serializer, because each class probably has new fields that should be serialized that the base class serializer won't know about. Having said that, it is a bug in v1.8.1 that the driver itself chose the BsonDocumentSerializer for your Resource class, and we need to fix that.

Can you tell us a bit more about your use case? Does your Resource class that derives from BsonDocument not have any new fields that need to be serialized?

Once we know a bit more about your use case we can provide some suggestions for workarounds.

Comment by Stefano Ricciardi [ 11/Jun/13 ]

I have added a sample reproduction project on GitHub: https://github.com/stefanoric/testmongoserializationerror

Stefano R.

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