EFCore.NamingConventions: Regression in 7.0.2: TPH table names are not being rewritten

When using TPH, table name is not being rewritten - following example creates tables blogs (correct) and Posts (incorrect). This behavior was correct in version 7.0.0

Repro:

using Microsoft.EntityFrameworkCore;

class Blog
{
    public int Id { get; set; }
    public ICollection<Post> Posts { get; set; }
}

abstract class Post
{
    public int Id { get; set; }
    public Blog Blog { get; set; }
    public int Blog_Id { get; set; }
}
class InternalPost : Post { }
class ExternalPost : Post { }

class TestContext : DbContext
{
    public DbSet<Blog> Blogs { get; set; } = null!;
    public DbSet<Post> Posts { get; set; } = null!;

    protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
    {
        base.OnConfiguring(optionsBuilder);
        optionsBuilder.UseNpgsql("Server=localhost;Database=testefnamingconventions_local;User Id=postgres;Password=root;");
        optionsBuilder.UseLowerCaseNamingConvention();
    }

    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        base.OnModelCreating(modelBuilder);

        modelBuilder.Entity<Blog>().HasMany(x => x.Posts).WithOne(x => x.Blog).HasForeignKey(x => x.Blog_Id);
        modelBuilder.Entity<Post>().HasOne(x => x.Blog).WithMany(x => x.Posts).HasForeignKey(x => x.Blog_Id);
        modelBuilder.Entity<InternalPost>();
        modelBuilder.Entity<ExternalPost>();
    }
}

Generated migration:

using Microsoft.EntityFrameworkCore.Migrations;
using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;

#nullable disable

namespace testefnamingconventions.Migrations
{
    /// <inheritdoc />
    public partial class InitialStructure : Migration
    {
        /// <inheritdoc />
        protected override void Up(MigrationBuilder migrationBuilder)
        {
            migrationBuilder.CreateTable(
                name: "blogs",
                columns: table => new
                {
                    id = table.Column<int>(type: "integer", nullable: false)
                        .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn)
                },
                constraints: table =>
                {
                    table.PrimaryKey("pk_blogs", x => x.id);
                });

            migrationBuilder.CreateTable(
                name: "Posts",
                columns: table => new
                {
                    id = table.Column<int>(type: "integer", nullable: false)
                        .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn),
                    blogid = table.Column<int>(name: "blog_id", type: "integer", nullable: false),
                    discriminator = table.Column<string>(type: "text", nullable: false)
                },
                constraints: table =>
                {
                    table.PrimaryKey("pk_posts", x => x.id);
                    table.ForeignKey(
                        name: "fk_posts_blogs_blogid",
                        column: x => x.blogid,
                        principalTable: "blogs",
                        principalColumn: "id",
                        onDelete: ReferentialAction.Cascade);
                });

            migrationBuilder.CreateIndex(
                name: "ix_posts_blog_id",
                table: "Posts",
                column: "blog_id");
        }

        /// <inheritdoc />
        protected override void Down(MigrationBuilder migrationBuilder)
        {
            migrationBuilder.DropTable(
                name: "Posts");

            migrationBuilder.DropTable(
                name: "blogs");
        }
    }
}

btw: Thank you for quickly solving #179 ❤️

About this issue

  • Original URL
  • State: closed
  • Created a year ago
  • Reactions: 11
  • Comments: 18 (6 by maintainers)

Commits related to this issue

Most upvoted comments

@at-besa I unfortnuately simply haven’t had any time in recent months to work on this plugin… In a few weeks I should be able to set aside a day or two for 8.0 preparation, I’ll try to also do the pending 7.0 work as well (like this PR).

Sorry about this, I’ll take a look as soon as I can.

If you need to update to 7.0.2 now there is simple workaround - manually map affected entities to their tables (see below) as everything else seem to be rewritten properly. It is enough to set the name on your TPH root entities.

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    // https://github.com/efcore/EFCore.NamingConventions/issues/184
    modelBuilder.Entity<User>().ToTable("users");
    modelBuilder.Entity<Grant>().ToTable("grants");
    modelBuilder.Entity<Setting>().ToTable("settings");
}

@xamir82 did you see the really easy workaround above by @Kukkimonsuta ? This issue should not block you from moving to .net8.

Thanks @erwan-joly, I do agree that this is a blocking regression; I may not have time to do considerable work on this plugin, but I do intend to at least fix this before releasing the official 8.0.0.

Once I finish with more things urgent (like releasing Npgsql and EFCore.PG 8.0.0), I’ll take a look here.

@aradalvand I’ll try to find some time for this plugin, but right at the moment there’s simply too much going on.

@roji are you gonna allow that PR before .net 8? 7.0.2 was released in january and this still affects several projects.

@UliPlabst Thank you for pointing that out, I didn’t see it before actually. Good to know.

Update: Lol now #234 is blocking me!

@xamir82 EFCore.PG was released yesterday night - I do intend to fix this regression in the coming weeks and then publish EFCore.NamingConventions 8.0, but you’ll have to be a bit more patient.

@roji not too sure as I did not test it but wouldn’t this be the issue ? If i understand correctly we prevent naming of abstract class to apply. https://github.com/efcore/EFCore.NamingConventions/commit/8ce1fb1bb043bc8d6b360ef11da9efbf74c1ee72#diff-340b0b728e82bffdba8e561a04c249bbe989fd2e0ccfe93ac0906191a70af34aL63

I believe you want to prevent abstract class only in case of TPC if (entityType.GetTableName() is { } tableName && (entityType.GetMappingStrategy() != RelationalAnnotationNames.TpcMappingStrategy || !entityType.ClrType.IsAbstract))