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

Self-referencing many-to-many between TPH subtypes creates cascade cycles #28633

Closed
ajcvickers opened this issue Aug 9, 2022 · 0 comments · Fixed by #28937
Closed

Self-referencing many-to-many between TPH subtypes creates cascade cycles #28633

ajcvickers opened this issue Aug 9, 2022 · 0 comments · Fixed by #28937
Labels
area-model-building closed-fixed The issue has been fixed and is/will be included in the release indicated by the issue milestone. type-bug
Milestone

Comments

@ajcvickers
Copy link
Member

These types create a valid model for SQL Server:

public abstract class Animal
{
    public int Id { get; set; }

    public ICollection<Animal> Humans { get; } = new List<Animal>();
    public ICollection<Animal> Pets { get; } = new List<Animal>();
}

public class Pet : Animal
{
}

public class Human : Animal
{
}

Notice that only one direction is configured as Cascade.

Model:
  EntityType: Animal Abstract
    Properties:
      Id (int) Required PK AfterSave:Throw ValueGenerated.OnAdd
      Discriminator (no field, string) Shadow Required AfterSave:Throw
    Skip navigations:
      Humans (ICollection<Animal>) CollectionAnimal Inverse: Pets
      Pets (ICollection<Animal>) CollectionAnimal Inverse: Humans
    Keys:
      Id PK
  EntityType: AnimalAnimal (Dictionary<string, object>) CLR Type: Dictionary<string, object>
    Properties:
      HumansId (no field, int) Indexer Required PK FK AfterSave:Throw
      PetsId (no field, int) Indexer Required PK FK Index AfterSave:Throw
    Keys:
      HumansId, PetsId PK
    Foreign keys:
      AnimalAnimal (Dictionary<string, object>) {'HumansId'} -> Animal {'Id'} Cascade
      AnimalAnimal (Dictionary<string, object>) {'PetsId'} -> Animal {'Id'} ClientCascade
    Indexes:
      PetsId
  EntityType: Human Base: Animal
  EntityType: Pet Base: Animal

But moving the relationships to subtypes:

public abstract class Animal
{
    public int Id { get; set; }
}

public class Pet : Animal
{
    public ICollection<Human> Humans { get; } = new List<Human>();
}

public class Human : Animal
{
    public ICollection<Pet> Pets { get; } = new List<Pet>();
}

Results in both types being Cascade.

Model:
  EntityType: Animal Abstract
    Properties:
      Id (int) Required PK AfterSave:Throw ValueGenerated.OnAdd
      Discriminator (no field, string) Shadow Required AfterSave:Throw
    Keys:
      Id PK
  EntityType: Human Base: Animal
    Skip navigations:
      Pets (ICollection<Pet>) CollectionPet Inverse: Humans
  EntityType: HumanPet (Dictionary<string, object>) CLR Type: Dictionary<string, object>
    Properties:
      HumansId (no field, int) Indexer Required PK FK AfterSave:Throw
      PetsId (no field, int) Indexer Required PK FK Index AfterSave:Throw
    Keys:
      HumansId, PetsId PK
    Foreign keys:
      HumanPet (Dictionary<string, object>) {'HumansId'} -> Human {'Id'} Cascade
      HumanPet (Dictionary<string, object>) {'PetsId'} -> Pet {'Id'} Cascade
    Indexes:
      PetsId
  EntityType: Pet Base: Animal
    Skip navigations:
      Humans (ICollection<Human>) CollectionHuman Inverse: Pets

Which throws on SQL Server:

Unhandled exception. Microsoft.Data.SqlClient.SqlException (0x80131904): Introducing FOREIGN KEY constraint 'FK_HumanPet_Animals_PetsId' on table 'HumanPet' may cause cycles or multiple cascade paths. Specify ON DELETE NO ACTION or ON UPDATE NO ACTION, or modify other FOREIGN KEY constraints.
Could not create constraint or index. See previous errors.
   at Microsoft.Data.SqlClient.SqlConnection.OnError(SqlException exception, Boolean breakConnection, Action`1 wrapCloseInAction)
   at Microsoft.Data.SqlClient.SqlInternalConnection.OnError(SqlException exception, Boolean breakConnection, Action`1 wrapCloseInAction)
   at Microsoft.Data.SqlClient.TdsParser.ThrowExceptionAndWarning(TdsParserStateObject stateObj, Boolean callerHasConnectionLock, Boolean asyncClose)
   at Microsoft.Data.SqlClient.TdsParser.TryRun(RunBehavior runBehavior, SqlCommand cmdHandler, SqlDataReader dataStream, BulkCopySimpleResultSet bulkCopyHandler, TdsParserStateObject stateObj, Boolean& dataReady)
   at Microsoft.Data.SqlClient.SqlCommand.RunExecuteNonQueryTds(String methodName, Boolean isAsync, Int32 timeout, Boolean asyncWrite)
   at Microsoft.Data.SqlClient.SqlCommand.InternalExecuteNonQuery(TaskCompletionSource`1 completion, Boolean sendToPipe, Int32 timeout, Boolean& usedCache, Boolean asyncWrite, Boolean inRetry, String methodName)
   at Microsoft.Data.SqlClient.SqlCommand.ExecuteNonQuery()
   at Microsoft.EntityFrameworkCore.Storage.RelationalCommand.ExecuteNonQuery(RelationalCommandParameterObject parameterObject)
   at Microsoft.EntityFrameworkCore.Migrations.MigrationCommand.ExecuteNonQuery(IRelationalConnection connection, IReadOnlyDictionary`2 parameterValues)
   at Microsoft.EntityFrameworkCore.Migrations.Internal.MigrationCommandExecutor.ExecuteNonQuery(IEnumerable`1 migrationCommands, IRelationalConnection connection)
   at Microsoft.EntityFrameworkCore.Storage.RelationalDatabaseCreator.CreateTables()
   at Microsoft.EntityFrameworkCore.Storage.RelationalDatabaseCreator.EnsureCreated()
   at Microsoft.EntityFrameworkCore.Infrastructure.DatabaseFacade.EnsureCreated()
   at Program.Main() in C:\local\code\AllTogetherNow\Daily\Daily.cs:line 80
ClientConnectionId:a095e670-5eed-42ba-bdef-57d7e30a170d
Error Number:1785,State:0,Class:16

Full code:

public static class Your
{
    public static string ConnectionString = @"Data Source=(LocalDb)\MSSQLLocalDB;Database=Test";
}

public abstract class Animal
{
    public int Id { get; set; }
}

public class Pet : Animal
{
    public ICollection<Human> Humans { get; } = new List<Human>();
}

public class Human : Animal
{
    public ICollection<Pet> Pets { get; } = new List<Pet>();
}

public class SomeDbContext : DbContext
{
    protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
        => optionsBuilder
            //.UseNpgsql("Server=127.0.0.1;Port=5432;Database=myDataBase;User Id=myUsername;Password=myPassword;")
            //.UseSqlite("Data Source=test.db")
            //.UseInMemoryDatabase("Test")
            // .UseLazyLoadingProxies()
            .UseSqlServer(Your.ConnectionString)
            .LogTo(Console.WriteLine, LogLevel.Information)
            .EnableSensitiveDataLogging();

    public DbSet<Animal> Animals => Set<Animal>();
    public DbSet<Human> Humans => Set<Human>();
    public DbSet<Pet> Pets => Set<Pet>();
}

public class Program
{
    public static void Main()
    {
        using (var context = new SomeDbContext())
        {
            Console.WriteLine(context.Model.ToDebugString());
            context.Database.EnsureDeleted();
            context.Database.EnsureCreated();
        }
    }
}
@ajcvickers ajcvickers added this to the 7.0.0 milestone Aug 11, 2022
@ajcvickers ajcvickers added the closed-fixed The issue has been fixed and is/will be included in the release indicated by the issue milestone. label Aug 31, 2022
@ajcvickers ajcvickers modified the milestones: 7.0.0, 7.0.0-rc2 Sep 9, 2022
@ajcvickers ajcvickers modified the milestones: 7.0.0-rc2, 7.0.0 Nov 5, 2022
@ajcvickers ajcvickers removed their assignment Aug 31, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
area-model-building closed-fixed The issue has been fixed and is/will be included in the release indicated by the issue milestone. type-bug
Projects
None yet
Development

Successfully merging a pull request may close this issue.

2 participants