dotnet / dotnet/efcore

Add a SetLoaded method with a lambda callback that allows the state of entities in the collection to be set.

Open
#27,436 18 comments 0 reactions 0 assignees View on GitHub
area-change-tracking customer-reported
Dominant language
C#
Stars
14.8k
Forks
3.4k
PR merge metrics
PR metrics pending

Description

## File a bug

After updating EF Core from v6.0.1 to v6.0.2, two of our tests start failing. This looks like a regression in the change tracker, which no longer produces SQL for removing an element from a many-to-many relationship.

The failing tests are:
- Many-to-many relationship: https://github.com/json-api-dotnet/JsonApiDotNetCore/blob/19f655704c91fa89f5b7561e1dd66586d9b521ee/test/JsonApiDotNetCoreTests/IntegrationTests/ReadWrite/Updating/Relationships/RemoveFromToManyRelationshipTests.cs#L239
- Self-referencing many-to-many relationship: https://github.com/json-api-dotnet/JsonApiDotNetCore/blob/19f655704c91fa89f5b7561e1dd66586d9b521ee/test/JsonApiDotNetCoreTests/IntegrationTests/ReadWrite/Updating/Relationships/RemoveFromToManyRelationshipTests.cs#L1001

To understand what's going on: Both tests set up the database, then create an API request to remove an element from an existing many-to-many relationship. Afterwards the database contents is inspected to verify the relationship has been updated.

The logic in the API (system under test) is defined at https://github.com/json-api-dotnet/JsonApiDotNetCore/blob/19f655704c91fa89f5b7561e1dd66586d9b521ee/src/JsonApiDotNetCore/Services/JsonApiResourceService.cs#L460, which delegates to https://github.com/json-api-dotnet/JsonApiDotNetCore/blob/19f655704c91fa89f5b7561e1dd66586d9b521ee/src/JsonApiDotNetCore/Repositories/EntityFrameworkCoreRepository.cs#L450. At a global level, the following happens: The _resource service_ builds an EF Core query that retrieves the parent entity, along with the included subset of related resources to remove. The produced SQL for that is unchanged between EF Core versions:

```sql
-- Microsoft.EntityFrameworkCore.Database.Command: Information: Executed DbCommand (2ms) [Parameters=[@__Create_Item1_0='1'], CommandType='Text', CommandTimeout='30']
SELECT w."Id", t."Id", t."FromItemId", t."ToItemId"
FROM "WorkItems" AS w
LEFT JOIN (
SELECT w1."Id", w0."FromItemId", w0."ToItemId"
FROM "WorkItemToWorkItem" AS w0
INNER JOIN "WorkItems" AS w1 ON w0."FromItemId" = w1."Id"
WHERE w1."Id" = @__Create_Item1_0
) AS t ON w."Id" = t."ToItemId"
WHERE w."Id" = @__Create_Item1_0
ORDER BY w."Id", t."FromItemId", t."ToItemId"
```

Then the _resource repository_ ensures all returned entities are tracked, marks the to-many relationship as loaded, then removes the requested related resources from the parent entity. In v6.0.1 the change tracker would "see" the removed related entities and produce SQL for that, but in v6.0.2 that no longer happens.

Result from `_dbContext.ChangeTracker.DebugView.LongView`, after calling `_dbContext.ChangeTracker.DetectChanges();` just before `SaveChangesAsync` in v6.0.1:
```
WorkItem {Id: 1} Unchanged
Id: 1 PK
AssigneeId: FK Unknown
Description:
DueAt:
GroupId: FK Unknown
ParentId: FK Unknown
Priority: 'Low'
Assignee:
Children: []
Group:
Parent:
Subscribers: []
RelatedFrom: []
RelatedTo: []
Tags: []
WorkItemToWorkItem {FromItemId: 1, ToItemId: 1} Deleted
FromItemId: 1 PK FK
ToItemId: 1 PK FK
FromItem:
ToItem:
```

In v6.0.2, this returns:
```
WorkItem {Id: 1} Unchanged
Id: 1 PK
AssigneeId: FK Unknown
Description:
DueAt:
GroupId: FK Unknown
ParentId: FK Unknown
Priority: 'Low'
Assignee:
Children: []
Group:
Parent:
Subscribers: []
RelatedFrom: []
RelatedTo: []
Tags: []
```

### Include provider and version information

EF Core version: v6.0.2
Database provider: PostgreSQL (tried various 6.0.x versions, no differences observed)
Target framework: .NET 6
Operating system: Windows 10 x64
IDE: Visual Studio Enterprise 2022 v17.0.5

Contributor guide

Open the contributing guide

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.