Skip to content

Owned JSON collection navigation absent from the stored document materializes as null (owned counterpart of #38625) #39060

Description

@philcarbone

Bug description

#38625 reports that a complex collection mapped to JSON whose key is absent from the stored document is materialized as null rather than an empty collection, so rows persisted before the collection was added to the type become unusable. #39014 fixes that for complex collections and, on purpose, leaves owned entity collection navigations mapped to JSON alone to keep the change to the reported scenario.

The owned case has exactly the same behavior, from the same code path: JsonEntityMaterializerRewriter runs every nested-property fixup after the read loop, so a navigation that never appeared in the document has its fixup invoked with null.

Reproduced on main with the existing JsonEntityBasic fixture (SQLite, json_remove/json_set to shape the stored document):

Stored OwnedReferenceRoot document AsNoTracking() tracking
key OwnedCollectionBranch absent OwnedCollectionBranch == null null
key present with [] empty list empty list

So an application that adds an owned collection to an existing JSON-mapped owned type sees null for every pre-existing row, but [] for every row written afterwards. Documents written by EF always contain the key, so this only bites after schema evolution or with documents produced by another writer.

Expected behavior

Consistent with the fix for complex collections in #39014: an absent owned collection navigation materializes as an empty collection; an explicit JSON null is preserved.

Precedent within EF

  • Cosmos already does this. CosmosShapedQueryCompilingExpressionVisitor creates a nested collection with collectionAccessor.Create() before its property read loop and adds elements as they are read (source), so a key that never appears yields an empty collection. (Cosmos also yields empty for an explicit null; the relational fix in Materialize required collections absent from a JSON document as empty #39014 keeps null there, which seems the better distinction, but the absent-key outcome is the same.)
  • Relational already produces the same object for a present []. MaterializeJsonEntityCollection builds the empty collection through the property's IClrCollectionAccessor; the fix in Materialize required collections absent from a JSON document as empty #39014 uses the same accessor for the absent case, so absent and [] become indistinguishable, which is what a schema-evolved row needs.

Notes for the fix

The mechanism from #39014 extends directly: register the absent-property fixup for INavigation { IsCollection: true } as well as IComplexProperty. Two details differ from the complex case and are worth a decision:

  • Tracking queries. For owned entities the change tracker performs fixup, and the existing tracked fixup only assigns the collection when it is null or empty. An absent key should produce the same result as a present [], which the tracked path already assigns, so the absent fixup can assign the empty collection unconditionally.
  • Behavioral change. Unlike complex collections (new in 10.0), owned JSON collections have materialized null for an absent key since JSON columns shipped in EF 7. Anyone relying on null here would see [] instead.

I'm happy to do this either as an extension of #39014 or as a separate PR once that one lands; which would the team prefer?

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions