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

Builders<T>.Projection fails to deserialize arrays nested in subdocuments

    • Type: Icon: Bug Bug
    • Resolution: Won't Fix
    • Priority: Icon: Major - P3 Major - P3
    • None
    • Affects Version/s: 2.11.0
    • Component/s: LINQ2
    • Environment:
      I've verified this behaviour both on Windows as well as in a Linux container
    • None
    • Hide

      1. What would you like to communicate to the user about this feature?
      2. Would you like the user to see examples of the syntax and/or executable code and its output?
      3. Which versions of the driver/connector does this apply to?

      Show
      1. What would you like to communicate to the user about this feature? 2. Would you like the user to see examples of the syntax and/or executable code and its output? 3. Which versions of the driver/connector does this apply to?
    • None
    • None
    • None
    • None
    • None
    • None

      I believe I’ve found a bug related to projection, specifically when you map an array that is a deep descendant of the document root.

      Suppose this is my model:

      public class DummyContainer
      {
          public DummyContainer(IList<Dummy> dummies)
          {
      
              Dummies = dummies;
              this.Id = ObjectId.GenerateNewId().ToString();
          }
      
          public string Id { get; private set; }
          public IList<Dummy> Dummies { get; private set; }
      }
      
      public class Dummy
      {
          public Dummy(string name)
          {
              this.Name = name;
              this.Id = ObjectId.GenerateNewId().ToString();
          }
      
          public string Id { get; private set; }
          public string Name { get; private set; }
      }
      

      First, an example that works:

      Let’s say I insert one document of type DummyContainer into an empty collection:

      collection.InsertOneAsync(new DummyContainer(new[]
      {
          new Dummy("SomeValue1"),
          new Dummy("SomeValue2"),
      }));
      

      And then I load all documents from that collection, projected into this type:

      class DummyContainerWithDummyNames
      {
          public string ContainerId { get; set; }
          public IEnumerable<string> DummyNames { get; set; }
      }
      

      Here we go:

      var dummyNamesByUsingFindAsync = await (await collection.FindAsync(Builders<DummyContainer>.Filter.Empty, new FindOptions<DummyContainer, DummyContainerWithDummyNames> { 
              Projection = Builders<DummyContainer>.Projection.Expression(x => new DummyContainerWithDummyNames
              {
                  ContainerId = x.Id,
                  DummyNames = x.Dummies.Select(d => d.Name)
              })
          })).ToListAsync();
      
      var dummyNamesUsingQuery = await collection.AsQueryable()
              .Select(x => new DummyContainerWithDummyNames
              {
                  ContainerId = x.Id,
                  DummyNames = x.Dummies.Select(d => d.Name)
              })
              .ToListAsync();
      

      Result:

      Both dummyNamesByUsingFindAsync and dummyNamesUsingQuery contain one document, and in both cases the property DummyNames is populated with ["SomeValue1", "SomeValue2"].

      An example that shows the bug:

      Ok, in order to reproduce the bug, we’re gonna wrap the DummyContainer in a DummyContainerWrapper and let this be our root entity:

       

      public class DummyContainerWrapper
      {
          public DummyContainerWrapper(DummyContainer container)
          {
              Container = container;
              this.Id = ObjectId.GenerateNewId().ToString();
          }
      
          public string Id { get; private set; }
          public DummyContainer Container { get; private set; }
      }
      

      We’re gonna insert such a document to a new collection:

       

      await collection.InsertOneAsync(new DummyContainerWrapper(new DummyContainer(new[]
      {
          new Dummy("SomeValue1"),
          new Dummy("SomeValue2"),
      })));
      

       

      And just like before, we’re gonna query the collection for all documents and project them into the same type we used above:

       

      var dummyNamesByUsingFindAsync = await (await collection.FindAsync(Builders<DummyContainerWrapper>.Filter.Empty, new FindOptions<DummyContainerWrapper, DummyContainerWithDummyNames> { 
          Projection = Builders<DummyContainerWrapper>.Projection.Expression(x => new DummyContainerWithDummyNames
          {
              ContainerId = x.Id,
              DummyNames = x.Container.Dummies.Select(d => d.Name)
          })
      })).ToListAsync();
      
      var dummyNamesUsingQuery = await collection.AsQueryable()
          .Select(x => new DummyContainerWithDummyNames
          {
              ContainerId = x.Id,
              DummyNames = x.Container.Dummies.Select(d => d.Name)
          })
          .ToListAsync();
      

      And this is where we see the bug:

      dummyNamesUsingQuery has one element, who’s DummyNames }}property is an {{IEnumerable<string> (the concrete type is List<string>) populated with{{ ["SomeValue1", "SomeValue2"]}}.

      dummyNamesByUsingFindAsync }}on the other hand also has one element, but it´s {{DummyNames }}property is of type {{System.Linq.Enumerable.SelectListIterator<MyTestProgram.Dummy, string>, and the values are both null.

       

       

       

            Assignee:
            robert@mongodb.com Robert Stam
            Reporter:
            knoopjohn@gmail.com John Knoop
            Votes:
            0 Vote for this issue
            Watchers:
            6 Start watching this issue

              Created:
              Updated:
              Resolved: