C# Driver
  1. C# Driver
  2. CSHARP-476

Support parameterized constructors on deserialization

    Details

    • Type: New Feature New Feature
    • Status: Closed Closed
    • Priority: Minor - P4 Minor - P4
    • Resolution: Fixed
    • Affects Version/s: 1.4.2
    • Fix Version/s: 1.8
    • Component/s: None
    • # Replies:
      6
    • Last comment by Customer:
      false

      Description

      I would be happy to see support for parameterized constructors during deserialization process. The target constuctor may be marked with special attribute or selected automatically. Example of this feature is in JSON.NET.
      This feature is very important when use Code Contracts. Current deserialization implementation with default parameter-less constructor fully incompatible with code contracts "invariants" so I forced to manually implement deseriaization for each type.

        Issue Links

          Activity

          Hide
          Craig Wilson
          added a comment - - edited

          For the next release, we have added support for setting a constructor using a delegate.

          .SetCreator(() => new TestClass(1, "blag");
          

          However, I assume that you are asking for MongoDB fields to get deserialized and set through the constructor. Is that correct?

          Show
          Craig Wilson
          added a comment - - edited For the next release, we have added support for setting a constructor using a delegate. .SetCreator(() => new TestClass(1, "blag"); However, I assume that you are asking for MongoDB fields to get deserialized and set through the constructor. Is that correct?
          Hide
          Pantyushin Roman
          added a comment -

          Yes. The example:

          Class1.cs
          public class Class1
          {
              private readonly string name;
              public Class1(string name)
              {
                  Contract.Requires(name != null);
                  this.name = name;
              }
              public string Name
              {
                  get
                  {
                      Contract.Ensures(Contract.Result<string>() != null);
                      return this.name;
                  }
              }
              [ContractInvariantMethod]
              private void ObjectInvariant()
              {
                  Contract.Invariant(this.name != null);
              }
          }
          

          I want constructor "Class1(string)" to be called during deserialization, with provided deserialized "name" value. This allows me to have object always in correct state.

          Show
          Pantyushin Roman
          added a comment - Yes. The example: Class1.cs public class Class1 { private readonly string name; public Class1(string name) { Contract.Requires(name != null); this.name = name; } public string Name { get { Contract.Ensures(Contract.Result<string>() != null); return this.name; } } [ContractInvariantMethod] private void ObjectInvariant() { Contract.Invariant(this.name != null); } } I want constructor "Class1(string)" to be called during deserialization, with provided deserialized "name" value. This allows me to have object always in correct state.
          Hide
          Craig Wilson
          added a comment -

          Ok. Do you have any proposals you'd like us to consider regarding how to do this? We have some ideas internally but would like to hear yours as well. The issue we'll need to figure out is how to match up the member for the class with the parameter from the constructor. Remember that there are conventions that will need to be created/altered to ensure consistency between the two.

          Show
          Craig Wilson
          added a comment - Ok. Do you have any proposals you'd like us to consider regarding how to do this? We have some ideas internally but would like to hear yours as well. The issue we'll need to figure out is how to match up the member for the class with the parameter from the constructor. Remember that there are conventions that will need to be created/altered to ensure consistency between the two.
          Hide
          Pantyushin Roman
          added a comment -

          I think in most simple case constructor for deserialization should be determined "statically" (i.e. it should not depend on existing document fields). It will be something like this:
          (1) Class map should have method to directly set ctor. Also there should be the new attribute to do the same thing. Something like:

          BsonClassMap.SetConstructor(ConstructorInfo ctor);
          class BsonDeserializationConstructorAttribute : Attribute { }
          

          (2) There should be new convention for cases when ctor does not specified directly (as described in (1)). This convention should activate process of automatic selection of ctor (to not break existing code). Something like:

          public interface IConstructorHandlingConvention {
              bool TryUseParameterizedConstructor {get;} }
          

          When it's activated, ctor automatically selected using following steps:
          a) if ctor only one - use it (even if it's parameter-less);
          b) use ctor with most number of arguments (if it's only one);
          c) throw exception.
          (3) There should be new convention to map ctor parameters to the properties. Something like:

          public interface IXXXConvention {
              bool Match(string ctorParameterName, string propertyName); }
          

          and default implementation

          return ctorParameterName.Equals(propertyName, StringComparision.OrdinalIgnoreCase);
          

          There may be new method in class map (and new attribute) to directly map ctor parameter to property, for special cases:

          BsonClassMap.MapCtorParameter(string ctorParameterName, string propertyName)
          class BsonCtorParameterMapAttribute : Attribute {
              public string PropertyName {get;} }
          

          Before (de)serialization we go through all public properties without setter and filter the ones that matches ctor parameter name. This properties concatenated with result of IMemberFinderConvention.FindMembers(Type) and then used on (de)serialization process.

          This logic can be avoided by providing new implemenation of IMemberFinderConvention that returns public properties without setter, but in this case we will be forced to manually unmap all calculated properties like this:

          public string FullName {
              get { return this.prefix + this.name; } }
          

          Then we map all ctor parameters to properties. Exception should be thrown if not all ctor parameters has mapped property. Of course all "Bson" attributes applied to properties are used on deserialization of mapped ctor parameters.

          Show
          Pantyushin Roman
          added a comment - I think in most simple case constructor for deserialization should be determined "statically" (i.e. it should not depend on existing document fields). It will be something like this: (1) Class map should have method to directly set ctor. Also there should be the new attribute to do the same thing. Something like: BsonClassMap.SetConstructor(ConstructorInfo ctor); class BsonDeserializationConstructorAttribute : Attribute { } (2) There should be new convention for cases when ctor does not specified directly (as described in (1)). This convention should activate process of automatic selection of ctor (to not break existing code). Something like: public interface IConstructorHandlingConvention { bool TryUseParameterizedConstructor {get;} } When it's activated, ctor automatically selected using following steps: a) if ctor only one - use it (even if it's parameter-less); b) use ctor with most number of arguments (if it's only one); c) throw exception. (3) There should be new convention to map ctor parameters to the properties. Something like: public interface IXXXConvention { bool Match(string ctorParameterName, string propertyName); } and default implementation return ctorParameterName.Equals(propertyName, StringComparision.OrdinalIgnoreCase); There may be new method in class map (and new attribute) to directly map ctor parameter to property, for special cases: BsonClassMap.MapCtorParameter(string ctorParameterName, string propertyName) class BsonCtorParameterMapAttribute : Attribute { public string PropertyName {get;} } Before (de)serialization we go through all public properties without setter and filter the ones that matches ctor parameter name. This properties concatenated with result of IMemberFinderConvention.FindMembers(Type) and then used on (de)serialization process. This logic can be avoided by providing new implemenation of IMemberFinderConvention that returns public properties without setter, but in this case we will be forced to manually unmap all calculated properties like this: public string FullName { get { return this.prefix + this.name; } } Then we map all ctor parameters to properties. Exception should be thrown if not all ctor parameters has mapped property. Of course all "Bson" attributes applied to properties are used on deserialization of mapped ctor parameters.
          Hide
          Pantyushin Roman
          added a comment - - edited

          The another, more flexible way for ctor parameters mapping:

          BsonClassMap.MapCtorParameter(string ctorParameterName, MemberInfo member)
          class CtorParameterMapAttribute : Attribute {
              public string MemberName {get;} }
          public interface ICtorParameterMapConvention {
              MemberInfo FindMatchingMember(ParameterInfo ctorParameter, Type objectType); }
          

          With default implementation of ICtorParameterMapConvention that checking all (even readonly) fields and properties of objectType and execute name comparison (ignore casing). Then all matching members unites with result of IMemberFinderConvention.FindMembers(Type) and used for (de)serialization.

          Show
          Pantyushin Roman
          added a comment - - edited The another, more flexible way for ctor parameters mapping: BsonClassMap.MapCtorParameter(string ctorParameterName, MemberInfo member) class CtorParameterMapAttribute : Attribute { public string MemberName {get;} } public interface ICtorParameterMapConvention { MemberInfo FindMatchingMember(ParameterInfo ctorParameter, Type objectType); } With default implementation of ICtorParameterMapConvention that checking all (even readonly) fields and properties of objectType and execute name comparison (ignore casing). Then all matching members unites with result of IMemberFinderConvention.FindMembers(Type) and used for (de)serialization.
          Hide
          auto
          added a comment -

          Author:

          {u'date': u'2013-01-16T02:26:11Z', u'email': u'robert@10gen.com', u'name': u'rstam'}

          Message: CSHARP-476: Added support for calling constructors, factory methods or arbitrary code to instantiate an object during deserialization.
          Branch: master
          https://github.com/mongodb/mongo-csharp-driver/commit/e0f958edea7be79a4831b3cf10e990b3e3149d04

          Show
          auto
          added a comment - Author: {u'date': u'2013-01-16T02:26:11Z', u'email': u'robert@10gen.com', u'name': u'rstam'} Message: CSHARP-476 : Added support for calling constructors, factory methods or arbitrary code to instantiate an object during deserialization. Branch: master https://github.com/mongodb/mongo-csharp-driver/commit/e0f958edea7be79a4831b3cf10e990b3e3149d04

            People

            • Votes:
              1 Vote for this issue
              Watchers:
              0 Start watching this issue

              Dates

              • Created:
                Updated:
                Resolved:
                Days since reply:
                1 year, 13 weeks, 5 days ago
                Date of 1st Reply: