Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Exception while using unique clustering key #35588

Open
Morasiu opened this issue Feb 5, 2025 · 1 comment
Open

Exception while using unique clustering key #35588

Morasiu opened this issue Feb 5, 2025 · 1 comment

Comments

@Morasiu
Copy link

Morasiu commented Feb 5, 2025

Bug description

While saving collection, which has separate Clustering key and a composite made with Guid and enum I get a circular dependency exception.

Saving is simple and look like this:

testFromDb.Categories = new List<TestCategory>()
{
    new TestCategory()
    {
        Category = Category.Basic,
    },
    new TestCategory
    {
        Category = Category.Pro,
    }
};

dbContext.SaveChanges(); // Exception here

Tips which my team discovered:

  • if Category is type of string it works (not sure about it)
  • if modelBuilder.Entity<TestCategory>().HasIndex(x => x.ClusteringKey).IsUnique().IsClustered(); is done WITHOUT .IsUnique() it works
  • only fails if there is more than one category updated

Here is a full repo with docker-compose file, migrations and data seeding for you to easily debug this error :)

EFCoreCompositeKeyClustering.zip

Your code

using Microsoft.EntityFrameworkCore;

// Use `docker compose up -d` to run the SQL Server container

var dbContext = new ApplicationDbContext();
dbContext.Database.EnsureCreated();

// BUG BELOW

var testFromDb = dbContext.Tests.Include(x => x.Categories).First();
testFromDb.Description = "New description";
testFromDb.Categories.Clear();
testFromDb.Categories = new List<TestCategory>()
{
    new TestCategory()
    {
        Category = Category.Basic,
    },
    new TestCategory
    {
        Category = Category.Pro,
    }
};

dbContext.SaveChanges(); // Exception here

Console.WriteLine("IT WORKS!");
// BUG END (Not the village from Hobbit)

public class Test
{
    public Guid Id { get; set; }
    public string Description { get; set; }
    public int ClusteringKey { get; }
    public ICollection<TestCategory> Categories { get; set; } = [];
}

public class TestCategory
{
    public Guid TestId { get; set; }
    public Category Category { get; set; }
    public int ClusteringKey { get; }
}

public enum Category
{
    Basic,
    Pro,
    SuperPro
}

class ApplicationDbContext : DbContext
{
    protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
    {
        optionsBuilder
            .UseSqlServer("Server=tcp:127.0.0.1,1433;Initial Catalog=TestDb;Persist Security Info=False;User ID=sa;Password=P@ssword1234;MultipleActiveResultSets=False;Encrypt=True;TrustServerCertificate=True;Connection Timeout=30;");
    }

    public DbSet<Test> Tests => Set<Test>();
    public DbSet<TestCategory> TestCategories => Set<TestCategory>();

    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        modelBuilder.Entity<Test>()
            .HasKey(x => x.Id)
            .IsClustered(false);
         modelBuilder.Entity<Test>()
             .Property(x => x.ClusteringKey)
             .ValueGeneratedOnAdd()
             .UseIdentityColumn(1_000_000);
         modelBuilder.Entity<Test>()
             .HasIndex(x => x.ClusteringKey).IsUnique().IsClustered();
        modelBuilder.Entity<TestCategory>()
            .HasKey(x => new { x.TestId, x.Category })
            .IsClustered(false);
        modelBuilder.Entity<TestCategory>()
            .HasOne<Test>()
            .WithMany(x => x.Categories)
            .HasForeignKey(x => x.TestId)
            .OnDelete(DeleteBehavior.Cascade);
        modelBuilder.Entity<TestCategory>()
            .Property(x => x.ClusteringKey)
            .ValueGeneratedOnAdd()
            .UseIdentityColumn(1_000_000);
        modelBuilder.Entity<TestCategory>()
            .HasIndex(x => x.ClusteringKey).IsUnique().IsClustered(); // Works without IsUnique
        
        // SEED FOR TESTING
        var testGuid = Guid.NewGuid();
        modelBuilder.Entity<Test>()
            .HasData(new Test
            {
                Id = testGuid,
                Description = "Test",
            });
        modelBuilder.Entity<TestCategory>()
            .HasData(
            [
                new TestCategory
                {
                    Category = Category.Basic,
                    TestId = testGuid,
                },
                new TestCategory
                {
                    Category = Category.Pro,
                    TestId = testGuid,
                },
                new TestCategory
                {
                    Category = Category.SuperPro,
                    TestId = testGuid,
                }
            ]);
    }
}

Stack traces

Unhandled exception. System.InvalidOperationException: Unable to save changes because a circular dependency was detected in the data to be saved: 'TestCategory [Added] <-
Index { 'ClusteringKey' } TestCategory [Added] <-
Index { 'ClusteringKey' } TestCategory [Added]To show additional information call 'DbContextOptionsBuilder.EnableSensitiveDataLogging'.'.
   at Microsoft.EntityFrameworkCore.Utilities.Multigraph`2.ThrowCycle(List`1 cycle, Func`2 formatCycle, Func`2 formatException)
   at Microsoft.EntityFrameworkCore.Utilities.Multigraph`2.TopologicalSortCore(Boolean withBatching, Func`4 canBreakEdges, Func`2 formatCycle, Func`2 formatException)
   at Microsoft.EntityFrameworkCore.Utilities.Multigraph`2.BatchingTopologicalSort(Func`4 canBreakEdges, Func`2 formatCycle, Func`2 formatException)
   at Microsoft.EntityFrameworkCore.Update.Internal.CommandBatchPreparer.TopologicalSort(IEnumerable`1 commands)
   at Microsoft.EntityFrameworkCore.Update.Internal.CommandBatchPreparer.BatchCommands(IList`1 entries, IUpdateAdapter updateAdapter)+MoveNext()
   at Microsoft.EntityFrameworkCore.Update.Internal.BatchExecutor.Execute(IEnumerable`1 commandBatches, IRelationalConnection connection)
   at Microsoft.EntityFrameworkCore.Storage.RelationalDatabase.SaveChanges(IList`1 entries)
   at Microsoft.EntityFrameworkCore.ChangeTracking.Internal.StateManager.SaveChanges(IList`1 entriesToSave)
   at Microsoft.EntityFrameworkCore.ChangeTracking.Internal.StateManager.SaveChanges(StateManager stateManager, Boolean acceptAllChangesOnSuccess)
   at Microsoft.EntityFrameworkCore.ChangeTracking.Internal.StateManager.<>c.<SaveChanges>b__112_0(DbContext _, ValueTuple`2 t)
   at Microsoft.EntityFrameworkCore.SqlServer.Storage.Internal.SqlServerExecutionStrategy.Execute[TState,TResult](TState state, Func`3 operation, Func`3 verifySucceeded)
   at Microsoft.EntityFrameworkCore.ChangeTracking.Internal.StateManager.SaveChanges(Boolean acceptAllChangesOnSuccess)
   at Microsoft.EntityFrameworkCore.DbContext.SaveChanges(Boolean acceptAllChangesOnSuccess)
   at Microsoft.EntityFrameworkCore.DbContext.SaveChanges()
   at Program.<Main>$(String[] args) in D:\Projekty\EFCoreCompositeKeyClustering\Program.cs:line 25

Verbose output


EF Core version

8.0.6

Database provider

Microsoft.EntityFrameworkCore.SqlServer

Target framework

.Net 8.0

Operating system

Windows 11

IDE

Rider 2024.3.4

@cincuranet
Copy link
Contributor

Note for the team: Repros on 9.0.1 as well.

@AndriySvyryd AndriySvyryd added this to the 10.0.0 milestone Feb 5, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

4 participants