Bug description
Summary
In EF Core 11, a relationship configured with IsConstrained(false) still participates as a non-breakable dependency in the SaveChanges modification-command graph.
This can cause SaveChanges() to throw a circular dependency exception even though there is no corresponding database foreign key constraint and the database can persist the graph safely.
A common example is:
- Entity A has a SQL Server
IDENTITY int primary key.
- Entity B has a client-generated
Guid primary key.
- A contains
BId, which references B.Id through an unconstrained EF relationship (IsConstrained(false)).
- B contains
RootId, which is a normal constrained FK referencing A.Id.
The database-valid insert order is:
- Insert A, including the already-known
BId GUID. Since this relationship is unconstrained, B does not need to exist yet.
- Obtain A's generated
IDENTITY value.
- Insert B using that value as
RootId.
This should be possible in a single SaveChanges() call. EF may of course use a separate internal batch to retrieve and propagate the store-generated identity value.
Currently EF detects:
A -> B // because A.BId references B.Id, even though IsConstrained(false)
B -> A // real FK B.RootId -> A.Id
and reports a circular dependency before executing the valid insert sequence.
Minimal reproduction
Using:
Microsoft.EntityFrameworkCore.SqlServer 11.0.0-rc.1.26425.128
Example:
using Microsoft.EntityFrameworkCore;
public sealed class ReproContext : DbContext
{
public DbSet<EntityA> EntitiesA => Set<EntityA>();
public DbSet<EntityB> EntitiesB => Set<EntityB>();
protected override void OnConfiguring(DbContextOptionsBuilder options)
=> options
.UseSqlServer(
@"Server=(localdb)\mssqllocaldb;" +
@"Database=EfCoreUnconstrainedCircularDependency;" +
@"Trusted_Connection=True;" +
@"TrustServerCertificate=True")
.EnableSensitiveDataLogging();
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<EntityA>(builder =>
{
builder.HasKey(x => x.Id);
// SQL Server IDENTITY
builder.Property(x => x.Id)
.ValueGeneratedOnAdd();
// A -> B is intentionally NOT backed by a database FK constraint.
//
// B.Id is client-generated and is therefore already known when A
// is inserted.
builder.HasOne(x => x.EntityB)
.WithMany()
.HasForeignKey(x => x.EntityBId)
.IsConstrained(false);
});
modelBuilder.Entity<EntityB>(builder =>
{
builder.HasKey(x => x.Id);
// Client-generated Guid.
builder.Property(x => x.Id)
.ValueGeneratedNever();
// This IS a real database FK.
// A must therefore be inserted first so that its IDENTITY value
// can be propagated into EntityB.RootId.
builder.HasOne(x => x.Root)
.WithMany()
.HasForeignKey(x => x.RootId)
.OnDelete(DeleteBehavior.NoAction);
});
}
}
public sealed class EntityA
{
public int Id { get; set; }
public Guid EntityBId { get; set; }
public EntityB EntityB { get; set; } = null!;
}
public sealed class EntityB
{
public Guid Id { get; set; }
public int RootId { get; set; }
public EntityA Root { get; set; } = null!;
}
Reproduction:
await using var db = new ReproContext();
await db.Database.EnsureDeletedAsync();
await db.Database.EnsureCreatedAsync();
var entityBId = Guid.NewGuid();
var a = new EntityA
{
EntityBId = entityBId
};
var b = new EntityB
{
Id = entityBId,
Root = a
};
a.EntityB = b;
db.AddRange(a, b);
await db.SaveChangesAsync();
SaveChangesAsync() currently fails because EF detects a circular dependency.
Expected behavior
SaveChanges() should succeed.
The required ordering is unambiguous:
INSERT EntityA
EntityBId = <known Guid>
retrieve EntityA.Id
INSERT EntityB
Id = <known Guid>
RootId = EntityA.Id
There is no database constraint requiring EntityB to exist before EntityA is inserted because the EntityA.EntityBId -> EntityB.Id relationship is configured with:
The normal constrained relationship:
EntityB.RootId -> EntityA.Id
must still participate in command ordering and, because EntityA.Id is store-generated, must still create the appropriate batching boundary.
Actual behavior
The unconstrained relationship participates in the modification-command dependency graph in the same way as a constrained relationship.
This creates:
EntityB -> EntityA
EntityA -> EntityB
and CommandBatchPreparer.TopologicalSort() reports a circular dependency.
Why I believe this is specific to IsConstrained(false)
IsConstrained(false) explicitly means that the EF relationship is not backed by a database foreign key constraint.
The EF Core 11 implementation already takes this into account for:
- relational model generation / migrations;
- query join semantics;
- the assumption that a matching principal exists.
However, command ordering still appears to treat the model-level foreign key as a mandatory store-ordering dependency.
CommandBatchPreparer currently handles model foreign keys without a mapped relational constraint in GetForeignKeyValues() / AddForeignKeyEdges() and eventually adds the edge using:
_modificationCommandGraph.AddEdge(
predecessor,
command,
new CommandDependency(foreignKey),
requiresBatchingBoundary);
The dependency therefore isn't breakable even when:
foreignKey.IsConstrained == false
and no store-generated value needs to be propagated across that particular relationship.
Possible implementation direction
Would it make sense for an unconstrained FK dependency to be breakable when it is not required for store-generated value propagation?
Conceptually something along the lines of:
var breakable =
!foreignKey.IsConstrained
&& !requiresBatchingBoundary;
_modificationCommandGraph.AddEdge(
predecessor,
command,
new CommandDependency(
foreignKey,
Breakable: breakable),
requiresBatchingBoundary);
This would preserve the current useful ordering preference for unconstrained relationships, but would allow the topological sorter to break that edge when it is the only reason for a cycle.
It also preserves required ordering for cases where the principal key itself is store-generated.
For the reproduction above:
from the unconstrained EntityA.EntityBId relationship could be broken.
The real:
dependency from EntityB.RootId remains non-breakable and retains its batching boundary because EntityA.Id is store-generated.
The resulting operation is therefore:
EntityA
↓ retrieve IDENTITY
EntityB
which matches the actual database constraints.
EfCore11UnconstrainedCircularRepro.zip
Related issues
This seems closely related to the EF Core 11 implementation of unconstrained foreign-key relationships:
There are older issues involving circular relationships, such as #29183, but those predate IsConstrained(false) and involve relationships where both sides are treated as constrained.
This may look similar to #1699 / #29183, but this case is fundamentally different. Those issues contain a real circular database dependency and require cycle breaking by inserting a nullable FK as null and updating it afterwards.
In this case, one relationship is explicitly configured with IsConstraint(false), so there is no corresponding database FK and therefore no circular database dependency to break. A valid command ordering exists naturally: insert A, retrieve its generated identity, then insert B.
The question is whether a relationship configured with IsConstraint(false) should participate in CommandBatchPreparer dependency ordering at all.
I could not find an existing issue covering the specific case where an IsConstrained(false) relationship creates an otherwise artificial SaveChanges cycle.
Your code
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Logging;
const string defaultConnectionString =
@"Server=(localdb)\mssqllocaldb;Database=EfCore11UnconstrainedCircularRepro;Trusted_Connection=True;TrustServerCertificate=True";
var connectionString = Environment.GetEnvironmentVariable("EF_REPRO_CONNECTION")
?? defaultConnectionString;
Console.WriteLine("EF Core 11 IsConstrained(false) circular SaveChanges repro");
Console.WriteLine("----------------------------------------------------------");
Console.WriteLine($"Connection: {connectionString}");
Console.WriteLine();
Console.WriteLine("WARNING: The repro deletes and recreates the configured database.");
Console.WriteLine();
await using var db = new ReproContext(connectionString);
await db.Database.EnsureDeletedAsync();
await db.Database.EnsureCreatedAsync();
var entityBId = Guid.NewGuid();
var a = new EntityA
{
// A.Id is SQL Server IDENTITY and is therefore unknown until A is inserted.
// B.Id, however, is already known now.
EntityBId = entityBId
};
var b = new EntityB
{
Id = entityBId,
// Real constrained relationship. EF must insert A first, obtain its IDENTITY
// value, propagate it into B.RootId and then insert B.
Root = a
};
// Unconstrained relationship. This is intentionally *not* backed by a database FK.
// Since B.Id is already known, SQL Server can legally insert A before B.
a.EntityB = b;
db.AddRange(a, b);
Console.WriteLine("Tracked graph before SaveChanges:");
Console.WriteLine($" A.Id = {a.Id} (store-generated IDENTITY)");
Console.WriteLine($" A.EntityBId = {a.EntityBId}");
Console.WriteLine($" B.Id = {b.Id} (client-generated Guid)");
Console.WriteLine($" B.RootId = {b.RootId} (must receive A.Id)");
Console.WriteLine();
Console.WriteLine("Expected valid database ordering:");
Console.WriteLine(" 1. INSERT A with the already-known B Guid (no DB FK constraint A -> B)");
Console.WriteLine(" 2. Read generated A.Id");
Console.WriteLine(" 3. INSERT B with RootId = A.Id (real DB FK B -> A)");
Console.WriteLine();
try
{
await db.SaveChangesAsync();
Console.ForegroundColor = ConsoleColor.Green;
Console.WriteLine("SaveChanges SUCCEEDED.");
Console.ResetColor();
Console.WriteLine();
Console.WriteLine("By running an EF Core version containing a fix for the issue, this is expected.");
Console.WriteLine($"Generated A.Id: {a.Id}");
Environment.ExitCode = 0;
}
catch (Exception ex)
{
Console.ForegroundColor = ConsoleColor.Red;
Console.WriteLine("SaveChanges FAILED.");
Console.ResetColor();
Console.WriteLine();
Console.WriteLine(ex);
Console.WriteLine();
Console.WriteLine("This reproduces the issue if the exception reports a circular dependency between A and B.");
Environment.ExitCode = 1;
}
public sealed class ReproContext(string connectionString) : DbContext
{
public DbSet<EntityA> EntitiesA => Set<EntityA>();
public DbSet<EntityB> EntitiesB => Set<EntityB>();
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
=> optionsBuilder
.UseSqlServer(connectionString)
.EnableSensitiveDataLogging()
.EnableDetailedErrors()
.LogTo(Console.WriteLine, LogLevel.Information);
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<EntityA>(builder =>
{
builder.ToTable("EntityA");
builder.HasKey(x => x.Id);
// SQL Server IDENTITY int PK.
builder.Property(x => x.Id)
.UseIdentityColumn();
// B.Id is client-generated and is known before SaveChanges.
// This EF navigation is intentionally NOT backed by a database FK constraint.
builder.HasOne(x => x.EntityB)
.WithMany()
.HasForeignKey(x => x.EntityBId)
.OnDelete(DeleteBehavior.NoAction)
.IsConstrained(false);
});
modelBuilder.Entity<EntityB>(builder =>
{
builder.ToTable("EntityB");
builder.HasKey(x => x.Id);
builder.Property(x => x.Id)
.ValueGeneratedNever();
// This is a real database FK. A must exist before B can be inserted.
builder.HasOne(x => x.Root)
.WithMany()
.HasForeignKey(x => x.RootId)
.OnDelete(DeleteBehavior.NoAction);
});
}
}
public sealed class EntityA
{
public int Id { get; set; }
public Guid EntityBId { get; set; }
public EntityB EntityB { get; set; } = null!;
}
public sealed class EntityB
{
public Guid Id { get; set; }
public int RootId { get; set; } // Referring back to A with real database FK constraint.
public EntityA Root { get; set; } = null!;
}
Stack traces
Verbose output
EF Core version
11.0.0-rc.1.26425.128
Database provider
Microsoft.EntityFrameworkCore.SqlServer
Target framework
v11.0.100-rc.1
Operating system
Windows 11
IDE
Visual Studio 2026 18.10.0
Bug description
Summary
In EF Core 11, a relationship configured with
IsConstrained(false)still participates as a non-breakable dependency in theSaveChangesmodification-command graph.This can cause
SaveChanges()to throw a circular dependency exception even though there is no corresponding database foreign key constraint and the database can persist the graph safely.A common example is:
IDENTITY intprimary key.Guidprimary key.BId, which referencesB.Idthrough an unconstrained EF relationship (IsConstrained(false)).RootId, which is a normal constrained FK referencingA.Id.The database-valid insert order is:
BIdGUID. Since this relationship is unconstrained, B does not need to exist yet.IDENTITYvalue.RootId.This should be possible in a single
SaveChanges()call. EF may of course use a separate internal batch to retrieve and propagate the store-generated identity value.Currently EF detects:
and reports a circular dependency before executing the valid insert sequence.
Minimal reproduction
Using:
Example:
Reproduction:
SaveChangesAsync()currently fails because EF detects a circular dependency.Expected behavior
SaveChanges()should succeed.The required ordering is unambiguous:
There is no database constraint requiring EntityB to exist before EntityA is inserted because the
EntityA.EntityBId -> EntityB.Idrelationship is configured with:The normal constrained relationship:
must still participate in command ordering and, because
EntityA.Idis store-generated, must still create the appropriate batching boundary.Actual behavior
The unconstrained relationship participates in the modification-command dependency graph in the same way as a constrained relationship.
This creates:
and
CommandBatchPreparer.TopologicalSort()reports a circular dependency.Why I believe this is specific to
IsConstrained(false)IsConstrained(false)explicitly means that the EF relationship is not backed by a database foreign key constraint.The EF Core 11 implementation already takes this into account for:
However, command ordering still appears to treat the model-level foreign key as a mandatory store-ordering dependency.
CommandBatchPreparercurrently handles model foreign keys without a mapped relational constraint inGetForeignKeyValues()/AddForeignKeyEdges()and eventually adds the edge using:The dependency therefore isn't breakable even when:
and no store-generated value needs to be propagated across that particular relationship.
Possible implementation direction
Would it make sense for an unconstrained FK dependency to be breakable when it is not required for store-generated value propagation?
Conceptually something along the lines of:
This would preserve the current useful ordering preference for unconstrained relationships, but would allow the topological sorter to break that edge when it is the only reason for a cycle.
It also preserves required ordering for cases where the principal key itself is store-generated.
For the reproduction above:
from the unconstrained
EntityA.EntityBIdrelationship could be broken.The real:
dependency from
EntityB.RootIdremains non-breakable and retains its batching boundary becauseEntityA.Idis store-generated.The resulting operation is therefore:
which matches the actual database constraints.
EfCore11UnconstrainedCircularRepro.zip
Related issues
This seems closely related to the EF Core 11 implementation of unconstrained foreign-key relationships:
IsConstrainedThere are older issues involving circular relationships, such as #29183, but those predate
IsConstrained(false)and involve relationships where both sides are treated as constrained.This may look similar to #1699 / #29183, but this case is fundamentally different. Those issues contain a real circular database dependency and require cycle breaking by inserting a nullable FK as null and updating it afterwards.
In this case, one relationship is explicitly configured with IsConstraint(false), so there is no corresponding database FK and therefore no circular database dependency to break. A valid command ordering exists naturally: insert A, retrieve its generated identity, then insert B.
The question is whether a relationship configured with IsConstraint(false) should participate in CommandBatchPreparer dependency ordering at all.
I could not find an existing issue covering the specific case where an
IsConstrained(false)relationship creates an otherwise artificialSaveChangescycle.Your code
Stack traces
Verbose output
EF Core version
11.0.0-rc.1.26425.128
Database provider
Microsoft.EntityFrameworkCore.SqlServer
Target framework
v11.0.100-rc.1
Operating system
Windows 11
IDE
Visual Studio 2026 18.10.0