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

Expression doesn't execute on composed class properly

    • Type: Icon: Bug Bug
    • Resolution: Fixed
    • Priority: Icon: Unknown Unknown
    • 2.18.0
    • Affects Version/s: None
    • Component/s: LINQ3

      The problem we are experiencing is that we have C# classes such as the following (PascalCase):

      Unable to find source-code formatter for language: csharp. Available languages are: actionscript, ada, applescript, bash, c, c#, c++, cpp, css, erlang, go, groovy, haskell, html, java, javascript, js, json, lua, none, nyan, objc, perl, php, python, r, rainbow, ruby, scala, sh, sql, swift, visualbasic, xml, yaml
      public class InstanceData {
          public string? InstanceName { get; set; }
      }
      
      public class Model {
          public int Version { get; set; }
          public InstanceData? Data { get; set; }
      }
      
      Unable to find source-code formatter for language: csharp. Available languages are: actionscript, ada, applescript, bash, c, c#, c++, cpp, css, erlang, go, groovy, haskell, html, java, javascript, js, json, lua, none, nyan, objc, perl, php, python, r, rainbow, ruby, scala, sh, sql, swift, visualbasic, xml, yaml
      // Startup.cs
      public void ConfigureServices(IServiceCollection services)
      {
          \\ ...
      
          ConventionRegistry.Register("MyConvention",
              new ConventionPack { new CamelCaseElementNameConvention() },
              t => \* Some Condition *\);
      
          \\ ...
      }
      

      and data in MongoDb as such (camelCase):

      Unable to find source-code formatter for language: bson. Available languages are: actionscript, ada, applescript, bash, c, c#, c++, cpp, css, erlang, go, groovy, haskell, html, java, javascript, js, json, lua, none, nyan, objc, perl, php, python, r, rainbow, ruby, scala, sh, sql, swift, visualbasic, xml, yaml
      _id: ObjectId("...")
      version: 1
      data: Object
          instanceName: "TestInstance123"
      

      The problem is, when Model gets mapped, it creates a member map for Version just fine, but for Data, an expression is created. This expression, when further evaluated within the Freeze() method, creates a trie where all children of the root node are lowercase. It then tries to look up "InstanceName" from a trie which has children [ "i" ], and throws an exception since the capitalization differs. It looks like it's creating a NEW ClassMap/Trie for Model (where both ElementName and MemberName are pascal case ), but using the cached map for InstanceData. The cached one has all MemberName's in pascal case, but all ElementName's in camel case. It looks like we have a total of 3 Trie's when really we need 4? Or perhaps only 2?

      Example Exception:

      An error occurred while deserializing the Data property of class MyNamespace.Model: 
      Element 'InstanceName' does not match any field or property of class MyNamespace.Data.
      

      This appears to be a bug with the cached serializer (or classMap?) for the InstanceData class.

      Example code:

      Unable to find source-code formatter for language: csharp. Available languages are: actionscript, ada, applescript, bash, c, c#, c++, cpp, css, erlang, go, groovy, haskell, html, java, javascript, js, json, lua, none, nyan, objc, perl, php, python, r, rainbow, ruby, scala, sh, sql, swift, visualbasic, xml, yaml
      public class Program
      {
          public static void Main(string[] args)
          {
              ConventionRegistry.Register("MyConvention", new ConventionPack { new CamelCaseElementNameConvention() }, t => true);
      
              var client = new MongoClient(/* Connection String */);
              var database = client.GetDatabase(/* Database */);
      
              var documents = database.GetCollection<Model>(/* Collection */).AsQueryable().AsQueryable()
                  // .Select(m => m) // This one works just fine.
                  // .Select(m => new Model { Version = m.Version, Data = m.Data }) // This one works just fine as well.
                  .Select(m => new Model { Version = m.Version, Data = m.Data != null ? new InstanceData { InstanceName = m.Data.InstanceName } : default }) // This one does not work. Can't find "InstanceName" in class "Data".
                  .Take(1000)
                  .ToArray();
          }
      }
      

      A few debug values/expressions from above query:

      MongoQueryableImpl.GetEnumerator:
      {aggregate([]).Select(m => new Model() {Version = m.Version, Data = IIF((m.Data != null), new InstanceData() {InstanceName = m.Data.InstanceName}, null)}).Take(1000)}
      
      PipelineBinderBase.BindMethodCall:
      {[MyDb.MyCollection].Select(new Model() {Version = {document}{version}, Data = IIF(({document}{Data} != null), new InstanceData() {InstanceName = {document}{Data}{instanceName}}, null)}).Take(1000)}
      
      MongoQueryProviderImpl.Prepare:
      {[MyDb.MyCollection].Select(new Model() {Version = {document}{version}, Data = IIF(({document}{Data} != null), new InstanceData() {InstanceName = {document}{Data}{instanceName}}, null)}).Take(1000)}
      
      MongoQueryProviderImpl.Execute:
      {() => Convert(value(MongoDB.Driver.Linq.MongoQueryProviderImpl`1[MyNamespace.Model]).ExecuteModel(aggregate([{ "$project" : { "Version" : "$version", "Data" : { "$cond" : [{ "$ne" : ["$Data", null] }, { "InstanceName" : "$Data.instanceName" }, null] }, "_id" : 0 } }, { "$limit" : 1000 }])), IAsyncCursor`1).ToEnumerable(value(System.Threading.CancellationToken))}
      
      MongoQueryProviderImpl.ExecuteModel:
      {aggregate([{ "$project" : { "Version" : "$version", "Data" : { "$cond" : [{ "$ne" : ["$Data", null] }, { "InstanceName" : "$Data.instanceName" }, null] }, "_id" : 0 } }, { "$limit" : 1000 }])}
      

            Assignee:
            robert@mongodb.com Robert Stam
            Reporter:
            jfedorchak@meso-scale.com John Fedorchak
            Votes:
            0 Vote for this issue
            Watchers:
            3 Start watching this issue

              Created:
              Updated:
              Resolved: