Table Héritage par type de béton (TPC) dans Entity Framework 6 (EF6)

Dans un effort pour éviter l'utilisation de Table par Hiérarchie (TPH), j'ai regardé des exemples de la meilleure façon d'implémenter l'inheritance de la class Table-Per-Concrete (TPC) dans mon model de database. Je suis tombé sur la documentation officielle et cet article .

Voici quelques classs de maquette avec un inheritance simple.

public class BaseEntity { public BaseEntity() { ModifiedDateTime = DateTime.Now; } [Key] [DatabaseGeneratedAtsortingbute(DatabaseGeneratedOption.Identity)] public int Id { get; set; } public DateTime ModifiedDateTime { get; set; } } public class Person : BaseEntity { public ssortingng FirstName { get; set; } public ssortingng LastName { get; set; } } public class Business : BaseEntity { public ssortingng Name { get; set; } public ssortingng Location { get; set; } } 

Les configurations DbModelBuilder utilisées par les exemples dans les deux articles.

 modelBuilder.Entity<BaseEntity>() .Property(c => c.Id) .HasDatabaseGeneratedOption(DatabaseGeneratedOption.None); modelBuilder.Entity<Person>().Map(m => { m.MapInheritedProperties(); m.ToTable("Person"); }); modelBuilder.Entity<Business>().Map(m => { m.MapInheritedProperties(); m.ToTable("Business"); }); 

L'application s'exécute avec succès mais quand je returnne à la database je trouve trois (3) tables au lieu des deux (2) que je m'attendais à find. Après un peu de test, il semblerait que la table "BaseEntity" soit créée mais ne soit jamais utilisée. Tout semble fonctionner correctement à l'exception de cette table orpheline vide.

Je dérange avec les configurations de DbModelBuilder, enlevant finalement les configurations de «BaseEntity» qui fournit le résultat attendu; Deux (2) tables, chacune d'entre elles ayant les bonnes propriétés et fonctionnant correctement.

Je fais un dernier test, efface toutes les configurations de DbModelBuilder, n'inclut que les deux (2) propriétés de DbSet pour "Person" et "Business" et je revérifie.

 public DbSet<Person> People { get; set; } public DbSet<Business> Businesses { get; set; } 

À ma grande surprise, le projet construit, sort dans la database, crée seulement les deux tables avec toutes les propriétés de la class, y compris celles héritées de la class "BaseEntity". Je peux faire des opérations de CRUD sans problème.

Après l'exécution de nombreux tests, je ne trouve aucun problème avec le test final et je n'ai pas été en mesure de reproduire l'erreur de key en double les deux articles mis en garde.

Les modifications apscopes à la database ont été validées, mais une erreur s'est produite lors de la mise à jour du context de l'object. ObjectContext peut être dans un état incohérent. Message d'exception interne: AcceptChanges ne peut pas continuer car les valeurs de key de l'object entrent en conflit avec un autre object dans ObjectStateManager. Assurez-vous que les valeurs de key sont uniques avant d'appeler AcceptChanges.

  1. Je suis curieux de savoir pourquoi les exemples utilisent la propriété MapInheritedProperties; est-ce une méthode obsolète?
  2. Pourquoi les deux exemples indiquent-ils d'inclure des propriétés de configuration pour "BaseEntity" mais en incluant la propriété DbSet ou toute configuration DbModelBuilder pour la class "BaseEntity" provoque la création d'une table inutilisée.
  3. En reference à l'erreur key unique dont les articles ont été avertis; Je suis incapable de reproduire l'erreur et j'ai testé plusieurs fois avec la key primaire soit un int généré par la database et un guid généré par la database. Les informations sur cette erreur sont-elles également obsolètes ou existe-t-il un test que je peux exécuter pour produire cette erreur?

Juste pour rendre tout cela plus simple, j'ai déplacé le code nécessaire pour forcer TablePerConcrete à open source. Son but est de permettre des fonctionnalités normalement disponibles uniquement dans l'interface Fluent (où vous devez disperser beaucoup de code dans votre méthode OnModelCreating de class Db) pour migrer vers des fonctionnalités basées sur les attributes.

Cela vous permet de faire des choses comme ça:

 [TablePerConcrete] public class MySubclassTable : MyParentClassEntity 

Forcer TPC indépendamment de ce que EF pourrait décider d'inférer de votre relation de class / sous-class parente.

Un défi intéressant ici est que parfois EF va bousiller une propriété Id héritée, en la définissant pour être remplie avec une valeur explicite plutôt que d'être générée par la database. Vous pouvez vous assurer qu'il ne le fait pas en ayant la class parente implémentant l'interface IId (qui dit simplement: Cela a une propriété Id), puis en marquant les sous-classs avec [ForcePKId] .

 public class MyParentClassEntity : IId { public int Id { get; set; } . . . [TablePerConcrete] [ForcePKId] public class MySubclassTable : MyParentClassEntity { // No need for PK/Id property here, it was inherited and will work as // you intended. 

Déclencher le code qui gère tout cela pour vous est assez simple – il suffit d'append quelques lignes à votre class Db:

 public class Db : DbContext { . . . protected override void OnModelCreating(DbModelBuilder modelBuilder) { var modelsProject = Assembly.GetExecutingAssembly(); B9DbExtender.New().Extend(modelBuilder, modelsProject); 

Vous pouvez y accéder de deux façons:

  1. Via un seul et unique logiciel avec toutes les classs pertinentes copiées-collées dans un seul file, ici: https://gist.github.com/b9chris/8efd30687d554d1ceeb3fee359c179f9

  2. Via une librairie, notre Brass9.Data, dont nous libérons l'open source. Il y a beaucoup d'autres outils EF6, comme Data Migrations. Il est également plus organisé, avec des classs réparties dans des files séparés comme vous le feriez normalement: https://github.com/b9chris/Brass9.Data

J'utilise des classs de maping, mais rien. Je le résous comme ça:

 public class PersonMap : EntityTypeConfiguration<Person> { public PersonMap() { Map(m => { m.ToTable("Person"); m.MapInheritedProperties(); }); HasKey(p => p.Id); Property(p => p.Id).HasDatabaseGeneratedOption(DatabaseGeneratedOption.None); } } 

Remeber – la class de base doit être abstraite.