Cosmos Serialization Modernization in EF Core

PR #38024 was not a simple public API removal. It modernized the EF Core Cosmos write pipeline from Newtonsoft DOM objects toward System.Text.Json streaming, while also adding a public deprecation and changing internal update behavior.

dotnet/efcorePR #38024
GCI0004GCI0003GCI0015BLOCK/REVIEWCosmos DBSerializationAPI Contracts

33

files changed

1,815

lines touched

1

public obsolete API

What changed

The PR replaced parts of the Cosmos update pipeline that used JObjectand JTokenwith serialized byte payloads written through Utf8JsonWriter. It also marked ContentResponseOnWriteEnabled()obsolete because EF Core no longer benefits from the response body content.

The largest compatibility question is not the public method being deleted. It is the shift from mutating a previously hydrated JSON document to serializing a fresh document from EF-tracked state.

Why this is risky

Cosmos documents can contain fields that are not modeled by EF. Before this modernization, the pipeline could preserve unknown fields through the internal __jObjectpath. After the change, updates serialize the modeled entity state again. That is cleaner and faster, but it raises a silent data-loss question for applications that store extra JSON alongside EF-managed properties.

The new obsolete attribute is also worth a reviewer stop. It tells users to stop using the API, but the message does not name a replacement because the intended migration is simply to remove the call.

What the original thin page got wrong

The earlier framing said this was a public API removal that would break every provider author. The verified diff is more nuanced: the direct public API change is an [Obsolete]annotation, while most signature churn is on EF-internal types that carry explicit internal-API disclaimers.

The stronger case study is the behavioral one: a serialization pipeline rewrite can be correct and still deserve merge-gate attention because persisted JSON shape and unmapped-field preservation are production contracts.

Diff evidence

src/EFCore.Cosmos/Storage/Internal and CosmosDbContextOptionsBuilder.cs
// Public Cosmos option marked obsolete
+[Obsolete("Enabling ContentResponseOnWrite currently has no benefit for EF Core.")]
public virtual CosmosDbContextOptionsBuilder ContentResponseOnWriteEnabled(bool enabled = true)
// Internal wrapper signatures moved from DOM objects to serialized bytes
-public virtual Task<bool> CreateItemAsync(string containerId, JToken document, ...)
+public virtual Task<bool> CreateItemAsync(string containerId, string documentId, ReadOnlyMemory<byte> document, ...)
-public virtual Task<bool> ReplaceItemAsync(string collectionId, string documentId, JObject document, ...)
+public virtual Task<bool> ReplaceItemAsync(string collectionId, string documentId, ReadOnlyMemory<byte> document, ...)
// Write-response hydration of __jObject was removed
-var createdDocument = Serializer.Deserialize<JObject>(jsonReader);
-entry.SetStoreGeneratedValue(jObjectProperty, createdDocument);
+entry.SetStoreGeneratedValue(etagProperty, eTag);

GauntletCI review signals

[GCI0004] Breaking Change Risk
Signal   : [Obsolete] added to a public Cosmos options-builder API

[GCI0003] Behavioral Change Detection
Signal   : method signatures changed across Cosmos wrapper/update pipeline types

[GCI0015] Data Integrity reviewer question
Signal   : fresh serialization can drop unmapped JSON fields that were previously preserved

Important context

  • The PR did not remove the public ContentResponseOnWriteEnabled method; it added Obsolete.
  • Most signature changes are on EF-internal types, so their compatibility risk is lower than ordinary public API churn.
  • The unmapped-field data-loss concern is a reviewer question, not proof that the PR was wrong; the PR intentionally modernized the pipeline.

Recommended review steps

  • Verify the Obsolete message gives users a clear migration decision: remove the call rather than switch APIs.
  • Review tests for Cosmos documents with unmapped JSON fields to confirm the new update path has the intended behavior.
  • Separate internal API churn from externally supported API changes in release notes and compatibility docs.

Sources

About the author

Eric Cogen -- Founder, GauntletCI

Eric Cogen is a senior .NET engineer with twenty years in production. He has shipped payments systems, internal platforms, and critical line-of-business applications — the kind where a 2 a.m. alert wasn't an emergency, it was a regular Tuesday. GauntletCI is the pre-commit checklist he wishes he had run before every commit.