0

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?

Karl
  • 75
  • 2
  • 6
  • You need to delete the proprerty CampaignId in Recipient class. Then uncomment CmapaignId property in TestRecipient. When you define relationship between two classes, EF need the foreign key property to be defined in the child reference and not inherited like you are doing. – CodeNotFound Apr 22 '16 at 17:41
  • @CodeNotFound, Thanks for the comment. I did notice that but it just seems strange to me that the property needs to be physically present on the referenced Type. This means I can't have a truly generalized base class and that feels like a limitation to me. Perhaps there is a good technical ORM reason for this and I would love to be enlightened. – Karl Apr 22 '16 at 17:53
  • this anwser is more clear => http://stackoverflow.com/a/20763305/797882 – CodeNotFound Apr 22 '16 at 17:55
  • @GertArnold, I left it out from the example code but SeedRecipient has the same relationship with Campaign as TestRecipient. Updated the code example to avoid confusion. – Karl Apr 22 '16 at 22:01

0 Answers0