-
Type:
Bug
-
Resolution: Unresolved
-
Priority:
Unknown
-
None
-
Affects Version/s: None
-
Component/s: None
-
None
Summary
For a document whose embedded (owned-collection) array field is missing or explicitly BSON null, the two materialization paths disagree:
- *Whole-entity materialization* normalizes it to an *empty list* — blog.Posts.Count == 0.
- *The projection path* (CollectionShaperExpression) materializes *null*.
Measured on the same three documents (empty array / missing field / explicit null), EF10, isolated mongodb-atlas-local container:
empty-array whole-entity: Posts is count=0 bare-count projection: OK -> 0 missing whole-entity: Posts is count=0 bare-count projection: ArgumentNullException (Parameter 'source') explicit-null whole-entity: Posts is count=0 bare-count projection: ArgumentNullException (Parameter 'source')
ArgumentNullException: Value cannot be null. (Parameter 'source') is Enumerable.Count(null) — the fold receives null where LINQ semantics say empty.
Why this is filed separately
This is the residual that keeps EF-357 only partially resolved. EF-357's translation-time ArgumentException for Select(b => b.Posts.Count) is fixed on branch EF-322-owned-collection-count-projection (commit 0cb1b1b), but the projection now reaches materialization and throws this ArgumentNullException for exactly those rows.
It is not a count problem. It is a general property of the projection path and will be the first thing an array-projection slice (Select(b => b.Posts)) hits.
Why it was not folded into the count-projection slice
Fixing it changes observable results for collection projections that work today: Select(b => b.Posts) currently returns null for those rows, and normalizing to an empty list changes that. It therefore needs its own verification pass over the array-projection shapes, rather than riding along with a count fix.
Note on the native path
The native $project path does not have this problem: an owned count leaf renders as {{{ $size:
{ $ifNull: [ "$Posts", [] ] }}}} and correctly yields 0 for both states. The asymmetry is between whole-entity/native and the driver-LINQ fallback projection path.
A practical consequence for testing: a Native == DriverLinq parity assertion over a seed containing missing/null arrays fails by design for these shapes, because native is more correct than the fallback. Differential tests for such shapes must use an in-memory LINQ oracle built from whole-entity materialization, not driver parity.
Source of the measurement
Spike findings document committed on branch EF-322-owned-collection-count-projection:
docs/superpowers/specs/2026-07-28-native-owned-collection-count-projections-spike-findings.md (question Q4).
Pinned in the test suite by
NativeOwnedCollectionCountTests.Bare_embedded_collection_Count_projection_still_throws_for_a_missing_or_null_array.
Also documented in src/MongoDB.EntityFrameworkCore/Query/AGENTS.md, in the "Owned-collection .Count as a PROJECTION LEAF" note.
Suggested fix
Have the projection path's CollectionShaperExpression produce an empty collection instance rather than null when the underlying BSON element is missing or null, matching whole-entity materialization. Verify against the full set of array-projection shapes before landing, since it changes today's observable results.