[CSHARP-2995] BsonClassMap.LookupClassMap supports private constructors inconsistently (regression) Created: 04/Mar/20  Updated: 24/Mar/22  Resolved: 27/Apr/20

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

Type: Bug Priority: Major - P3
Reporter: Dmitry Lukyanov (Inactive) Assignee: Dmitry Lukyanov (Inactive)
Resolution: Done Votes: 1
Labels: None
Remaining Estimate: Not Specified
Time Spent: Not Specified
Original Estimate: Not Specified

Attachments: File event-44e34c61cd3708d7e3b4e30000000000.json    
Issue Links:
Related
is related to CSHARP-4112 Auto Mapping of private properties st... Backlog

 Description   

The user's email:

Ello 
After upgrading the c# mongo driver from 2.10.1 to 2.10.2, I noticed that classes that deserialised without issues suddenly stopped working, resulting in exceptions about incorrect creator maps. Has something changed with this patch?
The class I'm deserialising has a private, parameterless constructor, and a public constructor with 4 arguments. I get the following exception since the patch:
Creator map for class Nallian.CargoFlow.UseCase.Models.Mawb.PiecesDetail has 4 arguments, but none are configured.
Any ideas what has gone wrong? I don't see documented changes regarding the mapping, might the change be unintended? Moving back to 2.10.1 has resolved the issue again, leading me to believe there's something with the most recent patch.
Kind regards, Anthony

 

 



 Comments   
Comment by Anup Marwadi [ 25/Mar/20 ]

Hello Dmitry,

Sure, I can look into this today and get back to you. For now, I am definitely able to resolve my issues by adding an empty constructor as I mentioned. I'll try and go back to the versions to see exactly where this change occurred. Thank you for your diligent follow-up. I will do the same today and get back to you!

Comment by Dmitry Lukyanov (Inactive) [ 25/Mar/20 ]

Hello anup@hypertrends.com,
it's still not clear what is the root case of your current issue. Can you please confirm that the mentioned class description was successfully processed in the previous driver versions? And if so, which driver version it was?

Comment by Anup Marwadi [ 21/Mar/20 ]

Just an FYI, I was able to solve this problem by adding the BsonConstructor attribute to the empty constructors for both the SectionAssignment and the RowAssignment.

Usually, there was no need to do this, but it appears that something has changed recently requiring the need for this constructor. Can you please let me know if this is the norm going forward? I didn't think this was needed at all.

Comment by Anup Marwadi [ 21/Mar/20 ]

Hello, I just ran the tests that you were requesting and I can't even get the ToJson() to work. It immediately errors out at that line saying: "Creator map for class HtComm.Events.Domain.SeatedEvents.SectionAssignment has 2 arguments, but none are configured."

I do have Serilog logging on and here's what my raw json for the log looks like (if it is any help)event-44e34c61cd3708d7e3b4e30000000000.json

Comment by Anup Marwadi [ 21/Mar/20 ]

Hello, you are right, it's not working with 2.10.1 either. Now that is even more interesting. In any case, gimme about 10mins and I will also post the JSONs like you are requesting.

Comment by Dmitry Lukyanov (Inactive) [ 20/Mar/20 ]

Hello anup@hypertrends.com,
can you please confirm that the above code worked in the driver version 2.10.1? The only change regarding this deserialization logic in 2.10.2 was related to immutable classes. It looks like no classes from the above list are immutable and should not be affected. As far as I can see this error should also be triggered in the driver 2.10.1.

Can you also please check this code(if the below code won't work in 2.10.2, please try it in 2.10.1 driver) and post results from reservedHoldJson and sectionsJson:

            var reservedHoldJson = reservedHold.ToJson();
 
            var sections = reservedHold.Sections;
            var sectionsJson = sections.ToJson();

where reservedHold is an argument in this method:

            public async Task<bool> ReplaceHold(ReservedHold reservedHold, CancellationToken cancellationToken = default(CancellationToken))

Comment by Anup Marwadi [ 20/Mar/20 ]

Hello,

Absolutely, I have a few different places where this is happening. Here is one example:

  1. We have a Reserved Hold Class. It keeps an array of SectionAssignments.
  2. The ReservedHold class acts as an Aggregate and will allow saving of Section Assignments into it.

// code placeholder
// THIS IS HOW A CLASS LOOKS LIKE - The important one to look at is the 
public class ReservedHold
    {
        [BsonConstructor]
        public ReservedHold(string id, string tenantId, string eventId, string title, 
            string description, HtmlColor color, ReservedHoldCriteria holdCriteria)
        {
            Id = id;
            TenantId = tenantId;
            EventId = eventId;
            Title = title;
            Description = description;
            HoldCriteria = holdCriteria;
            _Sections = new List<SectionAssignment>();
            DateCreatedUtc = DateTime.UtcNow;
            DateModifiedUtc = DateTime.UtcNow;
            Archivable = new Archivable();
            Color = color;
        }
 
 
        public string Description { get; protected set; }
 
 
        [BsonRequired] public HtmlColor Color {get; protected set;}
        
        public DateTime DateCreatedUtc { get; protected set; }
 
 
        public DateTime DateModifiedUtc { get; protected set; }
 
 
        public ReservedHoldCriteria HoldCriteria { get; protected set; }
 
 
        [BsonId]
        [BsonRepresentation(BsonType.ObjectId)]
        public string Id { get; protected set; }
 
 
        [BsonRequired] public string TenantId { get; protected set; }
 
 
        [BsonRequired] public string EventId { get; protected set; }
 
 
        [BsonIgnore] public IReadOnlyList<SectionAssignment> Sections => _Sections;
 
 
        [BsonRequired] public string Title { get; protected set; }
 
 
        public Archivable Archivable { get; protected set; }
 
 
        [BsonElement("Sections")] private List<SectionAssignment> _Sections { get; set; }
}

When this is first saved in MongoDB, it is saved like so:

// code placeholder
{ 
    "_id" : ObjectId("5e72db38effda01f643d41d1"), 
    "Description" : "", 
    "Color" : {
        "Value" : "#CDD973"
    }, 
    "DateCreatedUtc" : ISODate("2020-03-19T02:38:48.922+0000"), 
    "DateModifiedUtc" : ISODate("2020-03-19T02:38:48.922+0000"), 
    "HoldCriteria" : {
        "UnlockCode" : "U2ITIS", 
        "DateValidUntilUtc" : ISODate("2020-07-31T20:30:42.315+0000")
    }, 
    "TenantId" : "5db7583c34183c0c384f35ed", 
    "EventId" : "5e72daabeffda01f643d41c9", 
    "Title" : "VIP PIT", 
    "Archivable" : {
        "IsArchived" : false, 
        "DateArchivedUtc" : null
    }, 
    "Sections" : [    ]
}

Notice that Sections is an empty array. This is how the Section Assignment class looks like:

// Section Assignment class
public class SectionAssignment
    {
        [BsonRequired] public string Key { get; protected set; }
 
 
        [BsonElement("Rows")] private List<RowAssignment> RowsPrivate { get; set; }
 
 
        [BsonIgnore] public IReadOnlyList<RowAssignment> Rows => RowsPrivate;
 
 
        public SectionAssignment()
        {
 
 
        }
 
 
        [BsonConstructor]
        public SectionAssignment(string key, List<RowAssignment> rows)
        {
            RowsPrivate = rows;
            Key = key;
        }
 
 
    }

Initially, I didn't have the empty constructor, but added it to see if that made a difference. Now when I try to add the SectionAssignment and call the Replace Document method, I get the following error: 

// ERROR LOG
MongoDB.Bson.BsonSerializationException: Creator map for class HtComm.Events.Domain.SeatedEvents.SectionAssignment has 2 arguments, but none are configured.\r\n   at MongoDB.Bson.Serialization.BsonCreatorMap.Freeze()\r\n   at MongoDB.Bson.Serialization.BsonClassMap.Freeze()\r\n   at MongoDB.Bson.Serialization.BsonClassMap.LookupClassMap(Type classType)\r\n   at MongoDB.Bson.Serialization.BsonClassMapSerializationProvider.GetSerializer(Type type, IBsonSerializerRegistry serializerRegistry)\r\n   at MongoDB.Bson.Serialization.BsonSerializerRegistry.CreateSerializer(Type type)\r\n   at System.Collections.Concurrent.ConcurrentDictionary`2.GetOrAdd(TKey key, Func`2 valueFactory)\r\n   at MongoDB.Bson.Serialization.BsonSerializerRegistry.GetSerializer[T]()\r\n   at MongoDB.Bson.Serialization.Serializers.EnumerableSerializerBase`2.<>c__DisplayClass4_0.<.ctor>b__0()\r\n   at System.Lazy`1.ViaFactory(LazyThreadSafetyMode mode)\r\n   at System.Lazy`1.ExecutionAndPublication(LazyHelper executionAndPublication, Boolean useDefaultConstructor)\r\n   at System.Lazy`1.CreateValue()\r\n   at MongoDB.Bson.Serialization.Serializers.EnumerableSerializerBase`2.Serialize(BsonSerializationContext context, BsonSerializationArgs args, TValue value)\r\n   at MongoDB.Bson.Serialization.Serializers.SerializerBase`1.MongoDB.Bson.Serialization.IBsonSerializer.Serialize(BsonSerializationContext context, BsonSerializationArgs args, Object value)\r\n   at MongoDB.Bson.Serialization.IBsonSerializerExtensions.Serialize(IBsonSerializer serializer, BsonSerializationContext context, Object value)\r\n   at MongoDB.Bson.Serialization.BsonClassMapSerializer`1.SerializeNormalMember(BsonSerializationContext context, Object obj, BsonMemberMap memberMap)\r\n   at MongoDB.Bson.Serialization.BsonClassMapSerializer`1.SerializeClass(BsonSerializationContext context, BsonSerializationArgs args, TClass document)\r\n   at MongoDB.Bson.Serialization.BsonClassMapSerializer`1.Serialize(BsonSerializationContext context, BsonSerializationArgs args, TClass value)\r\n   at MongoDB.Bson.Serialization.Serializers.SerializerBase`1.MongoDB.Bson.Serialization.IBsonSerializer.Serialize(BsonSerializationContext context, BsonSerializationArgs args, Object value)\r\n   at MongoDB.Bson.Serialization.IBsonSerializerExtensions.Serialize(IBsonSerializer serializer, BsonSerializationContext context, Object value)\r\n   at MongoDB.Bson.Serialization.Serializers.BsonDocumentWrapperSerializer.SerializeValue(BsonSerializationContext context, BsonSerializationArgs args, BsonDocumentWrapper value)\r\n   at MongoDB.Bson.Serialization.Serializers.BsonValueSerializerBase`1.Serialize(BsonSerializationContext context, BsonSerializationArgs args, TBsonValue value)\r\n   at MongoDB.Bson.Serialization.Serializers.SerializerBase`1.MongoDB.Bson.Serialization.IBsonSerializer.Serialize(BsonSerializationContext context, BsonSerializationArgs args, Object value)\r\n   at MongoDB.Bson.Serialization.IBsonSerializerExtensions.Serialize(IBsonSerializer serializer, BsonSerializationContext context, Object value)\r\n   at MongoDB.Bson.Serialization.Serializers.BsonValueSerializerBase`1.Serialize(BsonSerializationContext context, BsonSerializationArgs args, TBsonValue value)\r\n   at MongoDB.Bson.Serialization.IBsonSerializerExtensions.Serialize[TValue](IBsonSerializer`1 serializer, BsonSerializationContext context, TValue value)\r\n   at MongoDB.Driver.Core.Operations.RetryableUpdateCommandOperation.UpdateRequestSerializer.SerializeUpdate(BsonSerializationContext context, BsonSerializationArgs args, UpdateRequest request)\r\n   at MongoDB.Driver.Core.Operations.RetryableUpdateCommandOperation.UpdateRequestSerializer.SerializeValue(BsonSerializationContext context, BsonSerializationArgs args, UpdateRequest value)\r\n   at MongoDB.Bson.Serialization.Serializers.SealedClassSerializerBase`1.Serialize(BsonSerializationContext context, BsonSerializationArgs args, TValue value)\r\n   at MongoDB.Bson.Serialization.Serializers.SerializerBase`1.MongoDB.Bson.Serialization.IBsonSerializer.Serialize(BsonSerializationContext context, BsonSerializationArgs args, Object value)\r\n   at MongoDB.Driver.Core.WireProtocol.Messages.Encoders.BinaryEncoders.CommandMessageBinaryEncoder.WriteType1Section(BsonBinaryWriter writer, Type1CommandMessageSection section, Int64 messageStartPosition)\r\n   at MongoDB.Driver.Core.WireProtocol.Messages.Encoders.BinaryEncoders.CommandMessageBinaryEncoder.WriteSections(BsonBinaryWriter writer, IEnumerable`1 sections, Int64 messageStartPosition)\r\n   at MongoDB.Driver.Core.WireProtocol.Messages.Encoders.BinaryEncoders.CommandMessageBinaryEncoder.WriteMessage(CommandMessage message)\r\n   at MongoDB.Driver.Core.Connections.BinaryConnection.SendMessagesHelper.EncodeMessages(CancellationToken cancellationToken, List`1& sentMessages)\r\n   at MongoDB.Driver.Core.Connections.BinaryConnection.SendMessagesAsync(IEnumerable`1 messages, MessageEncoderSettings messageEncoderSettings, CancellationToken cancellationToken)\r\n   at MongoDB.Driver.Core.WireProtocol.CommandUsingCommandMessageWireProtocol`1.ExecuteAsync(IConnection connection, CancellationToken cancellationToken)\r\n   at MongoDB.Driver.Core.Servers.Server.ServerChannel.ExecuteProtocolAsync[TResult](IWireProtocol`1 protocol, ICoreSession session, CancellationToken cancellationToken)\r\n   at MongoDB.Driver.Core.Operations.RetryableWriteOperationExecutor.ExecuteAsync[TResult](IRetryableWriteOperation`1 operation, RetryableWriteContext context, CancellationToken cancellationToken)\r\n   at MongoDB.Driver.Core.Operations.BulkUnmixedWriteOperationBase`1.ExecuteBatchAsync(RetryableWriteContext context, Batch batch, CancellationToken cancellationToken)\r\n   at MongoDB.Driver.Core.Operations.BulkUnmixedWriteOperationBase`1.ExecuteBatchesAsync(RetryableWriteContext context, CancellationToken cancellationToken)\r\n   at MongoDB.Driver.Core.Operations.BulkMixedWriteOperation.ExecuteBatchAsync(RetryableWriteContext context, Batch batch, CancellationToken cancellationToken)\r\n   at MongoDB.Driver.Core.Operations.BulkMixedWriteOperation.ExecuteAsync(IWriteBinding binding, CancellationToken cancellationToken)\r\n   at MongoDB.Driver.OperationExecutor.ExecuteWriteOperationAsync[TResult](IWriteBinding binding, IWriteOperation`1 operation, CancellationToken cancellationToken)\r\n   at MongoDB.Driver.MongoCollectionImpl`1.ExecuteWriteOperationAsync[TResult](IClientSessionHandle session, IWriteOperation`1 operation, CancellationToken cancellationToken)\r\n   at MongoDB.Driver.MongoCollectionImpl`1.BulkWriteAsync(IClientSessionHandle session, IEnumerable`1 requests, BulkWriteOptions options, CancellationToken cancellationToken)\r\n   at MongoDB.Driver.MongoCollectionImpl`1.UsingImplicitSessionAsync[TResult](Func`2 funcAsync, CancellationToken cancellationToken)\r\n   at MongoDB.Driver.MongoCollectionBase`1.ReplaceOneAsync(FilterDefinition`1 filter, TDocument replacement, ReplaceOptions options, Func`3 bulkWriteAsync)\r\n   at HtComm.Events.Services.SeatedEvents.ReservedHolds.ReservedHoldRepository.ReplaceHold(ReservedHold reservedHold, CancellationToken cancellationToken) in C:\\HyperTrends\\Source\\eventsbackend\\EventsEngine\\HtComm.Events.Service\\SeatedEvents\\ReservedHolds\\ReservedHoldRepository.cs:line 37\r\n   at HtComm.Events.Services.SeatedEvents.AddReservedHoldSectionRequestHandler.Handle(AddReservedHoldSectionRequest request, CancellationToken cancellationToken) in C:\\HyperTrends\\Source\\eventsbackend\\EventsEngine\\HtComm.Events.Service\\SeatedEvents\\AddReservedHoldSectionUseCase.cs:line 108\r\n",

This is happening pretty much all over the application now. None of these items were issues before.

The line 108, where the error is has the following code:

// SNIPPET CODE
  if (reservedHold.AddSectionAssignment(sectionAssignment))
                {
//LINE 108 -->
                    var outcome = await _reservedHoldRepository.ReplaceHold(reservedHold, cancellationToken);
                    if (outcome)

The Repository's ReplaceHold method looks like so:

// Repository
  public async Task<bool> ReplaceHold(ReservedHold reservedHold, 
            CancellationToken cancellationToken = default(CancellationToken))
        {
//Line 37
            var outcome = await _context.ReservedHolds.WithWriteConcern(WriteConcern.Acknowledged)
                .ReplaceOneAsync(x => x.Id == reservedHold.Id, reservedHold, new ReplaceOptions(), cancellationToken);
            return outcome.IsAcknowledged && outcome.ModifiedCount > 0;
        }

Hopefully this helps. Our code is pretty standard. I am unable to figure out why it won't let me do this operation for this scenario when I use the same approach in many other places and it works. The code has not changed in over 6 months (just an FYI).

Thank you and looking forward to collaborating with you to get this to the finish line.

 

Anup Marwadi

 

Comment by Dmitry Lukyanov (Inactive) [ 19/Mar/20 ]

Hello anup@hypertrends.com,
Thanks for reporting of your problem, can you please provide some details? In particular, it will be helpful, if you provide the full class description that triggers the error during deserialization.

Comment by Anup Marwadi [ 19/Mar/20 ]

Hello, I have started noticing all sorts of errors with this exact same issue after upgrading. Can we please confirm what has changed here? I'm moving back to 2.10.1 as well. Thank you!

Comment by Dmitry Lukyanov (Inactive) [ 16/Mar/20 ]

asked the user about additional details via email

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