[CSHARP-3712] Discriminator convention with CommandStartedEvent subscriber throws InvalidOperationException: Duplicate element name. Created: 17/Jun/21  Updated: 27/Oct/23  Resolved: 26/Jul/21

Status: Closed
Project: C# Driver
Component/s: None
Affects Version/s: 2.12.4
Fix Version/s: None

Type: Bug Priority: Unknown
Reporter: Daigo Kobayashi Assignee: Mikalai Mazurenka (Inactive)
Resolution: Works as Designed Votes: 0
Labels: None
Remaining Estimate: Not Specified
Time Spent: Not Specified
Original Estimate: Not Specified
Environment:

macOS: 11.4
.Net: 5.0.203


Attachments: Zip Archive MongodbDriverTest.zip     PNG File generated_doc_structure.png    

 Description   

Using discriminator convention with following cluster configurator throws InvalidOperationException: Duplicate element name.

var url = "mongodb://localhost:27017";
var settings = MongoClientSettings.FromUrl(new MongoUrl(url));
// If you comment out the following settings, everything works fine.
settings.ClusterConfigurator = cb =>
{
    cb.Subscribe<CommandStartedEvent>(e => { Console.WriteLine(e.Command.ToJson()); });
};

I've attached a reproducible project. If you comment out the above code, everything works fine. Here is the stack trace:

Unhandled exception. System.InvalidOperationException: Duplicate element name 'UserType'.
   at MongoDB.Driver.Core.Connections.BinaryConnection.SendMessagesAsync(IEnumerable`1 messages, MessageEncoderSettings messageEncoderSettings, CancellationToken cancellationToken)
   at MongoDB.Driver.Core.WireProtocol.CommandUsingCommandMessageWireProtocol`1.ExecuteAsync(IConnection connection, CancellationToken cancellationToken)
   at MongoDB.Driver.Core.Servers.Server.ServerChannel.ExecuteProtocolAsync[TResult](IWireProtocol`1 protocol, ICoreSession session, CancellationToken cancellationToken)
   at MongoDB.Driver.Core.Operations.CommandOperationBase`1.ExecuteProtocolAsync(IChannelSource channelSource, ICoreSessionHandle session, ReadPreference readPreference, CancellationToken cancellationToken)
   at MongoDB.Driver.Core.Operations.WriteCommandOperation`1.ExecuteAsync(IWriteBinding binding, CancellationToken cancellationToken)
   at MongoDB.Driver.Core.Operations.FindAndModifyOperationBase`1.ExecuteAttemptAsync(RetryableWriteContext context, Int32 attempt, Nullable`1 transactionNumber, CancellationToken cancellationToken)
   at MongoDB.Driver.Core.Operations.RetryableWriteOperationExecutor.ExecuteAsync[TResult](IRetryableWriteOperation`1 operation, RetryableWriteContext context, CancellationToken cancellationToken)
   at MongoDB.Driver.Core.Operations.RetryableWriteOperationExecutor.ExecuteAsync[TResult](IRetryableWriteOperation`1 operation, IWriteBinding binding, Boolean retryRequested, CancellationToken cancellationToken)
   at MongoDB.Driver.OperationExecutor.ExecuteWriteOperationAsync[TResult](IWriteBinding binding, IWriteOperation`1 operation, CancellationToken cancellationToken)
   at MongoDB.Driver.MongoCollectionImpl`1.ExecuteWriteOperationAsync[TResult](IClientSessionHandle session, IWriteOperation`1 operation, CancellationToken cancellationToken)
   at MongoDB.Driver.MongoCollectionImpl`1.UsingImplicitSessionAsync[TResult](Func`2 funcAsync, CancellationToken cancellationToken)
   at MongoTest.Program.Main(String[] args) in /Users/daigo/Desktop/mongo-test/MongoTest/Program.cs:line 43
   at MongoTest.Program.<Main>(String[] args)

Also I've checked the InvalidOperationException and taken the stack trace from the exception.

   at MongoDB.Bson.BsonDocument.Add(BsonElement element)
   at MongoDB.Bson.BsonDocument.Add(String name, BsonValue value)
   at MongoDB.Bson.Serialization.Serializers.BsonDocumentSerializer.DeserializeValue(BsonDeserializationContext context, BsonDeserializationArgs args)
   at MongoDB.Bson.Serialization.Serializers.BsonValueSerializerBase`1.Deserialize(BsonDeserializationContext context, BsonDeserializationArgs args)
   at MongoDB.Bson.Serialization.IBsonSerializerExtensions.Deserialize[TValue](IBsonSerializer`1 serializer, BsonDeserializationContext context)
   at MongoDB.Bson.Serialization.Serializers.BsonValueSerializer.DeserializeValue(BsonDeserializationContext context, BsonDeserializationArgs args)
   at MongoDB.Bson.Serialization.Serializers.BsonValueSerializerBase`1.Deserialize(BsonDeserializationContext context, BsonDeserializationArgs args)
   at MongoDB.Bson.Serialization.IBsonSerializerExtensions.Deserialize[TValue](IBsonSerializer`1 serializer, BsonDeserializationContext context)
   at MongoDB.Bson.Serialization.Serializers.BsonDocumentSerializer.DeserializeValue(BsonDeserializationContext context, BsonDeserializationArgs args)
   at MongoDB.Bson.Serialization.Serializers.BsonValueSerializerBase`1.Deserialize(BsonDeserializationContext context, BsonDeserializationArgs args)
   at MongoDB.Bson.Serialization.IBsonSerializerExtensions.Deserialize[TValue](IBsonSerializer`1 serializer, BsonDeserializationContext context)
   at MongoDB.Bson.IO.BsonWriter.WriteRawBsonDocument(IByteBuffer slice)
   at MongoDB.Bson.Serialization.Serializers.RawBsonDocumentSerializer.SerializeValue(BsonSerializationContext context, BsonSerializationArgs args, RawBsonDocument value)
   at MongoDB.Bson.Serialization.Serializers.BsonValueSerializerBase`1.Serialize(BsonSerializationContext context, BsonSerializationArgs args, TBsonValue value)
   at MongoDB.Bson.Serialization.Serializers.SerializerBase`1.MongoDB.Bson.Serialization.IBsonSerializer.Serialize(BsonSerializationContext context, BsonSerializationArgs args, Object value)
   at MongoDB.Bson.Serialization.IBsonSerializerExtensions.Serialize(IBsonSerializer serializer, BsonSerializationContext context, Object value)
   at MongoDB.Bson.Serialization.Serializers.BsonValueSerializerBase`1.Serialize(BsonSerializationContext context, BsonSerializationArgs args, TBsonValue value)
   at MongoDB.Bson.Serialization.Serializers.SerializerBase`1.MongoDB.Bson.Serialization.IBsonSerializer.Serialize(BsonSerializationContext context, BsonSerializationArgs args, Object value)
   at MongoDB.Bson.BsonExtensionMethods.ToJson(Object obj, Type nominalType, JsonWriterSettings writerSettings, IBsonSerializer serializer, Action`1 configurator, BsonSerializationArgs args)
   at MongoDB.Bson.BsonExtensionMethods.ToJson[TNominalType](TNominalType obj, JsonWriterSettings writerSettings, IBsonSerializer`1 serializer, Action`1 configurator, BsonSerializationArgs args)
   at Loquat.Shared.Extensions.MongoDbCollectionExtensions.<>c__DisplayClass0_1.<AddMongoDbContext>b__5(CommandStartedEvent e) in /Users/daigo/Desktop/mongo-test/Loquat.Shared/Extensions/MongoDbCollectionExtensions.cs:line 29
   at MongoDB.Driver.Core.Connections.CommandEventHelper.ProcessCommandRequestMessage(CommandRequestMessage originalMessage, Queue`1 messageQueue, ConnectionId connectionId, CommandMessageBinaryEncoder encoder, Stopwatch stopwatch)
   at MongoDB.Driver.Core.Connections.CommandEventHelper.ProcessRequestMessages(Queue`1 messageQueue, ConnectionId connectionId, Stream stream, MessageEncoderSettings encoderSettings, Stopwatch stopwatch)
   at MongoDB.Driver.Core.Connections.CommandEventHelper.BeforeSending(IEnumerable`1 messages, ConnectionId connectionId, IByteBuffer buffer, MessageEncoderSettings encoderSettings, Stopwatch stopwatch)
   at MongoDB.Driver.Core.Connections.BinaryConnection.SendMessagesHelper.SendingMessages(IByteBuffer buffer)
   at MongoDB.Driver.Core.Connections.BinaryConnection.SendMessagesAsync(IEnumerable`1 messages, MessageEncoderSettings messageEncoderSettings, CancellationToken cancellationToken)



 Comments   
Comment by Daigo Kobayashi [ 27/Jul/21 ]

Hello mikalai.mazurenka,

Thank you for the following-up.

Unfortunately, we can't rename or remove the UserType element. Because without UserType, we have no way to know the concrete type of the User class.

https://mongodb.github.io/mongo-csharp-driver/2.9/reference/bson/mapping/polymorphism/

We don't want to use ScalarDiscriminatorConvention or HierarchicalDiscriminatorConvention because if we rename classes, we also have to rename the document's _t property in the mongo database. That's why we are using a custom discriminator convention.

Comment by Mikalai Mazurenka (Inactive) [ 26/Jul/21 ]

Hi daigo@loquat.md,

The discriminator is added to the resulting document which is sent to the server, which means that beside already existing UserType property in User model, driver also includes another element named UserType which comes from the discriminator. The event listener (if registered) deserializes the resulting document before sending the message to the server, and .NET driver deserializer does not allow duplicate elements in BsonDocument.
So in this case I would suggest to either rename the discriminator element (UserCustomDiscriminatorConvention.ElementName) or remove the UserType element from the model.

Comment by Dmitry Lukyanov (Inactive) [ 21/Jun/21 ]

Hey daigo@loquat.md , thanks for your report, we will look at it and come back to you.

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