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: Unresolved
    • Priority: Icon: Major - P3 Major - P3
    • None
    • Affects Version/s: 2.11.0
    • Component/s: Builders
    • Labels:
    • Environment:
      I've verified this behaviour both on Windows as well as in a Linux container

      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:
            Unassigned Unassigned
            Reporter:
            knoopjohn@gmail.com John Knoop
            Votes:
            0 Vote for this issue
            Watchers:
            4 Start watching this issue

              Created:
              Updated: