ExportXMLWordPrintableJSON

    • Type: Bug
    • Resolution: Fixed
    • Priority: Unknown
    • 10.0.4, 9.1.4, 8.4.4
    • Affects Version/s: None
    • Component/s: None
    • None
    • None
    • Dotnet Drivers
    • Not Needed
    • None
    • None
    • None
    • None
    • None
    • None

      Summary

      When a root entity carries its own FK property whose name is identical to the FK on an intermediate entity, a 2-hop reference ThenInclude resolves the second hop against the root's field instead of the intermediate's. The $lookup matches the wrong field, and the query silently returns a null navigation where real data is correct.

      Pre-existing. Measured byte-identical at 34a02067 and at d8128848 (the EF-372 fix), so EF-372 neither introduced nor closed it. Found while verifying EF-372's scoping invariant on 2026-08-05 (EF10, real atlas-local container).

      Repro

      Model — note PRoot and PMid both have a property named LeafId:

      Unable to find source-code formatter for language: csharp. Available languages are: actionscript, ada, applescript, bash, c, c#, c++, cpp, css, erlang, go, groovy, haskell, html, java, javascript, js, json, lua, none, nyan, objc, perl, php, python, r, rainbow, ruby, scala, sh, sql, swift, visualbasic, xml, yaml
      PRoot { Id, MidId -> PMid, LeafId -> PLeaf }
      PMid  { Id, LeafId -> PLeaf }
      

      Seed so the two paths disagree: the root's LeafId points at a leaf labelled "WRONG", the mid's LeafId at a leaf labelled "RIGHT".

      Unable to find source-code formatter for language: csharp. Available languages are: actionscript, ada, applescript, bash, c, c#, c++, cpp, css, erlang, go, groovy, haskell, html, java, javascript, js, json, lua, none, nyan, objc, perl, php, python, r, rainbow, ruby, scala, sh, sql, swift, visualbasic, xml, yaml
      db.PRoots.Include(r => r.Mid).ThenInclude(m => m.Leaf)
      

      Observed, identically under Native and DriverLinq:

      rows=1; mid=M1; midLeaf=<null>          (correct answer: "RIGHT")
      

      Emitted pipeline — note the leaf $lookup comes first and reads the root's own LeafId:

      aggregate([{ "$lookup" : { "from" : "PL…", "localField" : "LeafId", "foreignField" : "_id", "as" : "_lookup_Leaf" } },
      { "$unwind" : { "path" : "$_lookup_Leaf", "preserveNullAndEmptyArrays" : false } },
      { "$lookup" : { "from" : "PM…", "localField" : "MidId", "foreignField" : "_id", "as" : "_lookup_Mid" } },
      { "$unwind" : { "path" : "$_lookup_Mid", "preserveNullAndEmptyArrays" : false } }])
      

      The correct second stage would read _lookup_Mid.LeafId.

      Root cause

      src/MongoDB.EntityFrameworkCore/Query/Visitors/MongoQueryableMethodTranslatingExpressionVisitor.cs:1663-1671 matches a root navigation by FK-property name. Because the root happens to own a property of the same name, the match succeeds, so the hop is treated as a root-level join and never enters the transitive-resolution branch at all. TryResolveIntermediateLookupPrefix — and therefore EF-372's prefix-or-decline guarantee — is never consulted.

      This is why the invariant as originally worded in MongoEFToLinqTranslatingExpressionVisitor.LeftJoin.cs ("any chain that reaches here is fully scoped") was too strong: it holds only for hops that actually enter the transitive resolution. That wording has been narrowed.

      Fix shape

      Open. The root-navigation match needs to be keyed on something stronger than FK-property name — the declaring entity type of the FK, or the navigation actually traversed at that hop — so a name collision cannot divert a transitive hop into the root-level branch.

      Related, same area, all filed separately: EF-375 (isSecondOrLaterJoin over an IEntityTypekeyed dictionary), EF-376 (navigation-name-only lookup aliases), EF-377 (chained Join with no model navigation on hop 1), EF-378 (sibling Include}}s without {{ThenInclude). Several of these are name or type-keyed lookups that lose information; worth checking whether a single stronger key resolves more than one.

      Status

      Unreleased path: the native cross-collection Include machinery postdates v10.0.2 / v9.1.2 / v8.4.2 (arrived with efb5f256, EF-117). At those release tags any cross-collection Include throws, so no shipped behavior is affected and no BREAKING-CHANGES.md entry applies.

      Severity note: this one produces silently wrong data, not a loud failure, which puts it above EF-377 and EF-378 in priority. There is no regression test yet — one should be added with the fix, and it must discriminate on the navigation value ("RIGHT" vs null), not merely on non-nullness: EF's change-tracker identity fix-up can repair the object graph while the $lookup matched the wrong field, which is exactly how this shape masks itself.

            Assignee:
            Arthur Vickers
            Reporter:
            Arthur Vickers
            Votes:
            0 Vote for this issue
            Watchers:
            1 Start watching this issue

              Created:
              Updated:
              Resolved: