Uploaded image for project: 'C# Driver'
  1. C# Driver
  2. CSHARP-3153

Add support for serialization of proxy models

    XMLWordPrintable

    Details

    • Type: New Feature
    • Status: Open
    • Priority: Major - P3
    • Resolution: Unresolved
    • Affects Version/s: 3.0.0
    • Fix Version/s: None
    • Component/s: Serialization
    • Labels:
      None
    • Backwards Compatibility:
      Fully Compatible

      Description

      I'm working on a project where we use proxy models pattern for intercept actions to models. It's a common practice for automate some operations, for example related to security read/write access, or for implement lazy loading of information in a transparent way. For the context, we are using Castle.Core library.

      Currently it's evident that serialization system of drivers is not designed for handle proxy models, and I'm going to explain why.

      Aside of relative simple to solve problems, there are others that needs hard workarounds.

      On simple problems, we can report the need of explicit a

      classMap.SetCreator(() => myProxyGenerator.CreateInstance(modelType));

      at every classMap declaration. This is feasible, but a global class map configuration with something like

      BsonClassMap.UseDefaultCreator(modelType => myProxyGenerator.CreateInstance(modelType));

      could be better.
       

      A much more difficult problem to solve is serialization. Let say that we have a classMap for User type. The relative dynamic type will be UserProxy (keeping name style of Castle.Core library).

      When we try to serialize an User model, the BsonClassMapSerializer performs this action:

      var actualType = value.GetType();
      if (actualType == typeof(TClass))
      {
          SerializeClass(context, args, value);
      }
      else
      {
          var serializer = BsonSerializer.LookupSerializer(actualType);
          serializer.Serialize(context, args, value);
      }
      

      the declaration of typeof(TClass) is directly compared to actual type of value, where instead a "procy model tolerant" check would be preferable. So it could be changed with something like this:

      var actualType = value.GetType();
      if (proxyModelFacade.PurgeProxyType(actualType) == typeof(TClass))
      {
          SerializeClass(context, args, value);
      }
      else
      {
          var serializer = BsonSerializer.LookupSerializer(actualType);
          serializer.Serialize(context, args, value);
      }
      

      where proxyModelFacade.PurgeProxyType(typeof(UserProxy)) will return for example typeof(User).

      This is a problem because this system will start a lookup of a new serializer, and where it will not be found, this will lead to find a BsonClassMap for proxy type, where an Automap() on a new classmap will lead to configuration errors (tests done still with Castle.Core library). So we have to register at least an adeguate class map for proxy type ahead, but issues are not ended.

      Even registering correctly a class map, we still have issue that creating a document on a collection with base type User, the document will be crated with discriminator "_t": "UserProxy". This because a proxy model is actually a derived type to the original one. So we should register a DiscriminatorConvention whit the scope to ignore any proxy type, and handle them has their base types.

      But issues are not ended, because we cannot register discriminator conventions for typeof(object), because on BsonSerializer.LookupDiscriminatorConvention(Type type) registration for typeof(object) are ignored! (WHY??)

      // inherit the discriminator convention from the closest parent (that isn't object) that has one
      // otherwise default to the standard hierarchical convention
      Type parentType = type.GetTypeInfo().BaseType;
      while (convention == null)
      {
          if (parentType == typeof(object))
          {
              convention = StandardDiscriminatorConvention.Hierarchical;
              break;
          }
          if (__discriminatorConventions.TryGetValue(parentType, out convention))
          {
              break;
          }
          parentType = parentType.GetTypeInfo().BaseType;
      }
      

      So we have to choose a common base type, different than object, and use it for register the convention.

      It's clear that this system has not been designed with use of proxies in mind. Please consider it for next major, it would be a great feature improvement.

        Attachments

          Activity

            People

            Assignee:
            Unassigned Unassigned
            Reporter:
            mirko@etherna.io Mirko Da Corte
            Votes:
            0 Vote for this issue
            Watchers:
            2 Start watching this issue

              Dates

              Created:
              Updated: