[CSHARP-485] Investigate Interface Usage in Bson library Created: 04/Jun/12  Updated: 08/Apr/15  Resolved: 08/Apr/15

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

Type: Improvement Priority: Minor - P4
Reporter: Craig Wilson Assignee: Robert Stam
Resolution: Done Votes: 6
Labels: None
Remaining Estimate: Not Specified
Time Spent: Not Specified
Original Estimate: Not Specified


 Description   

BsonClassMap.RegisterClassMap<IInterface> will not work correctly, even though there might be some validity to the need. Let's investigate this as well as other aspects of using interfaces as domain entities.

[Edit 2015-04-08]

Instead of calling BsonClassMap.RegisterClassMap<IInterface>, what is actually needed is to call BsonSerializer.RegisterSerializer<IInterface>(...). See comment below created on 2015-04-08.



 Comments   
Comment by Robert Stam [ 08/Apr/15 ]

In the 2.0 version of the driver the sample types provided by Gustavo Gondim can be successfully serialized and deserialized.

If you simply let the driver serialize them using default settings, the document that gets stored looks like this:

{ "_id" : CSUUID("00000000-0000-0000-0000-000000000000"), "Partner" : { "_t" : " Partner", "Mobile" : { "_t" : "Phone", "Number" : "555-1212" } } }

The "_t" values are called discriminators, and are required because we can't instantiate instances of an interface. We need to select a concrete class to instantiate. The "_t" value tells us which concrete class to instantiate.

If you want to suppress the "_t" values and use some implied concrete class during deserialization, you can configure that.

To configure that in code, do this:

var partnerSerializer = BsonSerializer.LookupSerializer<Partner>();
BsonSerializer.RegisterSerializer<IPartner>(new ImpliedImplementationInterfaceSerializer<IPartner, Partner>(partnerSerializer));
 
var phoneSerializer = BsonSerializer.LookupSerializer<Phone>();
BsonSerializer.RegisterSerializer<IPhone>(new ImpliedImplementationInterfaceSerializer<IPhone, Phone>(phoneSerializer));

This tells the driver that whenever it needs to deserialize an IPartner, it should assume that the implied implementation of the IPartner interface that we want to use is the Partner class (and similarly for Phone).

To configure this using attributes, define your classes as:

public class User : IUser
{
    [BsonId(IdGenerator = typeof(GuidGenerator))]
    public Guid Id { get; set; }
 
    [BsonIgnoreIfNull]
    [BsonSerializer(typeof(ImpliedImplementationInterfaceSerializer<IPartner, Partner>))]
    public IPartner Partner { get; set; }
}
 
public class Partner : IPartner
{
    [BsonSerializer(typeof(ImpliedImplementationInterfaceSerializer<IPhone, Phone>))]
    public IPhone Mobile { get; set; }
}
 
public class Phone : IPhone
{
    public string Number { get; set; }
}

Where we are using the [BsonSerializer] attribute to accomplish the same thing.

Unless you have a specific reason for needing to suppress the "_t" values, by far the easiest course of action is to simply let the driver write out the "_t" values and then everything just works automatically.

Comment by Robert Stam [ 19/Aug/14 ]

While it might be possible to get a class map for an interface to work when serializing objects, it's not likely to work on the way back in because we can't instantiate an instance of an interface, we need to instantiate an instance of a concrete class.

So it seems like the interface instance should be serialized as the actual type (the concrete type) with a discriminator (usually _t) to identify the actual type.

Comment by Gustavo Gondim [ 28/Jan/13 ]

It will be perfect if this improvement will be done. There is a real example with interface deserialization in C# Driver:

// Interfaces
public interface IUser {
    Guid Id { get; set;}
    IPartner Partner{ get; set; }
}
 
public interface IPartner {
    IPhone Mobile { get; set; }
}
 
public interface IPhone {
    string number { get; set; }
}
 
// Implemented Classes
public class User: IUser {
    [BsonId(IdGenerator = typeof(GuidGenerator))]
    public Guid Id { get; set; }
 
    [BsonIgnoreIfNull]
    public IPartner Partner { get; set; }
}
 
public class Partner : IPartner {
    public IPhone Mobile { get; set; }
}
 
public class Phone : IPhone {
    public string Number { get; set; }
}

Where "User" is the document to be stored in database.

To not see the MongoDB.Bson.BsonSerializationException today, when casting from a derived type to an interface, it needs to implement the BsonClassMap.RegisterClassMap with a cast to the interface. In another way, it needs to implement a custom BSON serializer class.

This improvement may reduce a lot of work.

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