Héritage en Entity Framework Core
Lors de la conception de notre base de données, il arrive souvent que nous ayons besoin de mettre en place de l’héritage. Entity Framework Core supporte évidemment ce scénario et nous allons voir comment le mettre en place.
Le type d’héritage
On va commencer par un petit rappel sur les différents types d’héritage. Niveau héritage, vous avez :
- TPT (Table Per Type) : Il y a une table partagée qui possède les propriétés de la classe de base et une table pour chaque classe héritée avec les propriétés qui lui sont spécifiques.
- TPH (Table Per Hierarchy) : Ici il n’y a qu’une seule table et un discriminant pour savoir sur quel type on se trouve.
- TPC (Table Per Concrete Class) : Chaque classe héritée possède sa table avec ses propriétés qui lui sont propres plus les propriétés de la classe de base.
Vous pouvez retrouver des exemples à l’adresse suivante : Types d’héritages
A l’heure actuelle Entity Framework Core ne supporte que TPH comme pattern pour faire de l’héritage. Les issues Github sont donc toujours « open » pour TPT (#2266) et TPC (#3170). A surveiller donc en fonction de vos besoins. A noter que « from scratch », TPH est le pattern le plus simple à mettre en œuvre et aura les meilleures performances (sans doute le pourquoi du comment ils ont commencés par TPH). Il y a par contre quelque chose de vraiment pas terrible en TPH mais qui est « by design » : Chaque propriété héritée peut être null. Alors attention à l’intégrité des données.
La mise en place
Les classes
Pour mettre en place notre héritage, nous allons définir une classe de base et des classes héritées comme suit :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
public class Contract { public int Id { get; set; } } public class TvContract : Contract { public string Bundle { get; set; } } public class MobileContract : Contract { public string PhoneNumber { get; set; } } |
Nous avons donc une classe de base et deux classes héritées.
Le contexte de données
Il va nous falloir ensuite mettre à jour notre contexte de données pour qu’il puisse faire le « mapping » de tout ça.
Alors pour faire ça, il y a plusieurs moyens de faire.
Par convention
Par convention, Entity Framework Core va comprendre qu’il y a de l’héritage à partir du moment où il va trouver plusieurs DbSet qui ont la même classe mère.
1 2 3 4 5 6 |
public class AppDataContext : DbContext { public DbSet<Contract> Contracts { get; set;} public DbSet<MobileContract> MobileContracts { get; set;} public DbSet<TvContract> TvContracts { get; set;} } |
Et voilà ! Entity Framework Core va s’occuper de créer une colonne « Discriminator » pour pouvoir faire la différence entre les deux entités. Il sera alors possible de faire une requête sur le DbSet qu’on veut pour récupérer soit les contrats mobiles, soit les contrats tv, soit les deux. Simple non ?
Par l’api fluent
Il est aussi possible d’utiliser l’api fluent pour configurer notre héritage.
Pour cela on va utiliser la méthode « OnModelCreating » de notre contexte de données.
1 2 3 4 5 6 7 8 9 10 11 12 |
public class AppDataContext : DbContext { public DbSet<Contract> Contracts { get; set; } protected override void OnModelCreating(ModelBuilder modelBuilder) { modelBuilder.Entity<Contract>() .HasDiscriminator<string>("contract_type") .HasValue<MobileContract>("mobile_contract") .HasValue<TvContract>("tv_contract"); } } |
Voilà un exemple en fluent api. Un des avantages de cette méthode est de pouvoir avoir la main sur le « discriminator ». On peut lui dire que c’est un int ou un string par exemple et même lui donner un autre nom ! Il faut cependant bien faire attention à designer les différents types possibles (ici MobileContract et TvContract) et la valeur qui correspond pour le « discriminator » sinon ça sera « dommage fromage ».
Pour récupérer nos contrats mobiles, nous ferons une requête comme ceci :
1 2 |
var dataContext = new AppDataContext(); var mobileContracts = dataContext.Contracts.OfType<MobileContract>(); |
On utilisera la méthode OfType<T> pour récupérer le type que l’on souhaite.
Voilà pour l’héritage en entity framework core !
Bonjour,
Article très intéressant pour ceux qui ne connaisse pas bien mais j’aimerais l’inverse, en parcourant la liste des dbset, y a-t-il un discriminateur et qui est ce discriminateur ?
A moins qu’il y ai un moyen de gérer la situation suivante :
public class Contract
{
public int Id { get; set; }
public int ExternalID { get; set; }
}
public class TvContract : Contract
{
public string Bundle { get; set; }
}
public class MobileContract : Contract
{
public string PhoneNumber { get; set; }
}
public class AppDataContext : DbContext
{
public DbSet Contracts { get; set;}
public DbSet MobileContracts { get; set;}
public DbSet TvContracts { get; set;}
}
public class AppDataContext : DbContext
{
public DbSet Contracts { get; set; }
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity()
.HasDiscriminator(« contract_type »)
.HasValue(« mobile_contract »)
.HasValue(« tv_contract »);
modelBuilder.Entity().HasAlternateKey(o => o.ExternalID);
}
}
J’ai un message d’erreur :
System.InvalidOperationException: A key cannot be configured on ‘MobileContract’ because it is a derived type. The key must be configured on the root type ‘Contract’. If you did not intend for ‘Contract’ to be included in the model, ensure that it is not included in a DbSet property on your context, referenced in a configuration call to ModelBuilder, or referenced from a navigation property on a type that is included in the model.
J’aimerai pouvoir faire en sorte que le discriminant soit récupéré (par réflexion j’imagine) pour l’ajouter de façon générique car je n’ai pas qu’un seul héritage dans mon modèle et j’aimerais ne pas avoir à faire des cas particulier.
D’où :
Y a-t-il un moyen de récupérer le discriminateur d’un objet qui va être hérité ?