Skip to content

Commit f62650a

Browse files
Document EF Core 9 breaking change: NoTrackingWithIdentityResolution restricted for JSON collection queries (#5413)
Fixes #4932 Co-authored-by: Andriy Svyryd <AndriySvyryd@users.noreply.github.com>
1 parent 8165335 commit f62650a

1 file changed

Lines changed: 61 additions & 0 deletions

File tree

entity-framework/core/what-is-new/ef-core-9.0/breaking-changes.md

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ EF Core 9 targets .NET 8. This means that existing applications that target .NET
3535
| [Shared framework dependencies were updated to 9.0.x](#shared-framework-dependencies) | Low |
3636
| [EF tools no longer support .NET Framework projects](#ef-tools-no-netfx) | Low |
3737
| [`EF.Constant()` and `EF.Parameter()` no longer work inside compiled queries](#ef-constant-compiled) | Low |
38+
| [Some `NoTrackingWithIdentityResolution` queries are now prohibited for JSON collections](#no-tracking-json) | Low |
3839

3940
## High-impact changes
4041

@@ -389,6 +390,66 @@ The internal implementation of <xref:Microsoft.EntityFrameworkCore.EF.Constant*>
389390

390391
Either remove the <xref:Microsoft.EntityFrameworkCore.EF.Constant*> or <xref:Microsoft.EntityFrameworkCore.EF.Parameter*> call from the compiled query, or stop using a compiled query for that particular query. Note that removing `EF.Constant()` causes the value to be sent as a SQL parameter rather than inlined as a constant, which may affect query plan performance.
391392

393+
<a name="no-tracking-json"></a>
394+
395+
### Some `NoTrackingWithIdentityResolution` queries are now prohibited for JSON collections
396+
397+
[Tracking Issue #33073](https://github.com/dotnet/efcore/issues/33073)
398+
399+
#### Old behavior
400+
401+
Previously, using <xref:Microsoft.EntityFrameworkCore.EntityFrameworkQueryableExtensions.AsNoTrackingWithIdentityResolution*> (or setting <xref:Microsoft.EntityFrameworkCore.QueryTrackingBehavior.NoTrackingWithIdentityResolution>) with queries that include JSON-mapped entity collections could silently produce incorrect results or data corruption, depending on the order in which entities were processed during materialization. Additionally, such queries could throw an unhelpful `Invalid token type: 'StartObject'` exception in some scenarios.
402+
403+
#### New behavior
404+
405+
Starting with EF Core 9.0, EF Core restricts the use of <xref:Microsoft.EntityFrameworkCore.EntityFrameworkQueryableExtensions.AsNoTrackingWithIdentityResolution*> for certain JSON collection query patterns, to prevent silent data corruption:
406+
407+
- If entity instances in a JSON collection would be materialized in an order that could cause data corruption, EF Core throws an exception instructing the user to use a different tracking behavior.
408+
- Using LINQ operators (such as `OrderBy`, `Where`, `Skip`, `Take`, etc.) directly on JSON collection navigations in a query with `AsNoTrackingWithIdentityResolution()` is now prohibited. For example, the following query would throw an exception:
409+
410+
```csharp
411+
var blogs = await context.Blogs
412+
.AsNoTrackingWithIdentityResolution()
413+
.Select(b => new
414+
{
415+
Blog = b,
416+
TopPosts = b.JsonPosts.OrderBy(p => p.Rating).Take(3).ToList()
417+
})
418+
.ToListAsync();
419+
```
420+
421+
#### Why
422+
423+
The combination of <xref:Microsoft.EntityFrameworkCore.EntityFrameworkQueryableExtensions.AsNoTrackingWithIdentityResolution*> and JSON collections could silently produce incorrect materialized objects due to how JSON is streamed from the database: nested includes in JSON are part of the parent's materialization rather than being materialized separately. The stand-alone change tracker used for identity resolution relies on key values to deduplicate entity instances, but when LINQ operators are applied to JSON collections, EF Core cannot reliably propagate those key values to the materializer, resulting in entities with null keys and potential data corruption.
424+
425+
#### Mitigations
426+
427+
Use a regular tracking query if identity resolution is required:
428+
429+
```csharp
430+
var blogs = await context.Blogs
431+
.AsTracking()
432+
.Select(b => new
433+
{
434+
Blog = b,
435+
TopPosts = b.JsonPosts.OrderBy(p => p.Rating).Take(3).ToList()
436+
})
437+
.ToListAsync();
438+
```
439+
440+
If you do not need identity resolution, use <xref:Microsoft.EntityFrameworkCore.EntityFrameworkQueryableExtensions.AsNoTracking*> instead:
441+
442+
```csharp
443+
var blogs = await context.Blogs
444+
.AsNoTracking()
445+
.Select(b => new
446+
{
447+
Blog = b,
448+
TopPosts = b.JsonPosts.OrderBy(p => p.Rating).Take(3).ToList()
449+
})
450+
.ToListAsync();
451+
```
452+
392453
## Azure Cosmos DB breaking changes
393454

394455
Extensive work has gone into making the Azure Cosmos DB provider better in 9.0. The changes include a number of high-impact breaking changes; if you are upgrading an existing application, please read the following carefully.

0 commit comments

Comments
 (0)