[CSHARP-2132] "Duplicate element name '...'" when serializing a derived type with a discriminator Created: 19/Dec/17  Updated: 27/Oct/23  Resolved: 01/Feb/18

Status: Closed
Project: C# Driver
Component/s: BSON, Serialization
Affects Version/s: 2.5
Fix Version/s: None

Type: Task Priority: Minor - P4
Reporter: Cy Assignee: Robert Stam
Resolution: Works as Designed Votes: 0
Labels: question
Remaining Estimate: Not Specified
Time Spent: Not Specified
Original Estimate: Not Specified

Backwards Compatibility: Minor Change

 Description   

When using a custom property for a discriminator value, there is a bug that occurs when serializing the object. The code below reproduces the error:

[BsonKnownTypes(typeof(ChildClass1))]
public abstract class ParentClass
{
	public string Type { get; set; }
}
 
[BsonDiscriminator("Child1")]
public class ChildClass1 : ParentClass
{
	public ChildClass1()
	{
		Type = "Child1";
	}
}
 
public class ParentClassDiscriminator : IDiscriminatorConvention
{
	public string ElementName { get; } = "Type";
 
	public Type GetActualType(IBsonReader bsonReader, Type nominalType)
	{
		var bookmark = bsonReader.GetBookmark();
 
		bsonReader.ReadStartDocument();
		if (!bsonReader.FindElement(ElementName))
		{
			throw new NotSupportedException($"Could not find element named: {ElementName}");
		}
 
		try
		{
			var val = bsonReader.ReadString();
 
 
			switch (val ?? "")
			{
				case "Child1":
					return typeof(ChildClass1);
				default:
					throw new NotSupportedException($"Could not find a Type to match type of: {val}");
			}
		}
		finally
		{
			bsonReader.ReturnToBookmark(bookmark);
		}
	}
 
	public BsonValue GetDiscriminator(Type nominalType, Type actualType)
	{
		if (actualType == typeof(ChildClass1))
		{
			return "Child1";
		}
 
		throw new NotImplementedException();
	}
}
public static class Bug
{
	public static void Example()
	{
		BsonSerializer.RegisterDiscriminatorConvention(typeof(ParentClass), new ParentClassDiscriminator());
 
		//case 1
		var object1 = new ChildClass1();
		object1.ToBsonDocument();//does not throw an error
 
		//case 2
		ParentClass object2 = new ChildClass1();
		object2.ToBsonDocument();//throws InvalidOperationException("Duplicate element name 'Type'.")
	}
}

In case case 1 this does not cause an issue since this line evaluates as false. However, in case 2 the evaluation is true which causes the serializer to write the discriminator value before continuing to serialize the rest of the object.

The solution should be to change the "SerializeDiscriminator" method to:

private void SerializeDiscriminator(BsonSerializationContext context, Type nominalType, object obj, List<BsonMemberMap> remainingMemberMaps)
{
	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);
 
			var memberMap = remainingMemberMaps.FirstOrDefault(
				bsonMemberMap => bsonMemberMap.ElementName == discriminatorConvention.ElementName);
			if (memberMap != null)
			{
				remainingMemberMaps.Remove(memberMap);
			}
		}
	}
}

Also change line 599 to:

SerializeDiscriminator(context, args.NominalType, document, remainingMemberMaps);



 Comments   
Comment by Robert Stam [ 01/Feb/18 ]

Your scenario can already be handled by the driver with no changes needed to the driver itself.

You were very much on the right track with your custom discriminator convention, but a very small change is needed: since the discriminator value is already being written to the document as part of the normal serialization (in the "Type" field), all you need to change is to return null from the GetDiscriminator method of your custom discriminator convention. That signals the driver to not write out the usual "_t" value.

As long as GetDiscriminator returns null it actually doesn't matter whether ElementName returns null or returns "Type".

Comment by Cy [ 19/Dec/17 ]

I created a PR that fixes this issue.

Generated at Wed Feb 07 21:41:42 UTC 2024 using Jira 9.7.1#970001-sha1:2222b88b221c4928ef0de3161136cc90c8356a66.