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

Circular dependency while creating tables with DatabaseFacade.EnsureCreated on EF Core 6 #26834

Closed
erdalsivri opened this issue Nov 26, 2021 · 3 comments
Labels
area-migrations closed-fixed The issue has been fixed and is/will be included in the release indicated by the issue milestone. customer-reported regression Servicing-approved type-bug
Milestone

Comments

@erdalsivri
Copy link

erdalsivri commented Nov 26, 2021

We use a Database-First approach to generate DbContext from a SqlServer database. However, we also use Model-First in unit tests to generate an in-memory Sqlite database.

Everything works fine on EF Core 5 but we've been struggling to upgrade to EF Core 6 due to a "circular dependency" error while creating database tables using the EnsureCreated method. Searching on the web for this error usually leads to SaveChanges errors while saving entities with circular references to the database so I couldn't find anything related to database creation.

GenerateCreateScript also causes the same issue. I used this method to test the issue against SqlServer to see if it is related to Sqlite but it doesn't seem to be. The error is from Multigraph.cs while trying to do topological sort. For some reason, it is not able to break cycles in our model and ends up with this error.

This issue does not reproduce on EF Core 5. Simply change 6.0.0 to 5.0.12 in EFCoreTest.csproj shared below to confirm it doesn't fail.

Include your code

Here is a simple project that reproduces the issue:

EFCore6Test.tar.gz

We have more than 200 entities in our model so I tried to simplify it and got a repro involving 7 entities.

Program.cs:

using EFCore6Test;
using Microsoft.Data.Sqlite;
using Microsoft.EntityFrameworkCore;

using var sqliteConnection = new SqliteConnection("Data Source=:memory:");
sqliteConnection.Open();

var dbContextOptions = new DbContextOptionsBuilder()
    .UseSqlite(sqliteConnection)
    .EnableDetailedErrors()
    .EnableSensitiveDataLogging().Options;
using var context = new SampleContext(dbContextOptions);
context.Database.EnsureCreated();

Console.WriteLine("done");

SampleContext.cs:

using Microsoft.EntityFrameworkCore;

namespace EFCore6Test;

public class SampleContext : DbContext
{
    public SampleContext(DbContextOptions options) : base(options)
    {
    }
    
    public DbSet<Book> Books { get; set; }
    public DbSet<Album> Albums { get; set; }
    public DbSet<User> Users { get; set; }
    public DbSet<Group> Groups { get; set; }
    public DbSet<Product> Products { get; set; }
    public DbSet<Team> Teams { get; set; }

    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        modelBuilder.Entity<Book>(entity =>
        {
            entity.HasOne(d => d.Album)
                .WithMany(p => p.Books)
                .HasForeignKey(d => d.AlbumId);
            entity.HasOne(d => d.User)
                .WithMany(p => p.Books)
                .HasForeignKey(d => d.UserId);
            entity.HasOne(d => d.ParentBook)
                .WithMany(p => p.InverseParentBook)
                .HasForeignKey(d => d.ParentBookId);
            entity.HasOne(d => d.PrivateGroup)
                .WithMany(p => p.BookPrivateGroups)
                .HasForeignKey(d => d.PrivateGroupId);
        });
        modelBuilder.Entity<Album>(entity =>
        {
            entity.HasOne(d => d.OwnerUser)
                .WithMany(p => p.AlbumOwnerUsers)
                .HasForeignKey(d => d.OwnerUserId);
            entity.HasOne(d => d.Book)
                .WithMany(p => p.Albums)
                .HasForeignKey(d => d.BookId);
        });
        modelBuilder.Entity<User>(entity =>
        {
            entity.HasOne(d => d.CreatedByProduct)
                .WithMany(p => p.Users)
                .HasForeignKey(d => d.CreatedByProductId);
            entity.HasOne(d => d.Book)
                .WithMany(p => p.Users)
                .HasForeignKey(d => d.BookId);
            entity.HasOne(d => d.ReaderGroup)
                .WithMany(p => p.UserReaderGroups)
                .HasForeignKey(d => d.ReaderGroupId);
        });
        modelBuilder.Entity<Product>(entity =>
        {
            entity.HasOne(d => d.ReadGroup)
                .WithMany(p => p.ProductReadGroups)
                .HasForeignKey(d => d.ReadGroupId);
        });
        modelBuilder.Entity<Group>(entity =>
        {
            entity.HasOne(d => d.OwnerAlbum)
                .WithMany(p => p.Groups)
                .HasForeignKey(d => d.OwnerAlbumId)
                .OnDelete(DeleteBehavior.SetNull);
            entity.HasOne(d => d.OwnerUser)
                .WithMany(p => p.Groups)
                .HasForeignKey(d => d.OwnerUserId);
            entity.HasOne(d => d.OwnerProduct)
                .WithMany(p => p.Groups)
                .HasForeignKey(d => d.OwnerProductId);
        });
        modelBuilder.Entity<Team>(entity =>
        {
            entity.HasOne(d => d.Album)
                .WithMany(p => p.Teams)
                .HasForeignKey(d => d.AlbumId);
        });
    }
}

public class Book
{
    public int Id { get; set; }
    public int? ParentBookId { get; set; }
    public int? PrivateGroupId { get; set; }
    public int? AlbumId { get; set; }
    public int? UserId { get; set; }

    public Album Album { get; set; }
    public User User { get; set; }
    public Book ParentBook { get; set; }
    public Group PrivateGroup { get; set; }
    public ICollection<Album> Albums { get; set; }
    public ICollection<User> Users { get; set; }
    public ICollection<Book> InverseParentBook { get; set; }
}

public class Album
{
    public int Id { get; set; }
    public int? BookId { get; set; }
    public int? OwnerUserId { get; set; }

    public User OwnerUser { get; set; }
    public Book Book { get; set; }
    public ICollection<Book> Books { get; set; }
    public ICollection<Group> Groups { get; set; }
    public ICollection<Team> Teams { get; set; }
}

public class User
{
    public int Id { get; set; }
    public int? BookId { get; set; }
    public int? ReaderGroupId { get; set; }
    public int? CreatedByProductId { get; set; }

    public Product CreatedByProduct { get; set; }
    public Book Book { get; set; }
    public Group ReaderGroup { get; set; }
    public ICollection<Album> AlbumOwnerUsers { get; set; }
    public ICollection<Book> Books { get; set; }
    public ICollection<Group> Groups { get; set; }
}

public class Group
{
    public int Id { get; set; }
    public int? OwnerAlbumId { get; set; }
    public int? OwnerUserId { get; set; }
    public int? OwnerProductId { get; set; }

    public Album OwnerAlbum { get; set; }
    public User OwnerUser { get; set; }
    public Product OwnerProduct { get; set; }
    public ICollection<User> UserReaderGroups { get; set; }
    public ICollection<Book> BookPrivateGroups { get; set; }
    public ICollection<Product> ProductReadGroups { get; set; }
}

public class Product
{
    public int Id { get; set; }
    public int? ReadGroupId { get; set; }

    public Group ReadGroup { get; set; }
    public ICollection<User> Users { get; set; }
    public ICollection<Group> Groups { get; set; }
}

public class Team
{
    public int Id { get; set; }
    public int AlbumId { get; set; }
    public Album Album { get; set; }
}

Include stack traces

System.InvalidOperationException: Unable to save changes because a circular dependency was detected in the data to be saved: 'Microsoft.EntityFrameworkCore.Migrations.Operations.CreateTableOperation ->
Microsoft.EntityFrameworkCore.Migrations.Operations.CreateTableOperation ->
Microsoft.EntityFrameworkCore.Migrations.Operations.CreateTableOperation'.
   at Microsoft.EntityFrameworkCore.Utilities.Multigraph`2.ThrowCycle(List`1 cycle, Func`2 formatCycle, Func`2 formatException)
   at Microsoft.EntityFrameworkCore.Utilities.Multigraph`2.TopologicalSort(Func`4 tryBreakEdge, Func`2 formatCycle, Func`2 formatException)
   at Microsoft.EntityFrameworkCore.Utilities.Multigraph`2.TopologicalSort(Func`4 tryBreakEdge)
   at Microsoft.EntityFrameworkCore.Migrations.Internal.MigrationsModelDiffer.Sort(IEnumerable`1 operations, DiffContext diffContext)
   at Microsoft.EntityFrameworkCore.Migrations.Internal.MigrationsModelDiffer.GetDifferences(IRelationalModel source, IRelationalModel target)
   at Microsoft.EntityFrameworkCore.Storage.RelationalDatabaseCreator.GetCreateTablesCommands(MigrationsSqlGenerationOptions options)
   at Microsoft.EntityFrameworkCore.Storage.RelationalDatabaseCreator.CreateTables()
   at Microsoft.EntityFrameworkCore.Storage.RelationalDatabaseCreator.EnsureCreated()
   at Microsoft.EntityFrameworkCore.Infrastructure.DatabaseFacade.EnsureCreated()
   at Program.<Main>$(String[] args) in /git/RiderProjects/EFCore6Test/Program.cs:line 13

Include provider and version information

EF Core version: 6.0.0
Database provider: Sqlite (Microsoft.EntityFrameworkCore.Sqlite 6.0.0)
Target framework: .Net 6.0.100
Operating system: Debian

@ajcvickers
Copy link
Member

@erdalsivri This looks like a duplicate of #26750.

@erdalsivri
Copy link
Author

@erdalsivri This looks like a duplicate of #26750.

I saw that bug I wasn't sure it was the same issue because that happens on SaveChanges due to circular dependency in entities while this one is on EnsureCreated while creating tables. Feel free to mark duplicate if you are sure that the underlying issue is the same

@ajcvickers
Copy link
Member

/cc @AndriySvyryd

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
area-migrations closed-fixed The issue has been fixed and is/will be included in the release indicated by the issue milestone. customer-reported regression Servicing-approved type-bug
Projects
None yet
Development

No branches or pull requests

3 participants