-
Type:
Bug
-
Resolution: Fixed
-
Priority:
Unknown
-
Affects Version/s: None
-
Component/s: None
-
None
-
Dotnet Drivers
-
Not Needed
-
None
-
None
-
None
-
None
-
None
-
None
Summary
A cross-collection reference Include chained three hops deep emits a $lookup whose localField is missing the preceding lookup's alias prefix. The field does not exist at that point in the pipeline, so the following inner $unwind drops every document and the query silently returns zero rows.
db.Lines.Include(l => l.Order).ThenInclude(o => o.Buyer).ThenInclude(b => b.Region).ToList();
// correct: 4 rows. actual: 0 rows.
Silent wrong data — an empty result set, no exception — in the default query mode.
Evidence
Captured MQL, with a purpose-built all-required model (Line to Order to Buyer to Region):
{"$lookup":{"from":"Orders","localField":"ord_id","foreignField":"_id","as":"_lookup_Order"}},
{"$unwind":{"path":"$_lookup_Order","preserveNullAndEmptyArrays":false}},
{"$lookup":{"from":"Buyers","localField":"_lookup_Order.buyer_id","foreignField":"_id","as":"_lookup_Buyer"}},
{"$unwind":{"path":"$_lookup_Buyer","preserveNullAndEmptyArrays":false}},
{"$lookup":{"from":"Regions","localField":"region_id","foreignField":"_id","as":"_lookup_Region"}},
{"$unwind":{"path":"$_lookup_Region","preserveNullAndEmptyArrays":false}}
Hop 2 correctly prefixes its localField with _lookup_Order. Hop 3 emits a bare region_id where _lookup_Buyer.region_id is required. Since region_id is not a field of the document at that stage, the lookup produces an empty array and the non-preserving $unwind removes the row.
Control: the same model's two-hop Include(l => l.Order).ThenInclude(o => o.Buyer) returns the correct rows, so the model is not at fault. The defect also reproduces with no composed operator anywhere in the query.
Scope
Unreleased. The affected code arrived with efb5f25 ("EF-117: Cross-collection Include / navigations / joins", PR #309), which is not an ancestor of v10.0.2; at every release tag the files do not exist and the driver-LINQ bridge threw "does not support Join, Include or navigation property access across collections". So no released version is affected and this is not a breaking change.
Relationship to EF-370 / EF-369
Found while re-reviewing the EF-370 fix. Not a regression from it — reproduced byte-identically at that branch's pre-fix commit. But it bounds an invariant EF-370 relies on: the transitive-localField prefixing that lets forced-unwind lookups be repositioned as a group is only actually correct to depth two. A comment added by EF-370 cites that invariant as load-bearing, so this ticket is what stops that citation from being wrong beyond depth 2.
Note the interaction with EF-370's other change: because a required navigation now emits a non-preserving $unwind, the dangling localField produces an empty result rather than rows carrying a null navigation. The underlying prefixing defect predates that and is independent of it, but the symptom is more severe with inner-join semantics.
Suggested direction
The hop-2 prefixing already works, so the fix is presumably to apply the same alias-prefixing rule at every hop beyond the first rather than only the second. Worth checking whether the depth-2 handling is special-cased or merely happens to be the only case exercised.
Gating tests should assert row counts and identities at three and four hops, not MQL — the wrong MQL here is indistinguishable from a legitimately empty result.