dotnet / efcore

EF Core is a modern object-database mapper for .NET. It supports LINQ queries, change tracking, updates, and schema migrations.
https://docs.microsoft.com/ef/
MIT License
13.66k stars 3.15k forks source link

FK & Table naming convention mismatch between EFCore 2.1 & 5 .0 while adding migration #27184

Closed eadaumamaheswarreddy closed 1 year ago

eadaumamaheswarreddy commented 2 years ago

we are upgrading our current EFCore version 2.1 to 5.0.

Below are the entity models in 2.1,

public class Transaction
{
      public int Id
} 
public class ProcessBank : Transaction
{

}
public class ProcessStoredBank : Transaction
{

}
public class BankStatusLog
{
      public int BankTransactionId { get; set; }
      public ProcessBank BankTransaction { get; set; }
}

DBContext :

public class BankContext : DbContext
{
        public DbSet<Transaction> Transactions { get; set; }
}

we have upgraded all references to 5.0 and tried adding migration without modifying existing models. After executing add-migration command, existing snapshot is updated and new migration file is generated. It has statements to drop FKs & Tables, Create/Alter columns, Create/Drop indexes.

Issue 1 : EFCore2.1 FK Name mismatch with EFCore5.0

FKs generation : Model snapshot was originally built using EFCore2.1.4 and below is the key generated EFCore2.1 --> FK Name --> FK_BankStatusLog_Transactions_BankTransactionId [Valid]

Now, we are trying to upgrade to EFCore5.0 by considering existing model snapshot(generated by 2.1.4) and below is the key generated EFCore5.0 --> FK Name --> FK_BankStatusLog_ProcessBank_BankTransactionId [Invalid]

FK name generated by EFCore5 doesn't exist.

New Migration File :

public partial class EFCore5_InitialCreate:Migration
{
         public override void Up()
         {
                  builder.DropForeignKey(name : "FK_BankStatusLog_ProcessBank_BankTransactionId", table : "BankStatucLog")
          }

         public override void Down()
         {
                  builder.AddForeignKey(
                                          name : "FK_BankStatusLog_ProcessBank_BankTransactionId", 
                                          table : "BankStatusLog",
                                          column : "BankTransactionId",
                                          principalTable : "processBank",
                                          principalColumn : "Id",
                                          onDelete : ReferentialAction.Cascade
                  )
          }
}

Issue 2 : Dropping derived entities.

In the above example, it generate statements for dropping processBank & processStoredBank but none of them are tables in DB. we have only Transactions Table.

Can you help us in mitigating these issues in EFCore5?

Include provider and version information

EF Core version: 5.0.13 Database provider: AzureSql Target framework: .NET 6.0 Operating system: Windows 10 IDE: Visual Studio Community 2022

ajcvickers commented 2 years ago

@eadaumamaheswarreddy I am not able to reproduce this. When I generate a schema with the model posted above, it creates a single table for Transactions, and an FK constraint called "FK_BankStatusLog_Transactions_BankTransactionId". Are you sure you have included all relevant mapping attributes and configuration in your repro code?

info: 1/14/2022 16:08:51.241 RelationalEventId.CommandExecuted[20101] (Microsoft.EntityFrameworkCore.Database.Command)
      Executed DbCommand (5ms) [Parameters=[], CommandType='Text', CommandTimeout='30']
      CREATE TABLE [Transactions] (
          [Id] int NOT NULL IDENTITY,
          [Discriminator] nvarchar(max) NOT NULL,
          CONSTRAINT [PK_Transactions] PRIMARY KEY ([Id])
      );
info: 1/14/2022 16:08:51.242 RelationalEventId.CommandExecuted[20101] (Microsoft.EntityFrameworkCore.Database.Command)
      Executed DbCommand (1ms) [Parameters=[], CommandType='Text', CommandTimeout='30']
      CREATE TABLE [BankStatusLog] (
          [Id] int NOT NULL IDENTITY,
          [BankTransactionId] int NOT NULL,
          CONSTRAINT [PK_BankStatusLog] PRIMARY KEY ([Id]),
          CONSTRAINT [FK_BankStatusLog_Transactions_BankTransactionId] FOREIGN KEY ([BankTransactionId]) REFERENCES [Transactions] ([Id]) ON DELETE CASCADE
      );
info: 1/14/2022 16:08:51.243 RelationalEventId.CommandExecuted[20101] (Microsoft.EntityFrameworkCore.Database.Command)
      Executed DbCommand (1ms) [Parameters=[], CommandType='Text', CommandTimeout='30']
      CREATE INDEX [IX_BankStatusLog_BankTransactionId] ON [BankStatusLog] ([BankTransactionId]);

My code:

public class Transaction
{
    public int Id { get; set; }
}

public class ProcessBank : Transaction
{

}

public class ProcessStoredBank : Transaction
{

}

public class BankStatusLog
{
    public int Id { get; set; }
    public int BankTransactionId { get; set; }
    public ProcessBank BankTransaction { get; set; }
}

public class SomeDbContext : DbContext
{
    protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
        => optionsBuilder
            .UseSqlServer(Your.ConnectionString)
            .LogTo(Console.WriteLine, LogLevel.Information)
            .EnableSensitiveDataLogging();

    public DbSet<Transaction> Transactions { get; set; }

    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        modelBuilder.Entity<ProcessBank>();
        modelBuilder.Entity<ProcessStoredBank>();
        modelBuilder.Entity<BankStatusLog>();
    }
}

public class Program
{
    public static void Main()
    {
        using (var context = new SomeDbContext())
        {
            context.Database.EnsureDeleted();
            context.Database.EnsureCreated();
        }
    }
}
eadaumamaheswarreddy commented 2 years ago

@ajcvickers - Everything is fine if we create from the scratch with EFCore5. our case is, we already built the model snapshot using 2.1 and now we are performing an upgradation to 5.

Updated the description in Issue 1 and more info is provided in README file

Please find the repo here

ajcvickers commented 2 years ago

@eadaumamaheswarreddy The model is configured to use TPT mapping because it uses ToTable to map each entity type to a different table:

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder.Entity<EfTable1>(entity =>
    {
        entity.ToTable("EfTable1");

        // ...
    });

    modelBuilder.Entity<EfTable2>(entity =>
    {
        entity.ToTable("EfTable2");

        // ...
    });
}

Using ToTable like this was ignored before EF Core 3.0, which was problematic because of the potential to break like this once TPT mapping was introduced. So we made a breaking change in 3.0 to throw when using ToTable like this. This meant anyone updating from 3.0/3.1 to 5.0 would not be broken by the introduction of TPT, but because you are going directly from 2.1 to 5.0, you are getting a change of mapping from TPH to TPT.

The solution is to stop using ToTable to map different entity types to different tables.