I'm using EF Core and implementing inheritance in a table-per-hierarchy (TPH) approach. I've found that once I added the hierarchy objects to my data model the migration is adding a foreign key for the base table and what appears to be a duplicate that the descendant classes are using.
Relationship is from Transaction
(parent) to TransactionItem
(child).
Object hierarchy:
- TransactionItem (Base class)
- Attachment
- Check
- Envelope
- Stub
Using Fluent API to define the relationship between Transaction
and TransactionItem
using the TransactionItem.TransactionId
field as the foreign key:
modelBuilder.Entity<TransactionItem>(t =>
{
t.ToTable("TransactionItem");
t.HasKey(a => a.Id);
t.Property(a => a.ConcurrencyStamp)
.IsRequired()
.IsConcurrencyToken();
t.Property(a => a.Created)
.IsRequired();
t.HasOne(a => a.Transaction)
.WithMany(a => a.TransactionItems)
.HasForeignKey(a => a.TransactionId)
.OnDelete(DeleteBehavior.Restrict);
t.HasDiscriminator<string>("ItemType")
.HasValue<Check>("Check")
.HasValue<Stub>("Stub")
.HasValue<Envelope>("Envelope")
.HasValue<Attachment>("Attachment");
});
The migration Up method is creating the base class table (TPH) with TransactionId
and TransactionId2
columns, each relating to the same parent table:
migrationBuilder.CreateTable(
name: "TransactionItem",
columns: table => new
{
Id = table.Column<int>(nullable: false)
.Annotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn),
ConcurrencyStamp = table.Column<string>(nullable: false),
Created = table.Column<DateTimeOffset>(nullable: false),
ItemType = table.Column<string>(nullable: false),
PageName = table.Column<string>(nullable: true),
Sequence = table.Column<int>(nullable: false),
TransactionId = table.Column<int>(nullable: false),
TransactionId2 = table.Column<int>(nullable: true),
CheckType = table.Column<string>(nullable: true),
Micr = table.Column<string>(nullable: true),
MicrValid = table.Column<bool>(nullable: true),
OcrId = table.Column<string>(nullable: true)
},
constraints: table =>
{
table.PrimaryKey("PK_TransactionItem", x => x.Id);
table.ForeignKey(
name: "FK_TransactionItem_Transaction_TransactionId",
column: x => x.TransactionId,
principalTable: "Transaction",
principalColumn: "Id",
onDelete: ReferentialAction.Restrict);
table.ForeignKey(
name: "FK_TransactionItem_Transaction_TransactionId2",
column: x => x.TransactionId2,
principalTable: "Transaction",
principalColumn: "Id",
onDelete: ReferentialAction.Restrict);
});
When I look at the ModelSnapshot.cs code I see the following relationships specific to the objects in the inheritance model establishing a foreign key for the derived class entities using the TransactionId2
column.
modelBuilder.Entity("SampleProject.Data.DataModels.TransactionItem", b =>
{
b.HasOne("SampleProject.Data.DataModels.Transaction", "Transaction")
.WithMany("TransactionItems")
.HasForeignKey("TransactionId");
});
modelBuilder.Entity("SampleProject.Data.DataModels.Attachment", b =>
{
b.HasOne("SampleProject.Data.DataModels.Transaction")
.WithMany("Attachments")
.HasForeignKey("TransactionId2");
});
modelBuilder.Entity("SampleProject.Data.DataModels.Check", b =>
{
b.HasOne("SampleProject.Data.DataModels.Transaction")
.WithMany("Checks")
.HasForeignKey("TransactionId2");
});
modelBuilder.Entity("SampleProject.Data.DataModels.Envelope", b =>
{
b.HasOne("SampleProject.Data.DataModels.Transaction")
.WithMany("Envelopes")
.HasForeignKey("TransactionId2");
});
modelBuilder.Entity("SampleProject.Data.DataModels.Stub", b =>
{
b.HasOne("SampleProject.Data.DataModels.Transaction")
.WithMany("Stubs")
.HasForeignKey("TransactionId2");
});
I'm obviously missing something but can't figure out what it is. I want to be able to use inheritance hierarchy with TPH approach and a single foreign key field from the base class table (TransactionItem
) to the parent (Transaction
). I appreciate any help that you can provide.
Thanks!
As pointed out by Ivan Stoev, I created this problem by having the parent object (
Transaction
in this case) implement read-only convenience properties for lists of my derived class types.Because
Transaction
declared properties like below, the ef migration was creating new relationships/foreign keys for them.I've since implemented these as methods so as not to conflict with the conventions of ef.
Many thanks to Ivan Stoev for prompting this discovery!