Entity Framework Core TPH with Discriminator in other table

Viewed 56

In a usual TPH implementation, a table has a "Discriminator" column used by EF to decide which class to instantiate. I have a db in which a the discriminator references another table, instead, containing a list of entity types and their relative codes. How to configure the dbContext, so that the discriminator is now a field in the referenced table?

To simplify things, let's build a simple example: a table "Pets" containing a list of pets, implemented in concrete classes "Dog" and "Cat", both derived from "Pet". Class Cat has a field "Mews", indicating the number of mews; class Dog has a field named "Woovs".

Typical implementation:

The Pets table has an identity column "Id" (primary key), a "Discriminator" indicating the type of concrete class, a Mews and a Woovs fields.

The dbContext is instructed to instantiate the proper concrete class like this.

            modelBuilder
                .Entity<Pet>()
                .HasDiscriminator(c => c.Discriminator)
                .HasValue<Cat>("CAT")
                .HasValue<Dog>("DOG")
                ;

My requirements:

Another table exists, "PetTypes", with an identity column "Id" (primary key) and a "Code" column, with unique constraint. The Pets table has no Discriminator column; it has a PetTypeId column, instead, referencing the PetTypes table (foreign key to PetTypes' Id column).

I would like to instruct the modelBuilder to use the PetTypes' Code property as discriminator; something like:

modelBuilder
                .Entity<Pet>()
                .HasDiscriminator(c => c.PetType.Code)
                .HasValue<Cat>("CAT")
                .HasValue<Dog>("DOG")
                ;

or:

modelBuilder
                .Entity<Pet>()
                .HasDiscriminator(c => c.PetType)
                .HasValue<Cat>(new PetType(Code = "CAT"))
                .HasValue<Dog>(new PetType(Code = "DOG"))
                ;

or some other way, so that the Pets class discriminator is agnostic ot the PetTypeId, but only deals with the PetType's code.

Is there any way to do the job?

1 Answers

TPH can be configured just like a standard primary/foreign key relationship. You don't need the Code column, just set your discriminator column as an integer with a foreign key to the PetTypes table. Here's an example:

public abstract class Pet
{
    public int Id { get; set; }
    public string Name { get; set; }
    public int PetTypeId { get; set; }
    public PetType PetType { get; set; }
}

public class Cat : Pet
{
    public int Mews { get; set; }
}

public class Dog : Pet
{
    public int Woovs { get; set; }
}

public class PetType
{
    public int Id { get; set; }
    public string Name { get; set; }
}

public class MyDbContext
{
    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        modelBuilder.Entity<Pet>(builder => 
        { 
            builder.HasDiscriminator(p => p.PetTypeId)
                .HasValue<Cat>(1)
                .HasValue<Dog>(2);
            builder.HasOne(p => p.PetType)
                .WithMany()
                .HasForeignKey(p.PetTypeId);
        });

        modelBuilder.Entity<PetType>(builder => 
        {
            builder.HasKey(pt => pt.Id);
        });
    }
}

}

With this example, all the pets of type Dog will have a PetTypeId of 2 that refers back to the PetType of Dog.

Related