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; }
}
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"]
.
Example that reproduces 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
.
I thought I’d post it here first, to check if this is already a known issue, before I open a Jira ticket.