MongoDB C# Driver serializing the discriminator twice when discriminator is explicitly declared in the class

183 views
Skip to first unread message

Sylvain Boissé

unread,
Jun 5, 2019, 8:29:52 AM6/5/19
to mongodb-csharp
I have a base class with a property named Type meant to be used as a discriminator, but that can be used in the C# code to tell what we are dealing with. Say we are dealing with vehicles:

    public class abstract Vehicle
    {
        public VehicleType Type {get; set;}
    }

    public enum VehicleType
    {
        SmallCar,
        BigTruck,
    }

This class is inherited by other classes:

    public class Car: Vehicle
    {
    }

    public class Truck: Vehicle
    {
    }

I have a collection of Vehicle in the database that can contain documents of any subclass that inherits from it.

    var collection = mongoDatabase.GetCollection<Vehicle>("myVehicles");

So I register a discriminator convention to be able to serialize or deserialize the documents properly, using the Type property of Vehicle:

    BsonSerializer.RegisterDiscriminatorConvention(typeof(Vehicle), new ScalarDiscriminatorConvention(nameof(Vehicle.Type)));

    // Note: serialization convention being used for enums is string.
    BsonSerializer.RegisterDiscriminator(typeof(Car), VehicleType.Car.ToString());
    BsonSerializer.RegisterDiscriminator(typeof(Truck), VehicleType.Truck.ToString());

Now if later on I want to deserialize documents from the database into C#, it works great. I get documents instantiated to C# objects in the correct type and everything is populated correctly inside of them.

But if I insert something into the collection:

    var myTruck = new Truck { Type = VehicleType.BigTruck };
    mongoDatabase.GetCollection<Vehicle>.InsertOneAsync(myTruck);

What gets inserted the database is the following:

    {
        Type: 'BigTruck',
        Type: 'Truck'
    }

The Type field is put twice. I have been integrating the source code of MongoDB C# driver v2.8.1 to investigate why.

What I found is in the class BsonClassMapSerializer<TClass>, no check is made if the class already contains a property with the same name as the discriminator:

    if (ShouldSerializeDiscriminator(args.NominalType))
    {
        SerializeDiscriminator(context, args.NominalType, document);
    }

    [...]

    private bool ShouldSerializeDiscriminator(Type nominalType)
    {
        return (nominalType != _classMap.ClassType || _classMap.DiscriminatorIsRequired || _classMap.HasRootClass) && !_classMap.IsAnonymous;
    }

So it seems to be expected by the serializer that if a discriminator is used, that this discriminator is not to be explicitly declared in the nominal type.

Because of that expectation, it gets serialized a first time because the property is there, just like any other property. But then it gets added again by the serializer because it determines the object requires discrimination.

I have figured out a few workarounds in order to be able to explicitly declare the discriminator without having it being serialized twice.

For example, from what I see here:

    private void SerializeDiscriminator(BsonSerializationContext context, Type nominalType, object obj)
    {
        var discriminatorConvention = _classMap.GetDiscriminatorConvention();
        if (discriminatorConvention != null)
        {
            var actualType = obj.GetType();
            var discriminator = discriminatorConvention.GetDiscriminator(nominalType, actualType);
            if (discriminator != null)
            {
                context.Writer.WriteName(discriminatorConvention.ElementName);
                BsonValueSerializer.Instance.Serialize(context, discriminator);
            }
        }
    }

I see that if I implement my own custom discriminator convention and make its GetDiscriminator() call to return null when the nominal type is Vehicle, it will skip serializing the discriminator.

But it seems to me like the serialization code of the driver should check for this to avoid serializing the discriminator twice in the same document.
Reply all
Reply to author
Forward
0 new messages