In the previous article I’ve talked about the use of a Value Object and you’ve seen the advantages of a value object in practice.
One challenge that occurs is regarding saving value objects in a database. And you will see why.
Let’s define our entity class:
public class Email : ValueObject<string> { public Email() { //for expando deserializer } public Email(SerializationInfo info, StreamingContext context) { //The special constructor is used to deserialize values. this.GetObjectData(info, context); } public static Email Create(string value) { return new Email { Value = value }; } public override int CompareTo(ValueObject<string> other) { return this.Value.CompareTo(other.Value); } protected override bool EqualsCore(object obj) { if (obj is null) return false; if (obj is ValueObject<string> vo) { return this.Value == vo.Value; } return false; } protected override int GetHashCodeCore() { return this.Value.GetHashCode(); } protected override void Validators(ValidationBuilder<string, string> validators) { validators.Add(x => (!string.IsNullOrEmpty(x)) ? ValidationResult<string>.Success() : ValidationResult<string>.Fail("Email cannot be empty"), 0); } }
public class ContactDetails : BasicEntity { [BsonElement("Email")] public Email Email { get; set; } }
This model contains an email value object. The actual value of the email is in fact in the Value property of the value object.
So how would the document look like in mongo?
This is not what we want. Imagine having this for all the value objects we add to our solution. Luckly for us, we have a solution for this in Mongo. We have to explicitly tell Mongo how to serialize and deserialize our value objects.
For this we will need the BsonSerializerAttribute and the IBsonSerializer interface. The BsonSerializer attribute takes a type as an input. The type must inherit the IBsonSerializer interface.
So, Let’s create the serializer for our email object:
public class EmailSerializer : IBsonSerializer<Email> { public Type ValueType => typeof(Email); public Email Deserialize(BsonDeserializationContext context, BsonDeserializationArgs args) { var value = context.Reader.ReadString(); return Email.Create(value); } public void Serialize(BsonSerializationContext context, BsonSerializationArgs args, Email value) { context.Writer.WriteString(value.Value); } public void Serialize(BsonSerializationContext context, BsonSerializationArgs args, object value) { if (value is Email email) { context.Writer.WriteString(email.Value); } else { throw new NotSupportedException("This is not an email"); } } object IBsonSerializer.Deserialize(BsonDeserializationContext context, BsonDeserializationArgs args) { var value = context.Reader.ReadString(); return Email.Create(value); } }
And of course, decorate the Email value object with the BsonSerializerAttribute.
[BsonSerializer(typeof(EmailSerializer))] public class Email : ValueObject<string>
Now when we save our model in the mongo db, we can see the difference (the value of the email is no longer a propertie of the field Email, but it’s directly written as it’s value):