Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Fluent API EF inheritance and mapping

I've a system with several self referencing entities with one-to-many relationship (parent-child). I'd like to use the common base class for all of those entities:

public class SelfReferencing
{
  public SelfReferencing Parent {get; set;}
  public ICollection<SelfReferencing> Children {get; set;}
}

and inherit the particular entity from SelfReferencing. Fluent API mapping requires the reference Properties to be of the defining type, when trying to do following:

modelBuilder.Entity<ConcreteSelfReferencing>()
                .HasMany(e => e.Children)
                .WithOptional(e => e.Parent);

So, can you help me to find a possibility to make use of inheritance and get the entities mapped?

THX

like image 474
toppless Avatar asked Jun 02 '14 15:06

toppless


1 Answers

Note: The example below is known as Table-Per-Hierarchy (TPH) - i.e. all contained in one table. Click on this link for Table-Per-Type (TPT), which has different tables for each type.

When using base types and inherited types, you have to tell EF how to determine the association for a specific inherited type.

Taking your code:

public abstract class SelfReferencing
{
    public SelfReferencing Parent { get; set; }
    public ICollection<SelfReferencing> Children { get; set; }
}

public class ConcreteSelfReferencing : SelfReferencing
{
}

EF now has to work out whether the sub-class is a ConcreteSelfReferencing or any other type of sub-class. This is determined by a discriminator on the table itself, to which the column is not part of your mapping.

To take another example, similar to I've used in the past:

public abstract class Policy
{
   public int Id { get; set; }
   public string PolicyNumber { get; set; }
}

public class InsurancePolicy : Policy
{
}

public class CarPolicy : Policy
{
}

The table was structured like this:

| Id    |   PolicyNumber  | Type  |  ..... |
  1         CAR0001         C
  2         ISN0001         I

To get EF to result them correctly, you would have:

public class MyContext : DbContext
{
   public MyContext() : base()
   {
   }

   public DbSet<Policy> Policies { get; set; }

   protected override void OnModelCreating(ModelBuilder builder)
   {
      var policyMap = modelBuilder.Entity<Policy>();

      // Set up discriminators
      policyMap.Map<InsurancePolicy>(p => o.Requires("Type").HasValue("I"))
               .Map<CarPolicy>(p => o.Requires("Type").HasValue("C"));

      // Notice that `Type` is only used for the discriminators, not an actual
      // mapped property
      policyMap.HasKey(x=>x.Id);
      policyMap.Property(x=>x.PolicyNumber);
   }
}

And from your code, you can either do the filtering yourself, or put the filtering in the DbContext. Here is an example from a separate class.

public class PolicyRepository
{
   private MyContext context = new MyContext();

   public PolicyRepository()
   {
   }

   public IQueryable<InsurancePolicy> GetInsurancePolicies()
   {
      return this.context.Policies.OfType<InsurancePolicy>();
   }

   public IQueryable<CarPolicy> GetCarPolicies()
   {
      return this.context.Policies.OfType<CarPolicy>();
   }
}
like image 58
Dominic Zukiewicz Avatar answered Oct 11 '22 21:10

Dominic Zukiewicz