Value Objects and Mongo Serializer — Digitteck
Value Objects and Mongo Serializer
dotnet·8 April 2019·3 min read

Value Objects and Mongo Serializer

When you store an entity that contains value objects in MongoDB, the C# driver serializes the entire value object — including internal fields and type discriminators — rather than just the scalar value inside it. A custom IBsonSerializer tells the driver exactly how to read and write the underlying primitive.

The Value Object

The Email value object wraps a string, adding validation and structural equality. It needs both a parameterless constructor (for the expando deserializer) and a serialization constructor:

csharp
// Email value object — wraps a string with validation and equality semantics
public class Email : ValueObject<string>
{
    public Email() { }  // required by expando deserializer

    public Email(SerializationInfo info, StreamingContext context)
    {
        this.GetObjectData(info, context);
    }

    public static Email Create(string value) => new Email { Value = value };

    public override int CompareTo(ValueObject<string> other)
        => this.Value.CompareTo(other.Value);

    protected override bool EqualsCore(object obj)
    {
        if (obj is ValueObject<string> vo) return this.Value == vo.Value;
        return false;
    }

    protected override int GetHashCodeCore() => 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"),
            priority: 0);
    }
}

The Problem

Without a custom serializer, the driver expands the value object's properties into the document — producing a nested object instead of a plain string field:

csharp
// Entity using the Email value object as a property
public class ContactDetails : BasicEntity
{
    [BsonElement("Email")]
    public Email Email { get; set; }
}

// Without a custom serializer, MongoDB serializes the full object tree:
// { "Email": { "Value": "[email protected]", "_t": "Email" } }
// — which is not what we want for a value object.

Custom IBsonSerializer

Implement IBsonSerializer<Email> to read and write the raw string via the BSON reader/writer:

csharp
// Custom IBsonSerializer for Email — reads/writes the raw string value
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)
    {
        return Email.Create(context.Reader.ReadString());
    }
}

Registering the Serializer

Decorate the value object class with [BsonSerializer(typeof(EmailSerializer))]. The driver picks it up automatically — no global registration needed:

csharp
// Decorate the value object class with the serializer attribute
[BsonSerializer(typeof(EmailSerializer))]
public class Email : ValueObject<string>
{
    // ...
}

// Now MongoDB stores the value directly as a string:
// { "Email": "[email protected]" }
// — clean, no wrapper object.

Tags

.NETC#MongoDBValue Object
digitteck

© 2026 Digitteck