I am developing a project that uses MongoDB (with C# driver) and DDD.
I have a class (aggregate) which have a property which type is an interface. In another class, I have implemented this interface. This class has another property which type is an interface and is setted with another implemented class.
The code below explains better:
// 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 struct Partner : IPartner {
public IPhone Mobile { get; set; }
}
public struct Phone : IPhone {
public string Number { get; set; }
}
Well, when I call the MongoCollection<User>.Insert()
method, it throws two exceptions:
System.IO.FileFormatException: An error occurred while deserializing
the Partner property of class .User: An error
occurred while deserializing the Phone property of class
.Partner: Value class
.Mobile cannot be deserialized. --->
System.IO.FileFormatException: An error occurred while deserializing
the Mobile property of class .Partner: Value
class .Phone cannot be deserialized. --->
MongoDB.Bson.BsonSerializationException: Value class
.Phone cannot be deserialized.
Then, I searched the internet for discover how to deserialize the type as an interface, and I think I have to ways to do it: mapping the property with a cast, using the BsonClassMap.RegisterClassMap
or writing a custom BSON serializer.
I need to know which of these two ways is better and how to implement it.
Note: I need a solution that does not modify the interfaces, because theirs project cannot contain any external reference.
Well, I have found a lot of problems when trying to get this answer.
First of all, the MongoDB C# Driver, does have some problems when deserializing interfaces, like said by Craig Wilson in this question comments, and as described in the issue page.
The secure implementation for this problem, like I said before, really may be a custom BSON serializer or a specific class map, using BsonClassMap.RegisterClassMap
.
So, I have implemented the class map and the problem persisted.
Looking forward with the problem, I have found that exception is related to another issue of the driver: the problem when deserializing structs
.
I have rolled back the project to the initial state (without classes map or custom serializers) and changed the struct type to class type, and it worked.
In resume, this exception error is related to structs deserialization, not with interfaces deserialization.
Anyway, it is a real problem, and the second issue needs to be considered more a bug than a improvement, like the first issue is.
You can find the issues at these links:
- https://jira.mongodb.org/browse/CSHARP-485
- https://jira.mongodb.org/browse/CSHARP-94
[BsonSerializer(typeof(ImpliedImplementationInterfaceSerializer<IReviewExpert, ReviewExpert>))]
public IReviewExpert Expert { get; set; }
works for me
We are on the 1.x branch of mongo drivers and that sadly doesn't have the ImpliedImplementationInterfaceSerializer
that was suggested by Robert Baker which seems to be otherwise a good solution. To this end I created my own serializer that allows you to specify a confcrete type for an interface member.
public class ConcreteTypeSerializer<TInterface, TImplementation> : BsonBaseSerializer where TImplementation : TInterface
{
private readonly Lazy<IBsonSerializer> _lazyImplementationSerializer;
public ConcreteTypeSerializer()
{
var serializer = BsonSerializer.LookupSerializer(typeof(TImplementation));
_lazyImplementationSerializer = new Lazy<IBsonSerializer>(() => serializer);
}
public override object Deserialize(BsonReader bsonReader, Type nominalType, Type actualType, IBsonSerializationOptions options)
{
if (bsonReader.GetCurrentBsonType() == BsonType.Null)
{
bsonReader.ReadNull();
return default(TInterface);
}
else
{
return _lazyImplementationSerializer.Value.Deserialize(bsonReader, nominalType, typeof(TImplementation), options);
}
}
public override void Serialize(BsonWriter bsonWriter, Type nominalType, object value, IBsonSerializationOptions options)
{
if (value == null)
{
bsonWriter.WriteNull();
}
else
{
var actualType = value.GetType();
if (actualType == typeof(TImplementation))
{
_lazyImplementationSerializer.Value.Serialize(bsonWriter, nominalType, (TImplementation)value, options);
}
else
{
var serializer = BsonSerializer.LookupSerializer(actualType);
serializer.Serialize(bsonWriter, nominalType, value, options);
}
}
}
}
Usage is as follows:
[BsonSerializer(typeof(ConcreteTypeSerializer<IMyInterface,MyClass>))]
public IMyInterface MyProperty {get; set;}
A few notes on the code - all it really does is lazily loads the serializer for the appropriate concrete type and then passes on all the serialize/deserialize calls to that with the appropriate concrete type instead of the interface.
It also checks that the type is actually of the expected type and if not just finds the default serializer for the type.