You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
#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 OwnedCollectionBranchabsent
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?
Bug description
#38625 reports that a complex collection mapped to JSON whose key is absent from the stored document is materialized as
nullrather 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:
JsonEntityMaterializerRewriterruns every nested-property fixup after the read loop, so a navigation that never appeared in the document has its fixup invoked withnull.Reproduced on
mainwith the existingJsonEntityBasicfixture (SQLite,json_remove/json_setto shape the stored document):OwnedReferenceRootdocumentAsNoTracking()OwnedCollectionBranchabsentOwnedCollectionBranch == nullnull[]So an application that adds an owned collection to an existing JSON-mapped owned type sees
nullfor 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
nullis preserved.Precedent within EF
CosmosShapedQueryCompilingExpressionVisitorcreates a nested collection withcollectionAccessor.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 explicitnull; the relational fix in Materialize required collections absent from a JSON document as empty #39014 keepsnullthere, which seems the better distinction, but the absent-key outcome is the same.)[].MaterializeJsonEntityCollectionbuilds the empty collection through the property'sIClrCollectionAccessor; 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 asIComplexProperty. Two details differ from the complex case and are worth a decision:nullor 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.nullfor an absent key since JSON columns shipped in EF 7. Anyone relying onnullhere 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?