In certain cases, configuring enum lookups on an owned type (via the new ConfigureOwnedEnumLookup extension method) causes EF Core to generate an enum lookup table with a redundant foreign key reference to itself. This results in a warning appearing within the Package Manager Console when creating migrations (given an enum called Month):
Microsoft.EntityFrameworkCore.Model.Validation[10614]
The foreign key {'Id'} on entity type 'EnumWithNumberLookup<Month> targets itself, it should be removed since it serves no purpose.
This is a minor issue and it's really just to bring it to your attention in case you have any idea what might be causing it. I don't believe it will affect behaviour in any way.
Steps To Reproduce
It's difficult to reproduce this issue. I've set up both a dummy .NET Core console app project and my real ASP.NET Core-based project to create the exact same minimal set of entities that exposes the issue. The relevant classes are listed below.
Domain
A "product consultant" (not modelled) can optionally have an "annual target". An "annual target" owns zero or many "monthly targets", each of which correspond to a month of the year.
publicclassAnnualTarget{protectedAnnualTarget(){// Required by EF}publicintYearStartPeriod{get;privateset;}publicintTarget=>MonthlyTargets.Sum(x =>x.Target);protectedvirtualIReadOnlyCollection<MonthlyTarget>MonthlyTargets{get;privateset;}=null!;privateintProductConsultantId{get;set;}}publicclassMonthlyTarget{protectedMonthlyTarget(){// Required by EF}publicMonthlyTarget(intyearStartPeriod,Monthmonth,inttarget){YearStartPeriod=yearStartPeriod;Month=month;Target=target;}publicMonthMonth{get;privateset;}publicintYearStartPeriod{get;privateset;}publicintTarget{get;privateset;}publicintAdjustment{get;privateset;}privateintProductConsultantId{get;set;}}publicenumMonth{January=1,February=2,March=3,April=4,May=5,June=6,July=7,August=8,September=9,October=10,November=11,December=12}Context
publicclassAnnualTargetConfiguration:IEntityTypeConfiguration<AnnualTarget>{privatereadonlyModelBuilder_modelBuilder;privatereadonlyEnumLookupOptions_enumLookupOptions;publicAnnualTargetConfiguration(ModelBuildermodelBuilder,EnumLookupOptionsenumLookupOptions){_modelBuilder=modelBuilder;_enumLookupOptions=enumLookupOptions;}publicvoidConfigure(EntityTypeBuilder<AnnualTarget>builder){builder.ToTable(nameof(AnnualTarget));builder.HasKey("ProductConsultantId",nameof(AnnualTarget.YearStartPeriod));builder.OwnsMany<MonthlyTarget>("MonthlyTargets", b =>{b.ToTable(nameof(MonthlyTarget));b.WithOwner();b.HasKey("ProductConsultantId",nameof(MonthlyTarget.YearStartPeriod),nameof(MonthlyTarget.Month));b.ConfigureOwnedEnumLookup(_enumLookupOptions,_modelBuilder);});}}publicclassCommissionsContext:DbContext{protectedoverridevoidOnConfiguring(DbContextOptionsBuilderoptionsBuilder){base.OnConfiguring(optionsBuilder);if(!optionsBuilder.IsConfigured){optionsBuilder.UseSqlServer("Data Source=tcp:dev.db.sales.mycompany.co.uk;Trusted_Connection=Yes;database=Commissions");optionsBuilder.UseLazyLoadingProxies();}}protectedoverridevoidOnModelCreating(ModelBuildermodelBuilder){varenumLookupOptions=EnumLookupOptions.Default.SetNamingScheme(x =>x.Pascalize());modelBuilder.ApplyConfiguration(newAnnualTargetConfiguration(modelBuilder,enumLookupOptions));modelBuilder.ConfigureEnumLookup(enumLookupOptions);}}Outputs
The generated migration file (..._InitialCreate.cs) is identical between the dummy project and the real project:
publicpartialclassInitialCreate:Migration{protectedoverridevoidUp(MigrationBuildermigrationBuilder){migrationBuilder.CreateTable(name:"AnnualTarget",columns: table =>new{YearStartPeriod=table.Column<int>(nullable:false),ProductConsultantId=table.Column<int>(nullable:false)},constraints: table =>{table.PrimaryKey("PK_AnnualTarget", x =>new{x.ProductConsultantId,x.YearStartPeriod});});migrationBuilder.CreateTable(name:"Month",columns: table =>new{Id=table.Column<int>(nullable:false),Name=table.Column<string>(nullable:true)},constraints: table =>{table.PrimaryKey("PK_Month", x =>x.Id);});migrationBuilder.CreateTable(name:"MonthlyTarget",columns: table =>new{Month=table.Column<int>(nullable:false),YearStartPeriod=table.Column<int>(nullable:false),ProductConsultantId=table.Column<int>(nullable:false),Target=table.Column<int>(nullable:false),Adjustment=table.Column<int>(nullable:false)},constraints: table =>{table.PrimaryKey("PK_MonthlyTarget", x =>new{x.ProductConsultantId,x.YearStartPeriod,x.Month});table.ForeignKey(name:"FK_MonthlyTarget_Month_Month",column: x =>x.Month,principalTable:"Month",principalColumn:"Id",onDelete:ReferentialAction.Cascade);table.ForeignKey(name:"FK_MonthlyTarget_AnnualTarget_ProductConsultantId_YearStartPeriod",columns: x =>new{x.ProductConsultantId,x.YearStartPeriod},principalTable:"AnnualTarget",principalColumns:new[]{"ProductConsultantId","YearStartPeriod"},onDelete:ReferentialAction.Cascade);});migrationBuilder.InsertData(table:"Month",columns:new[]{"Id","Name"},values:newobject[,]{{1,"January"},{2,"February"},{3,"March"},{4,"April"},{5,"May"},{6,"June"},{7,"July"},{8,"August"},{9,"September"},{10,"October"},{11,"November"},{12,"December"}});migrationBuilder.CreateIndex(name:"IX_Month_Name",table:"Month",column:"Name",unique:true,filter:"[Name] IS NOT NULL");migrationBuilder.CreateIndex(name:"IX_MonthlyTarget_Month",table:"MonthlyTarget",column:"Month");}protectedoverridevoidDown(MigrationBuildermigrationBuilder){migrationBuilder.DropTable(name:"MonthlyTarget");migrationBuilder.DropTable(name:"Month");migrationBuilder.DropTable(name:"AnnualTarget");}}However, the generated designer scripts differ between the two projects.
Dummy .NET Console Project (working as intended)
// <auto-generated />usingMicrosoft.EntityFrameworkCore;usingMicrosoft.EntityFrameworkCore.Infrastructure;usingMicrosoft.EntityFrameworkCore.Metadata;usingMicrosoft.EntityFrameworkCore.Migrations;usingMicrosoft.EntityFrameworkCore.Storage.ValueConversion;usingexperiment.Data;namespaceexperiment.Migrations{[DbContext(typeof(CommissionsContext))][Migration("20200917121204_InitialCreate")]partialclassInitialCreate{protectedoverridevoidBuildTargetModel(ModelBuildermodelBuilder){
#pragma warning disable 612,618modelBuilder.HasAnnotation("ProductVersion","3.1.8").HasAnnotation("Relational:MaxIdentifierLength",128).HasAnnotation("SqlServer:ValueGenerationStrategy",SqlServerValueGenerationStrategy.IdentityColumn);modelBuilder.Entity("SpatialFocus.EntityFrameworkCore.Extensions.EnumWithNumberLookup<experiment.Models.Month>", b =>{b.Property<int>("Id").HasColumnType("int");b.Property<string>("Name").HasColumnType("nvarchar(450)");b.HasKey("Id");b.HasIndex("Name").IsUnique().HasFilter("[Name] IS NOT NULL");b.ToTable("Month");b.HasData(new{Id=1,Name="January"},new{Id=2,Name="February"},new{Id=3,Name="March"},new{Id=4,Name="April"},new{Id=5,Name="May"},new{Id=6,Name="June"},new{Id=7,Name="July"},new{Id=8,Name="August"},new{Id=9,Name="September"},new{Id=10,Name="October"},new{Id=11,Name="November"},new{Id=12,Name="December"});});modelBuilder.Entity("experiment.Models.Targets.AnnualTarget", b =>{b.Property<int>("ProductConsultantId").HasColumnType("int");b.Property<int>("YearStartPeriod").HasColumnType("int");b.HasKey("ProductConsultantId","YearStartPeriod");b.ToTable("AnnualTarget");});modelBuilder.Entity("SpatialFocus.EntityFrameworkCore.Extensions.EnumWithNumberLookup<experiment.Models.Month>", b =>{b.HasOne("SpatialFocus.EntityFrameworkCore.Extensions.EnumWithNumberLookup<experiment.Models.Month>",null).WithMany().HasForeignKey("Id").OnDelete(DeleteBehavior.Cascade).IsRequired();});modelBuilder.Entity("experiment.Models.Targets.AnnualTarget", b =>{b.OwnsMany("experiment.Models.Targets.MonthlyTarget","MonthlyTargets", b1 =>{b1.Property<int>("ProductConsultantId").HasColumnType("int");b1.Property<int>("YearStartPeriod").HasColumnType("int");b1.Property<int>("Month").HasColumnType("int");b1.Property<int>("Adjustment").HasColumnType("int");b1.Property<int>("Target").HasColumnType("int");b1.HasKey("ProductConsultantId","YearStartPeriod","Month");b1.HasIndex("Month");b1.ToTable("MonthlyTarget");b1.HasOne("SpatialFocus.EntityFrameworkCore.Extensions.EnumWithNumberLookup<experiment.Models.Month>",null).WithMany().HasForeignKey("Month").OnDelete(DeleteBehavior.Cascade).IsRequired();b1.WithOwner().HasForeignKey("ProductConsultantId","YearStartPeriod");});});
#pragma warning restore 612,618}}}Real ASP.NET Core Project (has issue)
// <auto-generated />usingMyCompany.Commissions.Infrastructure.Data;usingMicrosoft.EntityFrameworkCore;usingMicrosoft.EntityFrameworkCore.Infrastructure;usingMicrosoft.EntityFrameworkCore.Metadata;usingMicrosoft.EntityFrameworkCore.Migrations;usingMicrosoft.EntityFrameworkCore.Storage.ValueConversion;namespaceMyCompany.Commissions.Infrastructure.Migrations{[DbContext(typeof(CommissionsContext))][Migration("20200917115338_InitialCreate")]partialclassInitialCreate{protectedoverridevoidBuildTargetModel(ModelBuildermodelBuilder){
#pragma warning disable 612,618modelBuilder.HasAnnotation("ProductVersion","3.1.8").HasAnnotation("Relational:MaxIdentifierLength",128).HasAnnotation("SqlServer:ValueGenerationStrategy",SqlServerValueGenerationStrategy.IdentityColumn);modelBuilder.Entity("MyCompany.Commissions.Domain.ProductConsultants.Targets.AnnualTarget", b =>{b.Property<int>("ProductConsultantId").HasColumnType("int");b.Property<int>("YearStartPeriod").HasColumnType("int");b.HasKey("ProductConsultantId","YearStartPeriod");b.ToTable("AnnualTarget");});modelBuilder.Entity("SpatialFocus.EntityFrameworkCore.Extensions.EnumWithNumberLookup<MyCompany.Commissions.Domain.ProductConsultants.Month>", b =>{b.Property<int>("Id").HasColumnType("int");b.Property<string>("Name").HasColumnType("nvarchar(450)");b.HasKey("Id");b.HasIndex("Name").IsUnique().HasFilter("[Name] IS NOT NULL");b.ToTable("Month");b.HasData(new{Id=1,Name="January"},new{Id=2,Name="February"},new{Id=3,Name="March"},new{Id=4,Name="April"},new{Id=5,Name="May"},new{Id=6,Name="June"},new{Id=7,Name="July"},new{Id=8,Name="August"},new{Id=9,Name="September"},new{Id=10,Name="October"},new{Id=11,Name="November"},new{Id=12,Name="December"});});modelBuilder.Entity("MyCompany.Commissions.Domain.ProductConsultants.Targets.AnnualTarget", b =>{b.OwnsMany("MyCompany.Commissions.Domain.ProductConsultants.Targets.MonthlyTarget","MonthlyTargets", b1 =>{b1.Property<int>("ProductConsultantId").HasColumnType("int");b1.Property<int>("YearStartPeriod").HasColumnType("int");b1.Property<int>("Month").HasColumnType("int");b1.Property<int>("Adjustment").HasColumnType("int");b1.Property<int>("Target").HasColumnType("int");b1.HasKey("ProductConsultantId","YearStartPeriod","Month");b1.HasIndex("Month");b1.ToTable("MonthlyTarget");b1.HasOne("SpatialFocus.EntityFrameworkCore.Extensions.EnumWithNumberLookup<MyCompany.Commissions.Domain.ProductConsultants.Month>",null).WithMany().HasForeignKey("Month").OnDelete(DeleteBehavior.Cascade).IsRequired();b1.WithOwner().HasForeignKey("ProductConsultantId","YearStartPeriod");});});modelBuilder.Entity("SpatialFocus.EntityFrameworkCore.Extensions.EnumWithNumberLookup<MyCompany.Commissions.Domain.ProductConsultants.Month>", b =>{b.HasOne("SpatialFocus.EntityFrameworkCore.Extensions.EnumWithNumberLookup<MyCompany.Commissions.Domain.ProductConsultants.Month>",null).WithMany().HasForeignKey("Id").OnDelete(DeleteBehavior.Cascade).IsRequired();});
#pragma warning restore 612,618}}}The problem lies at the end of the designer file for the ASP.NET Core project:
modelBuilder.Entity("SpatialFocus.EntityFrameworkCore.Extensions.EnumWithNumberLookup<MyCompany.Commissions.Domain.ProductConsultants.Month>", b =>{b.HasOne("SpatialFocus.EntityFrameworkCore.Extensions.EnumWithNumberLookup<MyCompany.Commissions.Domain.ProductConsultants.Month>",null).WithMany().HasForeignKey("Id").OnDelete(DeleteBehavior.Cascade).IsRequired();});
In certain cases, configuring enum lookups on an owned type (via the new
ConfigureOwnedEnumLookupextension method) causes EF Core to generate an enum lookup table with a redundant foreign key reference to itself. This results in a warning appearing within the Package Manager Console when creating migrations (given an enum calledMonth):This is a minor issue and it's really just to bring it to your attention in case you have any idea what might be causing it. I don't believe it will affect behaviour in any way.
Steps To Reproduce
It's difficult to reproduce this issue. I've set up both a dummy .NET Core console app project and my real ASP.NET Core-based project to create the exact same minimal set of entities that exposes the issue. The relevant classes are listed below.
Domain
A "product consultant" (not modelled) can optionally have an "annual target". An "annual target" owns zero or many "monthly targets", each of which correspond to a month of the year.
Context
Outputs
The generated migration file (
..._InitialCreate.cs) is identical between the dummy project and the real project:However, the generated designer scripts differ between the two projects.
Dummy .NET Console Project (working as intended)
Real ASP.NET Core Project (has issue)
The problem lies at the end of the designer file for the ASP.NET Core project: