I'm using EF 6.1.3, Code-First workflow and have stumbled upon a mapping issue that I can't seem to get around.
I have got a Campaign
object that has Campaign
(1) : TestRecipient
(M) relationship. I don't really need an object reference back to Campaign
from TestRecipient
so going with CampaignId
only, which is the Foreign Key. TestRecipient
and its sibling SeedRecipient
both derive from abstract Recipient
class and are merely marker classes in the current design at the moment.
Here are the classes (abbreviated):
public class Campaign
{
public Campaign(Guid id, string name)
{
this.Id = id;
this.Name = name;
}
public Guid Id { get; private set; }
public string Name { get; private set; }
public virtual ICollection<TestRecipient> TestRecipients { get; private set; }
public virtual ICollection<SeedRecipient> SeedRecipients { get; private set; }
}
public abstract class Recipient
{
protected Recipient()
{
Id = Guid.NewGuid();
}
protected Recipient(Guid campaignId, string emailAddress) : this()
{
CampaignId = campaignId;
EmailAddress = new EmailAddress(emailAddress);
}
public Guid Id { get; protected set; }
public Guid CampaignId { get; protected set; }
public EmailAddress EmailAddress { get; protected set;}
}
public class TestRecipient : Recipient
{
private TestRecipient(){ }
public TestRecipient(Guid campaignId, string emailAddress)
:base(campaignId, emailAddress) { }
//public Guid CampaignId //If I move this property here then mapping works it out without problems
//{ get; private set; }
}
public class SeedRecipient : Recipient
{
private SeedRecipient() { }
public SeedRecipient(Guid campaignId, string emailAddress)
: base(campaignId, emailAddress) { }
}
public class EmailAddress
{
private EmailAddress() { }
public EmailAddress(string emailAddress) { this.Value = emailAddress; }
public string Value { get; private set;}
}
Campaign
will be mapped to its own table and Recipients (both TestRecipient
and SeedRecipient
) will be mapped to a Recipient
table using TPH strategy. Here are the Entity Configurations:
public class CampaignContext : DbContext
{
public CampaignContext() { }
public CampaignContext(string dbConfigName) : base(dbConfigName) { }
public DbSet<Campaign> Campaigns { get; set; }
public DbSet<Recipient> Recipients { get; set; }
public DbSet<TestRecipient> TestRecipients { get; set; }
public DbSet<SeedRecipient> SeedsRecipients { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
modelBuilder.Configurations.Add(new CampaignConfiguration());
modelBuilder.Entity<Recipient>()
.Map(m => m.ToTable("Recipients"))
.Map<TestRecipient>(m =>
{
m.Requires("Type").HasValue("Test");
})
.Map<SeedRecipient>(m => m.Requires("Type").HasValue("Seed"));
}
}
class CampaignConfiguration : EntityTypeConfiguration<Campaign>
{
public CampaignConfiguration()
{
ToTable("Campaigns");
HasKey(p => p.Id);
Property(p => p.Id).HasColumnOrder(1);
HasMany<TestRecipient>(p => p.TestRecipients).WithRequired().HasForeignKey(p => p.CampaignId);
Property(p => p.Name)
.HasColumnName("Name")
.HasColumnType("nvarchar")
.HasColumnOrder(2)
.HasMaxLength(250)
.IsRequired();
}
}
When I run Add-Migration
command I get the following error:
The foreign key component CampaignId is not a declared property on type TestRecipient. Verify that it has not been explicitly excluded from the model and that it is a valid primitive property.
The classes are part of a Domain model designed based on DDD. I'd imagine we would be introducing even more base classes to encapsulate Ids and audit fields. If I move CampaignId
property to the derived classes it generates the migration as expected.
Why doesn't EF "see" the inherited CampaignId
property? Is this EF's limitation? Please let me know if TPH is possible with this hierarchy and/or what am I doing wrong?