efcore/EFCore.NamingConventions

Invalid name of Index using inheritance and many-to-many

amyboose opened this issue · 3 comments

My code:

using Microsoft.EntityFrameworkCore;

namespace EfCoreTpc;
public class Program
{
    public static async Task Main(params string[] args)
    {
        IHost host = Host.CreateDefaultBuilder()
        .ConfigureServices(services =>
        {
            services.AddDbContext<MyContext>(builder =>
            {
                builder.UseNpgsql("Host=localhost;Port=7435;Database=testdb;Username=admin;Password=testpass")
                    .UseSnakeCaseNamingConvention();
            });
        })
        .Build();
    }
}

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

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

public abstract class CampaignProduct
{
    public int CampaignId { get; set; }
    public int ProductId { get; set; }
    public Campaign Campaign { get; set; } = null!;
    public Product Product { get; set; } = null!;
}

public class CampaignProductBinding : CampaignProduct
{

}

public class CampaignProductUnbinding : CampaignProduct
{

}

public class MyContext : DbContext
{
    public MyContext(DbContextOptions options) : base(options) { }
    public DbSet<Product> Products { get; set; }
    public DbSet<Campaign> Campaigns { get; set; }
    public DbSet<CampaignProduct> CampaignProducts { get; set; }
    public DbSet<CampaignProductBinding> CampaignProductBindings { get; set; }
    public DbSet<CampaignProductUnbinding> CampaignProductUnbindings { get; set; }

    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        modelBuilder.Entity<CampaignProduct>(builder =>
        {
            builder.UseTpcMappingStrategy();

            builder
                .HasKey(p => new { p.CampaignId, p.ProductId });
        });
    }
}

I've got an successful migration without using SnakeCaseNamingConvention.
But using convention I've got an exception:

Microsoft.EntityFrameworkCore.Design.OperationExecutor.OperationBase.Execute(Action action)
  Exception data:
    Severity: ERROR
    SqlState: 42P07
    MessageText: relation "pk_campaign_products" already exists
    File: index.c
    Line: 869
    Routine: index_create
42P07: relation "pk_campaign_products" already exists
PM> 

A migration using SnakeCaseNamingConvention:

migrationBuilder.CreateTable(
name: "campaigns",
columns: table => new
{
	id = table.Column<int>(type: "integer", nullable: false)
		.Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn)
},
constraints: table =>
{
	table.PrimaryKey("pk_campaigns", x => x.id);
});

migrationBuilder.CreateTable(
name: "products",
columns: table => new
{
	id = table.Column<int>(type: "integer", nullable: false)
		.Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn)
},
constraints: table =>
{
	table.PrimaryKey("pk_products", x => x.id);
});

migrationBuilder.CreateTable(
name: "CampaignProductBindings",
columns: table => new
{
	campaignid = table.Column<int>(name: "campaign_id", type: "integer", nullable: false),
	productid = table.Column<int>(name: "product_id", type: "integer", nullable: false)
},
constraints: table =>
{
	table.PrimaryKey("pk_campaign_products", x => new { x.campaignid, x.productid });
	table.ForeignKey(
		name: "fk_campaign_products_campaigns_campaign_id",
		column: x => x.campaignid,
		principalTable: "campaigns",
		principalColumn: "id",
		onDelete: ReferentialAction.Cascade);
	table.ForeignKey(
		name: "fk_campaign_products_products_product_id",
		column: x => x.productid,
		principalTable: "products",
		principalColumn: "id",
		onDelete: ReferentialAction.Cascade);
});

migrationBuilder.CreateTable(
name: "CampaignProductUnbindings",
columns: table => new
{
	campaignid = table.Column<int>(name: "campaign_id", type: "integer", nullable: false),
	productid = table.Column<int>(name: "product_id", type: "integer", nullable: false)
},
constraints: table =>
{
	table.PrimaryKey("pk_campaign_products", x => new { x.campaignid, x.productid });
	table.ForeignKey(
		name: "fk_campaign_products_campaigns_campaign_id",
		column: x => x.campaignid,
		principalTable: "campaigns",
		principalColumn: "id",
		onDelete: ReferentialAction.Cascade);
	table.ForeignKey(
		name: "fk_campaign_products_products_product_id",
		column: x => x.productid,
		principalTable: "products",
		principalColumn: "id",
		onDelete: ReferentialAction.Cascade);
});

migrationBuilder.CreateIndex(
name: "ix_campaign_products_product_id",
table: "CampaignProductBindings",
column: "product_id");

migrationBuilder.CreateIndex(
name: "ix_campaign_products_product_id",
table: "CampaignProductUnbindings",
column: "product_id");

The migration creates 2 indexes with the same name: ix_campaign_products_product_id

For example, indexes without SnakeCaseNamingConvention:

migrationBuilder.CreateIndex(
	name: "IX_CampaignProductBindings_ProductId",
	table: "CampaignProductBindings",
	column: "ProductId");

migrationBuilder.CreateIndex(
	name: "IX_CampaignProductUnbindings_ProductId",
	table: "CampaignProductUnbindings",
	column: "ProductId");

The last example show that names contains full class name.

Microsoft documentation writes:

By convention, indexes created in a relational database are named IX_type name_property name.

Provider and version information

EF Core version: 7.0.2
Database provider: Npgsql Entity Framework Core provider for PostgreSQL 7.0.1
Target framework: NET 7.0
Operating system: Windows 10
IDE: Visual Studio 2022 17.4