Bug description
When reassigning a 1:1 relationship with a unique index (foreign key on dependent side using a shadow property) to an attached entity, DbContext.SaveChangesAsync() throws a unique constraint violation exception (duplicate key value violates unique constraint "IX_...").
This worked as expected in EF Core 9, EF Core 10, and EF Core 11.0.0-preview.6, but fails in EF Core 11.0.0-preview.7.
This regression was introduced by PR #38581 (commit fb2b999e9b6acc578cff47edec132044618b6028).
This occurs across all relational providers: PostgreSQL (Npgsql), SQL Server (Microsoft.EntityFrameworkCore.SqlServer), and SQLite (Microsoft.EntityFrameworkCore.Sqlite).
Root Cause Analysis
PR #38581 ("Avoid false circular dependency edges for unchanged unique index values during update batching", fixing #35588) added checks to CommandBatchPreparer.AddUniqueValueEdges to skip edge creation for Modified entities when the unique index value is unchanged:
// Pass 2: Adding predecessor edges to successor commands
if (command.EntityState == EntityState.Modified)
{
var (originalValue, _) = rowIndexValueFactory.CreateEquatableIndexValue(command, fromOriginalValues: true);
if (Equals(originalValue, value))
{
continue;
}
}
However, when an untracked entity c0 is attached via DbContext.Attach(c0) and then assigned to a principal g1.Color = c0:
c0's shadow foreign key / unique index column GroupId is updated to G1 during navigation fixup, with IsModified = true and IsWrite = true. Because c0 was attached, both its OriginalValue and CurrentValue evaluate to G1.
- The entity previously associated with
G1 (c1) has GroupId cleared (OriginalValue = G1, CurrentValue = NULL).
- In
CommandBatchPreparer.AddUniqueValueEdges:
- Pass 1 (Predecessors):
c1 (OriginalValue = G1, CurrentValue = NULL) is registered in indexPredecessorsMap[G1] = [c1].
- Pass 2 (Successors): When evaluating
c0 (CurrentValue = G1), EF Core should create a dependency edge c1 -> c0 so c1 releases GroupId before c0 claims it.
- However, because
c0 has originalValue (G1) == value (G1), the new check in PR #38581 executes continue; and skips adding the dependency edge c1 -> c0.
- Without the dependency edge in
_modificationCommandGraph, c0 is executed before c1 (UPDATE RgbColors SET GroupId = 'G1' WHERE Id = '0x9a79e4'), violating the unique constraint IX_RgbColors_GroupId.
Your code
using System;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.EntityFrameworkCore;
// Minimal runnable reproduction
// Packages:
// - Microsoft.EntityFrameworkCore (11.0.0-preview.7.*)
// - Microsoft.EntityFrameworkCore.SqlServer (11.0.0-preview.7.*) OR
// - Microsoft.EntityFrameworkCore.Sqlite (11.0.0-preview.7.*) OR
// - Npgsql.EntityFrameworkCore.PostgreSQL (11.0.0-preview.6/preview.7)
var provider = Provider.Sqlite; // Change to Provider.SqlServer or Provider.Postgres to test other databases
await using var dbContext = new AppDbContext(provider);
await dbContext.Database.EnsureDeletedAsync();
await dbContext.Database.EnsureCreatedAsync();
// 1. Seed: Group0 has Color0, Group1 has Color1
var group0 = new WorkItemGroup { Name = "G0" };
var group1 = new WorkItemGroup { Name = "G1" };
var color0 = new RgbColor { Id = "0x9a79e4", DisplayName = "red" };
var color1 = new RgbColor { Id = "0xb4e6b9", DisplayName = "turquoise" };
group0.Color = color0;
group1.Color = color1;
dbContext.Groups.AddRange(group0, group1);
await dbContext.SaveChangesAsync();
// 2. In a new context: reassign Group1's color to Color0
await using var updateContext = new AppDbContext(provider);
var g1 = await updateContext.Groups.Include(g => g.Color).FirstAsync(g => g.Name == "G1");
var c0 = new RgbColor { Id = "0x9a79e4" };
updateContext.Attach(c0);
g1.Color = c0;
// In EF Core 10 and EF Core 11.0.0-preview.6:
// Color1 (SET GroupId = NULL) executes BEFORE Color0 (SET GroupId = G1) -> Succeeds!
//
// In EF Core 11.0.0-preview.7:
// Color0 (SET GroupId = G1) executes BEFORE Color1 (SET GroupId = NULL) -> Throws DbUpdateException
await updateContext.SaveChangesAsync();
Console.WriteLine("Saved successfully!");
public class WorkItemGroup
{
public Guid Id { get; set; } = Guid.NewGuid();
public string Name { get; set; } = "";
public RgbColor? Color { get; set; }
}
public class RgbColor
{
public string Id { get; set; } = "";
public string? DisplayName { get; set; }
public WorkItemGroup? Group { get; set; }
}
public enum Provider
{
Postgres,
SqlServer,
Sqlite
}
public class AppDbContext : DbContext
{
private readonly Provider _provider;
public AppDbContext(Provider provider)
{
_provider = provider;
}
public DbSet<WorkItemGroup> Groups => Set<WorkItemGroup>();
public DbSet<RgbColor> RgbColors => Set<RgbColor>();
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
switch (_provider)
{
case Provider.Sqlite:
optionsBuilder.UseSqlite("Data Source=test_efcore_repro.db");
break;
case Provider.SqlServer:
optionsBuilder.UseSqlServer("Server=localhost,1433;Database=test_efcore_repro;User Id=sa;Password=Passw0rd!;TrustServerCertificate=True");
break;
case Provider.Postgres:
optionsBuilder.UseNpgsql("Host=localhost;Port=5432;Database=test_efcore_repro;Username=postgres;Password=postgres");
break;
}
}
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<WorkItemGroup>()
.HasOne(g => g.Color)
.WithOne(c => c.Group)
.HasForeignKey<RgbColor>("GroupId");
}
}
Stack traces
PostgreSQL (Npgsql):
Microsoft.EntityFrameworkCore.DbUpdateException: An error occurred while saving the entity changes. See the inner exception for details.
---> Npgsql.PostgresException (0x80004005): 23505: duplicate key value violates unique constraint "IX_RgbColors_GroupId"
DETAIL: Key ("GroupId")=(01a06c7d-c9bf-7a22-af83-dac4c89d178f) already exists.
at Microsoft.EntityFrameworkCore.Update.ReaderModificationCommandBatch.ExecuteAsync(IRelationalConnection connection, CancellationToken cancellationToken)
SQL Server (Microsoft.EntityFrameworkCore.SqlServer):
Microsoft.EntityFrameworkCore.DbUpdateException: An error occurred while saving the entity changes. See the inner exception for details.
---> Microsoft.Data.SqlClient.SqlException (0x80131904): Cannot insert duplicate key row in object 'dbo.RgbColors' with unique index 'IX_RgbColors_GroupId'. The duplicate key value is (c5636c6b-c4ad-4297-94bf-fbbc681dff64).
at Microsoft.EntityFrameworkCore.Update.ReaderModificationCommandBatch.ExecuteAsync(IRelationalConnection connection, CancellationToken cancellationToken)
SQLite (Microsoft.EntityFrameworkCore.Sqlite):
Microsoft.EntityFrameworkCore.DbUpdateException: An error occurred while saving the entity changes. See the inner exception for details.
---> Microsoft.Data.Sqlite.SqliteException (0x80004005): SQLite Error 19: 'UNIQUE constraint failed: RgbColors.GroupId'.
at Microsoft.EntityFrameworkCore.Update.ReaderModificationCommandBatch.ExecuteAsync(IRelationalConnection connection, CancellationToken cancellationToken)
EF Core version
11.0.0-preview.7.* (worked in 11.0.0-preview.6, 10.0.x, and 9.0.x)
Database provider
Microsoft.EntityFrameworkCore.SqlServer / Microsoft.EntityFrameworkCore.Sqlite / Npgsql.EntityFrameworkCore.PostgreSQL
Target framework
.NET 11.0
Operating system
Windows 11
IDE
Visual Studio 2026 / VS Code / Rider
Bug description
When reassigning a 1:1 relationship with a unique index (foreign key on dependent side using a shadow property) to an attached entity,
DbContext.SaveChangesAsync()throws a unique constraint violation exception (duplicate key value violates unique constraint "IX_...").This worked as expected in EF Core 9, EF Core 10, and EF Core 11.0.0-preview.6, but fails in EF Core 11.0.0-preview.7.
This regression was introduced by PR #38581 (commit fb2b999e9b6acc578cff47edec132044618b6028).
This occurs across all relational providers: PostgreSQL (
Npgsql), SQL Server (Microsoft.EntityFrameworkCore.SqlServer), and SQLite (Microsoft.EntityFrameworkCore.Sqlite).Root Cause Analysis
PR #38581 ("Avoid false circular dependency edges for unchanged unique index values during update batching", fixing #35588) added checks to
CommandBatchPreparer.AddUniqueValueEdgesto skip edge creation forModifiedentities when the unique index value is unchanged:However, when an untracked entity
c0is attached viaDbContext.Attach(c0)and then assigned to a principalg1.Color = c0:c0's shadow foreign key / unique index columnGroupIdis updated toG1during navigation fixup, withIsModified = trueandIsWrite = true. Becausec0was attached, both itsOriginalValueandCurrentValueevaluate toG1.G1(c1) hasGroupIdcleared (OriginalValue = G1,CurrentValue = NULL).CommandBatchPreparer.AddUniqueValueEdges:c1(OriginalValue = G1, CurrentValue = NULL) is registered inindexPredecessorsMap[G1] = [c1].c0(CurrentValue = G1), EF Core should create a dependency edgec1 -> c0soc1releasesGroupIdbeforec0claims it.c0hasoriginalValue (G1) == value (G1), the new check in PR #38581 executescontinue;and skips adding the dependency edgec1 -> c0._modificationCommandGraph,c0is executed beforec1(UPDATE RgbColors SET GroupId = 'G1' WHERE Id = '0x9a79e4'), violating the unique constraintIX_RgbColors_GroupId.Your code
Stack traces
PostgreSQL (
Npgsql):SQL Server (
Microsoft.EntityFrameworkCore.SqlServer):SQLite (
Microsoft.EntityFrameworkCore.Sqlite):EF Core version
11.0.0-preview.7.*(worked in11.0.0-preview.6,10.0.x, and9.0.x)Database provider
Microsoft.EntityFrameworkCore.SqlServer/Microsoft.EntityFrameworkCore.Sqlite/Npgsql.EntityFrameworkCore.PostgreSQLTarget framework
.NET 11.0Operating system
Windows 11IDE
Visual Studio 2026 / VS Code / Rider