Uploaded image for project: 'C# Driver'
  1. C# Driver
  2. CSHARP-2627

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

    • Type: Icon: Bug Bug
    • Resolution: Done
    • Priority: Icon: Major - P3 Major - P3
    • None
    • Affects Version/s: 2.8.1
    • Component/s: Serialization
    • Labels:
      None
    • Environment:
      .NET Core 2.0

      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.

            Assignee:
            wan.bachtiar@mongodb.com Wan Bachtiar
            Reporter:
            sboisse Sylvain Boissé
            Votes:
            0 Vote for this issue
            Watchers:
            3 Start watching this issue

              Created:
              Updated:
              Resolved: