Changes made on a foreign key id is lost if we read the foreign object after assignation in some cases
Issue with foreign key ids changes that is lost after read
We have a weird issue with the change tracker since upgrade from .net 6 to .net 7. This issue is also present on .net 8.0.1.
I tracked down the issue and was able to write a really minimal example to reproduce it.
- Two parent entities must be read including the same child entity.
- Change the foreign key id of one of the parent to point to another child object. The new child object is not currently tracked.
- Then, if, somewhere before the SaveChanges (or any DetectChanges), we read the new object (not currently tracked) then the foreign key change is lost.
Minimal code to reproduce the issue
public class Parent1
{
public int Id { get; set; }
public ReferenceTable? ReferenceTable { get; set; }
public int? ReferenceTableId { get; set; }
}
public class Parent2
{
public int Id { get; set; }
public ReferenceTable? ReferenceTable { get; set; }
public int? ReferenceTableId { get; set; }
}
public class ReferenceTable
{
public int Id { get; set; }
public string Value { get; set; } = string.Empty;
}
using Microsoft.EntityFrameworkCore;
int newId;
// Data seed
using (var context = new TestContext())
{
var referenceTable1 = new ReferenceTable { Value = "Record 1" };
var referenceTable2 = new ReferenceTable { Value = "Record 2" };
context.Add(referenceTable1);
context.Add(referenceTable2);
context.SaveChanges();
context.Add(new Parent1 { ReferenceTableId = referenceTable1.Id });
context.Add(new Parent2 { ReferenceTableId = referenceTable1.Id });
context.SaveChanges();
newId = referenceTable2.Id;
}
// Minimal code for bug reproduction
using (var context = new TestContext())
{
// Two parents that include the same referenceTable item are read
var parent1 = await context.Parents1
.Include(x => x.ReferenceTable)
.SingleAsync();
var parent2 = await context.Parents2
.Include(x => x.ReferenceTable)
.SingleAsync();
// Change the foreign key of reference table from 1 to 2 (2 is not tracked)
Console.WriteLine($"Change the foreign key of reference table from {parent2.ReferenceTableId} to {newId} (new one is not currently tracked)");
parent2.ReferenceTableId = newId;
Console.WriteLine($"The value is now : {parent2.ReferenceTableId}");
// Read the new one (this cause it to be tracked)
var referenceTableSecondRecord = await context.ReferenceTables
.SingleOrDefaultAsync(x => x.Id == newId);
// In .net 6, the value here is the new one. However in .net 7 the new value of the foreign key is lost.
Console.WriteLine($"After the read operation, the value is : {parent2.ReferenceTableId}");
}
Output:
Change the foreign key of reference table from 5 to 6 (new one is not currently tracked)
The value is now : 6
After the read operation, the value is : 5
Full project here: ConsoleApp.zip
Provider and version information
EF Core version: 7.0.15 (also tested 8.0.1) Database provider: tested PostgreSql and SQlite. The issue arise with both. Target framework: 7.0.15 (also tested 8.0.1) Operating system: Windows and also tested with docker (mcr.microsoft.com/dotnet/aspnet:7.0-bullseye-slim) IDE: Visual Studio 2022 17.8
Note for team: technically, a call to DetectChanges is required between setting the FK and performing the loading. However, we may be able to improve this with a local check.
If it's a wanted change between ef core 6 and 7, I would suggest a "breaking changes" entry on the ef core 6 to 7 migration documentation. Because this undocumented breaking change can be really dangerous as some foreign key updates that succeeded in version 6 do not work anymore in versions 7 and 8. Without throwing any error, so at risk of data corruption. Thanks