Custom Serialization for MongoDB - Hashset with IBsonSerializable
The official C# driver for MongoDB does a great job serializing most objects without any extra work. Arrays, Lists and Hashsets all round trip nicely and share a common representation in the actual database as a simple array. This is great if you ever change the C# code from one collection type to another - there's no migration work to do on the database - you can write a List and retrieve a Hashset and everything just works.
But there are cases where everything doesn't work and one of these is a Hashset with a custom comparer. The MongoDB driver will instantiate a regular Hashset rather than one with the custom comparer when it materializes objects from the database.
Fortunately MongoDB provides several ways to override the default Bson serialization. Unfortunately the documentation doesn't include an example showing how to do it. So here's an example using the IBsonSerializable option. In this example I show a custom Hashset with a custom comparer to test for equality. It still serializes to an array in MongoDB but on deserialization it instantiates the correct Hashset with the custom comparer in place.
///
/// A HashSet with a specific comparer that prevents duplicate Entity Ids
///
public class EntityHashSet : HashSet, IBsonSerializable
{
private class EntityComparer : IEqualityComparer
{
public bool Equals(Entity x, Entity y)
{
return x.Id.Equals(y.Id);
}
public int GetHashCode(Entity obj)
{
return obj.Id.GetHashCode();
}
}
public EntityHashSet() : base(new EntityComparer()) { }
public EntityHashSet(IEnumerable values) : base (values, new EntityComparer()) { }
public void Serialize(MongoDB.Bson.IO.BsonWriter bsonWriter, Type nominalType, bool serializeIdFirst)
{
if (nominalType != typeof(EntityHashSet)) throw new ArgumentException("Cannot serialize anything but self");
ArraySerializer ser = new ArraySerializer();
ser.Serialize(bsonWriter, typeof(Entity[]), this.ToArray(), serializeIdFirst);
}
public object Deserialize(MongoDB.Bson.IO.BsonReader bsonReader, Type nominalType)
{
if (nominalType != typeof(EntityHashSet)) throw new ArgumentException("Cannot deserialize anything but self");
ArraySerializer ser = new ArraySerializer();
return new EntityHashSet((Entity[])ser.Deserialize(bsonReader, typeof(Entity[])));
}
public bool GetDocumentId(out object id, out IIdGenerator idGenerator)
{
id = null;
idGenerator = null;
return false;
}
public void SetDocumentId(object id) { return; }
}