In c# mongodb driver how to set dateOnly in serializer to nullable datetime object?

Viewed 2326

I have a model with a property (let's say it's called X) of type DateTime?.
I want to set like [BsonDateTimeOptions(DateOnly = true)] but using the class mapper in mongo c# driver like:

BsonClassMap.RegisterClassMap<MyModel>(cm =>
{
    cm.AutoMap();
    cm.MapMember(c => c.X).SetSerializer(new DateTimeSerializer(dateOnly: true));
});

But for some reason, I'm getting an error because the property is Nullable, for normal DateTime type it works properly.

Why I need this because I need to save the value of X as date only without timezone.

I already tried to build custom serializer but I'm getting an error when deserializing saying:

ReadBsonType can only be called when State is Type, not when State is Value

And here's the custom serializer:

public class DateTimeNullableSerialzier : DateTimeSerializer, IBsonSerializer
{
    public object Deserialize(BsonDeserializationContext context, BsonDeserializationArgs args)
    {
        if (context.Reader.CurrentBsonType == BsonType.Null)
            return null;

        return base.Deserialize(context, args);
    }

    public void Serialize(BsonSerializationContext context, BsonSerializationArgs args, object value)
    {
        if (value is null)
            context.Writer.WriteNull();
        else
            base.Serialize(context, args, (DateTime)value);
    }
}
2 Answers

If anyone's reading this and has the same issue, there is actually a way to do this without implementing your own serializer. This is with the 2.x version of the driver, though.

By default, mongo will use NullableSerializer<T> for your DateTime? field, but the default doesn't have dateOnly set. However, NullableSerializer<T> just wraps a normal serializer for the <T> type, and there is a way to give it your own instance that you have configured to use dateOnly:

var dateSerializer = new DateTimeSerializer(dateOnly: true);
var nullableDateOnlySerializer = new NullableSerializer<DateTime>().WithSerializer(dateSerializer));
BsonClassMap.RegisterClassMap<MyModel>(cm =>
{
    cm.AutoMap();
    cm.MapMember(c => c.X).SetSerializer(nullableDateOnlySerializer);
});

Actually, how I implemented the serializer was the problem, after doing some fixes it became usable for any nullable DateTime property (DateTime?) and accept the same constructor params as in DateTimeSerializer (but will not work for DateTime types, for some reason):

public class DateTimeNullableSerializer : IBsonSerializer
{
    public Type ValueType { get; }
    private DateTimeSerializer dateTimeSerializer;
    /// <summary>
    /// Initializes a new instance of the <see cref="T:DateTimeNullableSerializer" /> class.
    /// </summary>
    public DateTimeNullableSerializer()
    {
        ValueType = typeof(DateTime?);
        dateTimeSerializer = new DateTimeSerializer(DateTimeKind.Utc, BsonType.DateTime);
    }

    /// <summary>
    /// Initializes a new instance of the <see cref="T:DateTimeNullableSerializer" /> class.
    /// </summary>
    /// <param name="dateOnly">if set to <c>true</c> [date only].</param>
    public DateTimeNullableSerializer(bool dateOnly)
    {
        ValueType = typeof(DateTime?);
        dateTimeSerializer = new DateTimeSerializer(dateOnly);
    }

    /// <summary>
    /// Initializes a new instance of the <see cref="T:DateTimeNullableSerializer" /> class.
    /// </summary>
    /// <param name="dateOnly">if set to <c>true</c> [date only].</param>
    /// <param name="representation">The representation.</param>
    public DateTimeNullableSerializer(bool dateOnly, BsonType representation)
    {
        ValueType = typeof(DateTime?);
        dateTimeSerializer = new DateTimeSerializer(dateOnly, representation);
    }

    /// <summary>
    /// Initializes a new instance of the <see cref="T:DateTimeNullableSerializer" /> class.
    /// </summary>
    /// <param name="representation">The representation.</param>
    public DateTimeNullableSerializer(BsonType representation)
    {
        ValueType = typeof(DateTime?);
        dateTimeSerializer = new DateTimeSerializer(DateTimeKind.Utc, representation);
    }

    /// <summary>
    /// Initializes a new instance of the <see cref="T:DateTimeNullableSerializer" /> class.
    /// </summary>
    /// <param name="kind">The kind.</param>
    public DateTimeNullableSerializer(DateTimeKind kind)
    {
        ValueType = typeof(DateTime?);
        dateTimeSerializer = new DateTimeSerializer(kind, BsonType.DateTime);
    }

    /// <summary>
    /// Initializes a new instance of the <see cref="T:DateTimeNullableSerializer" /> class.
    /// </summary>
    /// <param name="kind">The kind.</param>
    /// <param name="representation">The representation.</param>
    public DateTimeNullableSerializer(DateTimeKind kind, BsonType representation)
    {
        ValueType = typeof(DateTime?);
        dateTimeSerializer = new DateTimeSerializer(kind, representation);
    }

    public object Deserialize(BsonDeserializationContext context, BsonDeserializationArgs args)
    {
        if (context.Reader.CurrentBsonType == BsonType.Null)
        {
            context.Reader.ReadNull();
            return null;
        }

        return dateTimeSerializer.Deserialize(context, args);
    }

    public void Serialize(BsonSerializationContext context, BsonSerializationArgs args, object value)
    {
        if (value is null)
            context.Writer.WriteNull();
        else
            dateTimeSerializer.Serialize(context, args, (DateTime)value);
    }
}
Related