[CSHARP-3186] Regression upgrading driver from 2.7 to 2.11 Created: 14/Aug/20  Updated: 27/Oct/23  Resolved: 29/Sep/20

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

Type: Bug Priority: Major - P3
Reporter: Gian Maria Ricci Assignee: Dmitry Lukyanov (Inactive)
Resolution: Works as Designed Votes: 1
Labels: None
Remaining Estimate: Not Specified
Time Spent: Not Specified
Original Estimate: Not Specified

Issue Links:
Duplicate
duplicates CSHARP-3108 Deserialization throws No matching cr... Closed
Case:

 Description   

I have a project that runs perfectly fine with 2.7 drivers. After upgrading to 2.11 we start getting lots of error with the serializer. As an example these are two classes that we use in the project and that worked perfectly with 2.7 driver but fails to be deserialized with 2.11 driver.

 

public class ReportConfigurationLayoutSet : DomainEvent
 {
 [JsonConstructor]
 private ReportConfigurationLayoutSet()
 {
 }
public ReportConfigurationLayoutSet(
 ReportDocumentConfigurationValueObject newLayout,
 ReportDocumentConfigurationValueObject oldLayout
 )
 {
 NewLayout = newLayout ?? throw new ArgumentNullException(nameof(newLayout));
 OldLayout = oldLayout;
 }
public ReportDocumentConfigurationValueObject NewLayout { get; private set; }
public ReportDocumentConfigurationValueObject OldLayout { get; private set; }
 }
 
public class ReportDocumentConfigurationValueObject
 {
 /// <summary>
 /// we assume that we have a list of types of nodes, where a node can be a template to be aggregated to
 /// the docx or a repeated section of template.
 /// for the offer document the repeated sections can be machines, modules inside machines, etc...
 /// </summary>
 public List<ReportConfigurationNodeValueObject> Nodes { get; private set; }
public ReportConfigurationTypeId ReportTypeId { get; private set; }
[JsonConstructor]
 private ReportDocumentConfigurationValueObject()
 {
 }
public ReportDocumentConfigurationValueObject(
 List<ReportConfigurationNodeValueObject> nodes,
 ReportConfigurationTypeId reportTypeId)
 {
 Nodes = nodes ?? throw new ArgumentNullException(nameof(nodes));
 ReportTypeId = reportTypeId ?? throw new ArgumentNullException(nameof(reportTypeId));
 }

 

Upgrading to 2.11 driver I started got this error.

 

 

An error occurred while deserializing the Events property of class NStore.Domain.Changeset: 
An error occurred while deserializing the NewLayout property of class ProductsCatalog.Shared.Model.ReportConfiguration.Events.ReportConfigurationLayoutSet: 
An error occurred while deserializing the Nodes property of class ProductsCatalog.Shared.Model.ReportConfiguration.ReportDocumentConfigurationValueObject: 
No matching creator found..

 

I do not completely understand why it stopped working and I'm worrying because it worked perfectly with older driver.

 

Gian Maria.

 

 



 Comments   
Comment by Andy Meyvaert [ 03/Aug/23 ]

We are facing exactly the same problem using immutable domain classes. The proposed solution works but is not the way we like to configure our mongo (de)serialisation.

The alternative using classmap does not allow null as a default value:

BsonClassMap.RegisterClassMap<MyClass>(cm =>
{
    cm.GetMemberMap(x => x.MyProperty).SetDefaultValue(null);
}

This will throw an error on startup:

System.ArgumentNullException: 'Value cannot be null. (Parameter 'defaultValueCreator')'

Comment by Gian Maria Ricci [ 11/Jan/21 ]

Sorry for the late response, ok for the solution, I'll try with the attribute.

Comment by Dmitry Lukyanov (Inactive) [ 29/Sep/20 ]

Hello alkampfer, thanks for your report,

you're right that this behavior was changed in the latest patches. Now, we consider classes that have only private set;  as immutable, so this leads to a bit different code path which in turn gives the mentioned error. You're also right about the reason why it fails because the provided document doesn't have all the required fields. The designed path to solve it is to add [BsonDefaultValue(null)] to OtherString field:

            [BsonDefaultValue(null)]
            public string OtherString { get; private set; }

Comment by Gian Maria Ricci [ 14/Aug/20 ]

Hi,

I've made a repro here https://github.com/alkampfergit/MongoDbDriverExperiments/tree/master/Bugs/CSHARP-3186, the error is the following.

We have a base class ABSTRACT, two protected properties. Then we have a derived class, it was born with another private property, and has a constructor with three arguments. We had some objects serialized into database.

Then we add another property to derived class, that property is also private and set on constructor. We expect to be able to deserialize old serialized version of the class missing that second property, because we expect serializer to pass null on that parameter.

This is true if the property is public, but if the property is private, we have an error in MostArgumentsCreatorSelector.Match because we have four properties in creatorMap.ElementNames (constructor has 4 arguments) but dictionary passed as argument has only three parameters (because it comes from an old version of the object, where the other property is not present). No suitable constructor was match.

 

Expected behavior: please check if you can pass null to all extra argument on constructor, if you find a suitable constructor is ok, but if you do not find any EXACT constructor, it would be better to find a best match passing null on other arguments.

I think that this kind of behavior was introduced with last modification done to ImmutableTypeClassMapConvention class.

 

Comment by Gian Maria Ricci [ 14/Aug/20 ]

Actually I have an abstract class with some derived classes, that base abstract class is done like this.

 

    public abstract class ReportConfigurationNodeValueObject
    {
        /// <summary>
        /// the section name
        /// </summary>
        public string SectionName { get; protected set; }        /// <summary>
        /// questa proprietà indica la condizione su cui il template/sezione può essere vista o meno
        /// </summary>
        public string Condition { get; protected set; }        protected ReportConfigurationNodeValueObject(
            string sectionName,
            string condition
            )
        {
            if (string.IsNullOrEmpty(sectionName))
            {
                throw new ArgumentNullException(nameof(sectionName));
            }            SectionName = sectionName;
            Condition = condition;
        }

Now I have inherited classes like this one

 

    public class ReportConfigurationSectionValueObject : ReportConfigurationNodeValueObject
    {
        public SectionMarkValues SectionMark { get; set; }        public ReportConfigurationSectionValueObject(
            string sectionName,
            string condition,
            SectionMarkValues sectionMark
            ) : base(sectionName, condition)
        {
            SectionMark = sectionMark;
        }

And it does not work. If I made the two property of ReportConfigurationNodeValueObject public instead of protected everything went fine and the driver was able to deserialize everything.

I'm still not able to reproduce in a simple test, in that project I also have some custom serializer for some internal id etc, and I wonder where exactly is the problem.

If I have a repro I'll happy to attach to this issue.

thanks for the attention.

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