Entity Framework
Relation de correspondance avec Entity Framework Code First: un à plusieurs et plusieurs à plusieurs
Recherche…
Introduction
La rubrique explique comment mapper des relations un à plusieurs et plusieurs à plusieurs à l'aide du code Entity Framework en premier.
Cartographier un à plusieurs
Alors disons que vous avez deux entités différentes, quelque chose comme ceci:
public class Person
{
public int PersonId { get; set; }
public string Name { get; set; }
}
public class Car
{
public int CarId { get; set; }
public string LicensePlate { get; set; }
}
public class MyDemoContext : DbContext
{
public DbSet<Person> People { get; set; }
public DbSet<Car> Cars { get; set; }
}
Et vous voulez établir une relation un-à-plusieurs entre eux, c'est-à-dire qu'une personne peut avoir zéro, une ou plusieurs voitures et qu'une voiture appartient à une seule personne. Chaque relation est bidirectionnelle, donc si une personne a une voiture, la voiture appartient à cette personne.
Pour ce faire, modifiez simplement vos classes de modèle:
public class Person
{
public int PersonId { get; set; }
public string Name { get; set; }
public virtual ICollection<Car> Cars { get; set; } // don't forget to initialize (use HashSet)
}
public class Car
{
public int CarId { get; set; }
public string LicensePlate { get; set; }
public int PersonId { get; set; }
public virtual Person Person { get; set; }
}
Et c'est tout :) Vous avez déjà votre relation établie. Dans la base de données, cela est bien sûr représenté par des clés étrangères.
Cartographier un à plusieurs: contre la convention
Dans le dernier exemple, vous pouvez voir que EF indique quelle colonne est la clé étrangère et où doit-elle pointer. Comment? En utilisant des conventions. Avoir une propriété de type Person
nommée Person
avec une propriété PersonId
conduit EF à conclure que PersonId
est une clé étrangère et pointe vers la clé primaire de la table représentée par le type Person
.
Mais que faire si vous deviez changer PersonId en OwnerId et Person en Propriétaire dans le type de voiture ?
public class Car { public int CarId { get; set; } public string LicensePlate { get; set; } public int OwnerId { get; set; } public virtual Person Owner { get; set; } }
Eh bien, malheureusement, dans ce cas, les conventions ne suffisent pas à produire le schéma de base de données correct:
Pas de soucis; vous pouvez aider EF avec quelques conseils sur vos relations et vos clés dans le modèle. Configurez simplement votre type de Car
pour utiliser la propriété OwnerId
tant que FK. Créez une configuration de type d'entité et appliquez-la dans votre OnModelCreating()
:
public class CarEntityTypeConfiguration : EntityTypeConfiguration<Car>
{
public CarEntityTypeConfiguration()
{
this.HasRequired(c => c.Owner).WithMany(p => p.Cars).HasForeignKey(c => c.OwnerId);
}
}
Cela signifie essentiellement que Car
a une propriété requise, Owner
( HasRequired () ) et dans le type de Owner
, la propriété Cars
est utilisée pour renvoyer aux entités de voiture ( WithMany () ). Et enfin la propriété représentant la clé étrangère est spécifiée ( HasForeignKey () ). Cela nous donne le schéma que nous voulons:
Vous pouvez également configurer la relation à partir du côté Person
:
public class PersonEntityTypeConfiguration : EntityTypeConfiguration<Person>
{
public PersonEntityTypeConfiguration()
{
this.HasMany(p => p.Cars).WithRequired(c => c.Owner).HasForeignKey(c => c.OwnerId);
}
}
L'idée est la même, seuls les côtés sont différents (notez comment vous pouvez lire le tout: «cette personne a beaucoup de voitures, chaque voiture a un propriétaire requis»). Peu importe si vous configurez la relation du côté Person
ou du côté Car
. Vous pouvez même inclure les deux, mais dans ce cas, veillez à spécifier la même relation des deux côtés!
Cartographier zéro ou un à plusieurs
Dans les exemples précédents, une voiture ne peut exister sans une personne. Et si vous vouliez que la personne soit facultative du côté de la voiture? Eh bien, c'est facile, sachant comment faire un à plusieurs. Il suffit de changer le PersonId
in Car
pour être nullable:
public class Car
{
public int CarId { get; set; }
public string LicensePlate { get; set; }
public int? PersonId { get; set; }
public virtual Person Person { get; set; }
}
Et puis utilisez le HasOptional () (ou WithOptional () , en fonction de quel côté vous faites la configuration):
public class CarEntityTypeConfiguration : EntityTypeConfiguration<Car>
{
public CarEntityTypeConfiguration()
{
this.HasOptional(c => c.Owner).WithMany(p => p.Cars).HasForeignKey(c => c.OwnerId);
}
}
Plusieurs à plusieurs
Passons à l'autre scénario, où chaque personne peut avoir plusieurs voitures et chaque voiture peut avoir plusieurs propriétaires (mais encore une fois, la relation est bidirectionnelle). C'est une relation plusieurs-à-plusieurs. Le moyen le plus simple est de laisser EF faire de la magie en utilisant des conventions.
Changez simplement le modèle comme ceci:
public class Person
{
public int PersonId { get; set; }
public string Name { get; set; }
public virtual ICollection<Car> Cars { get; set; }
}
public class Car
{
public int CarId { get; set; }
public string LicensePlate { get; set; }
public virtual ICollection<Person> Owners { get; set; }
}
Et le schéma:
Presque parfait. Comme vous pouvez le constater, EF a reconnu la nécessité d'une table de jointure, dans laquelle vous pouvez suivre les paires de personnes.
Plusieurs à plusieurs: personnalisation de la table de jointure
Vous voudrez peut-être renommer les champs de la table de jointure pour qu’ils soient un peu plus conviviaux. Vous pouvez le faire en utilisant les méthodes de configuration habituelles (encore une fois, peu importe de quel côté vous effectuez la configuration):
public class CarEntityTypeConfiguration : EntityTypeConfiguration<Car>
{
public CarEntityTypeConfiguration()
{
this.HasMany(c => c.Owners).WithMany(p => p.Cars)
.Map(m =>
{
m.MapLeftKey("OwnerId");
m.MapRightKey("CarId");
m.ToTable("PersonCars");
}
);
}
}
Assez facile à lire même: cette voiture a beaucoup de propriétaires ( HasMany () ), chaque propriétaire ayant plusieurs voitures ( WithMany () ). Mappez ceci de sorte que vous mappiez la clé gauche sur OwnerId ( MapLeftKey () ), la bonne clé pour CarId ( MapRightKey () ) et le tout pour la table PersonCars (ToTable () ). Et cela vous donne exactement ce schéma:
Many-to-many: entité de jointure personnalisée
Je dois admettre que je ne suis pas vraiment fan de laisser EF inférer la table de jointure sans une entité jointe. Vous ne pouvez pas suivre les informations supplémentaires vers une association personne-voiture (disons la date à partir de laquelle elle est valide), car vous ne pouvez pas modifier la table.
En outre, le CarId
dans la table de jointure fait partie de la clé primaire, donc si la famille achète une nouvelle voiture, vous devez d'abord supprimer les anciennes associations et en ajouter de nouvelles. EF vous le cache, mais cela signifie que vous devez effectuer ces deux opérations au lieu d'une simple mise à jour (sans parler du fait que des insertions / suppressions fréquentes peuvent entraîner une fragmentation des index - une bonne chose est qu'il existe une solution simple ).
Dans ce cas, vous pouvez créer une entité de jointure faisant référence à une voiture spécifique et à une personne spécifique. Fondamentalement, vous considérez votre association plusieurs-à-plusieurs comme une combinaison de deux associations un-à-plusieurs:
public class PersonToCar
{
public int PersonToCarId { get; set; }
public int CarId { get; set; }
public virtual Car Car { get; set; }
public int PersonId { get; set; }
public virtual Person Person { get; set; }
public DateTime ValidFrom { get; set; }
}
public class Person
{
public int PersonId { get; set; }
public string Name { get; set; }
public virtual ICollection<PersonToCar> CarOwnerShips { get; set; }
}
public class Car
{
public int CarId { get; set; }
public string LicensePlate { get; set; }
public virtual ICollection<PersonToCar> Ownerships { get; set; }
}
public class MyDemoContext : DbContext
{
public DbSet<Person> People { get; set; }
public DbSet<Car> Cars { get; set; }
public DbSet<PersonToCar> PersonToCars { get; set; }
}
Cela me donne beaucoup plus de contrôle et c'est beaucoup plus flexible. Je peux maintenant ajouter des données personnalisées à l'association et chaque association a sa propre clé primaire, de sorte que je puisse y mettre à jour la référence de la voiture ou du propriétaire.
Notez que ceci n'est qu'une combinaison de deux relations un-à-plusieurs, vous pouvez donc utiliser toutes les options de configuration présentées dans les exemples précédents.