Entity Framework
Relation de mappage avec Entity Framework Code First: One-to-one et variations
Recherche…
Introduction
Cette rubrique explique comment mapper des relations de type un à un à l'aide d'Entity Framework.
Cartographier un à zéro ou un
Alors disons encore que vous avez le modèle suivant:
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 maintenant, vous voulez le configurer pour que vous puissiez exprimer la spécification suivante: une personne peut avoir une ou une voiture et chaque voiture appartient à une seule personne (les relations sont bidirectionnelles, donc si CarA appartient à PersonA, alors PersonA 'possède 'CarA).
Modifions donc un peu le modèle: ajoutez les propriétés de navigation et les propriétés de la clé étrangère:
public class Person
{
public int PersonId { get; set; }
public string Name { get; set; }
public int CarId { get; set; }
public virtual Car Car { get; set; }
}
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 la configuration:
public class CarEntityTypeConfiguration : EntityTypeConfiguration<Car>
{
public CarEntityTypeConfiguration()
{
this.HasRequired(c => c.Person).WithOptional(p => p.Car);
}
}
À ce moment, cela devrait être explicite. La voiture a une personne requise ( HasRequired () ), la personne ayant une voiture optionnelle ( WithOptional () ). Encore une fois, peu importe de quel côté vous configurez cette relation, faites attention lorsque vous utilisez la bonne combinaison de Has / With et Required / Optional. Du côté de la Person
, cela ressemblerait à ceci:
public class PersonEntityTypeConfiguration : EntityTypeConfiguration<Person>
{
public PersonEntityTypeConfiguration()
{
this.HasOptional(p => p.Car).WithOptional(c => c.Person);
}
}
Maintenant, vérifions le schéma de la base de données:
Regardez attentivement: vous pouvez voir qu'il n'y a pas de FK dans People
pour faire référence à la Car
. En outre, le FK in Car
n'est pas le PersonId
, mais le CarId
. Voici le script réel pour le FK:
ALTER TABLE [dbo].[Cars] WITH CHECK ADD CONSTRAINT [FK_dbo.Cars_dbo.People_CarId] FOREIGN KEY([CarId])
REFERENCES [dbo].[People] ([PersonId])
Cela signifie donc que les propriétés de clé CarId
et PersonId
foregn que nous avons dans le modèle sont fondamentalement ignorées. Ils sont dans la base de données, mais ce ne sont pas des clés étrangères, comme on peut s'y attendre. En effet, les mappages individuels ne permettent pas d'ajouter le FK dans votre modèle EF. Et c'est parce que les correspondances individuelles sont assez problématiques dans une base de données relationnelle.
L'idée est que chaque personne peut avoir exactement une voiture et que cette voiture ne peut appartenir qu'à cette personne. Ou il peut y avoir des enregistrements de personnes auxquels aucune voiture n'est associée.
Alors, comment cela pourrait-il être représenté avec des clés étrangères? De toute évidence, il pourrait y avoir un PersonId
in Car
, et un CarId
in People
. Pour que chaque personne puisse avoir une seule voiture, PersonId
devrait être unique en Car
. Mais si PersonId
est unique dans People
, alors comment pouvez-vous ajouter deux ou plusieurs enregistrements où PersonId
est NULL
(plus d'une voiture sans propriétaire)? Réponse: vous ne pouvez pas (en fait, vous pouvez créer un index unique filtré dans SQL Server 2008 et versions ultérieures, mais oublions un instant cette technicité, sans parler des autres SGBDR). Sans parler du cas où vous spécifiez les deux extrémités de la relation ...
La seule manière réelle d'appliquer cette règle si les tables People
et Car
ont la même clé primaire (mêmes valeurs dans les enregistrements connectés). Et pour ce faire, CarId
in Car
doit être à la fois un PK et un FK au PK du People. Et cela complique tout le schéma. Lorsque je l'utilise, je nomme plutôt le PK / FK dans Car
PersonId
et le configure en conséquence:
public class Person
{
public int PersonId { get; set; }
public string Name { get; set; }
public virtual Car Car { get; set; }
}
public class Car
{
public string LicensePlate { get; set; }
public int PersonId { get; set; }
public virtual Person Person { get; set; }
}
public class CarEntityTypeConfiguration : EntityTypeConfiguration<Car>
{
public CarEntityTypeConfiguration()
{
this.HasRequired(c => c.Person).WithOptional(p => p.Car);
this.HasKey(c => c.PersonId);
}
}
Pas idéal, mais peut-être un peu mieux. Cependant, vous devez être vigilant lorsque vous utilisez cette solution, car elle va à l’encontre des conventions de nommage habituelles, ce qui peut vous induire en erreur. Voici le schéma généré à partir de ce modèle:
Cette relation n'est donc pas appliquée par le schéma de base de données, mais par Entity Framework lui-même. C'est pourquoi vous devez faire très attention lorsque vous utilisez ceci, pour ne laisser personne en colère directement avec la base de données.
Cartographie individuelle
La cartographie individuelle (lorsque les deux côtés sont requis) est également une opération délicate.
Imaginons comment cela pourrait être représenté avec des clés étrangères. Encore une fois, un CarId
dans People
fait référence à CarId
in Car
et un PersonId
in Car qui fait référence à PersonId
dans People
.
Maintenant, que se passe-t-il si vous souhaitez insérer un enregistrement de voiture? Pour que cela réussisse, il doit y avoir un PersonId
spécifié dans cet enregistrement car il est requis. Et pour que ce PersonId
soit valide, l'enregistrement correspondant dans People
doit exister. OK, alors allons-y et insérez l'enregistrement de la personne. Mais pour que cela réussisse, un CarId
valide doit être dans l'enregistrement personnel - mais cette voiture n'est pas encore insérée! Cela ne peut pas être parce que nous devons insérer le dossier de la personne référée en premier. Mais nous ne pouvons pas insérer l'enregistrement de la personne référée, car il renvoie à l'enregistrement de la voiture, de sorte qu'il doit être inséré en premier (réception par clé étrangère :)).
Donc, cela ne peut pas non plus être représenté comme "logique". Encore une fois, vous devez supprimer l’une des clés étrangères. Lequel vous laissez tomber est à vous. Le côté qui reste avec une clé étrangère est appelé «dépendant», le côté qui est laissé sans clé étrangère est appelé «principal». Et encore une fois, pour assurer l'unicité dans la dépendante, le PK doit être le FK, donc l'ajout d'une colonne FK et son importation dans votre modèle ne sont pas pris en charge.
Alors, voici la configuration:
public class CarEntityTypeConfiguration : EntityTypeConfiguration<Car>
{
public CarEntityTypeConfiguration()
{
this.HasRequired(c => c.Person).WithRequiredDependent(p => p.Car);
this.HasKey(c => c.PersonId);
}
}
À ce jour, vous devriez vraiment en avoir la logique :) Rappelez-vous juste que vous pouvez également choisir l'autre côté, faites juste attention à utiliser les versions dépendantes / principales de WithRequired (et vous devrez toujours configurer le PK dans Car).
public class PersonEntityTypeConfiguration : EntityTypeConfiguration<Person>
{
public PersonEntityTypeConfiguration()
{
this.HasRequired(p => p.Car).WithRequiredPrincipal(c => c.Person);
}
}
Si vous vérifiez le schéma de la base de données, vous constaterez que c'est exactement la même chose que dans le cas de la solution one-to-one ou zero. C'est parce que, encore une fois, cela n'est pas appliqué par le schéma, mais par EF lui-même. Alors encore une fois, faites attention :)
Cartographier un ou un zéro ou un zéro
Et pour terminer, regardons brièvement le cas où les deux côtés sont facultatifs.
Maintenant, vous devriez vraiment vous ennuyer avec ces exemples :), donc je ne vais pas entrer dans les détails et jouer avec l'idée d'avoir deux FK-s et les problèmes potentiels et vous avertir des dangers de ne pas appliquer ces règles dans le schéma mais juste EF lui-même.
Voici la configuration à appliquer:
public class CarEntityTypeConfiguration : EntityTypeConfiguration<Car>
{
public CarEntityTypeConfiguration()
{
this.HasOptional(c => c.Person).WithOptionalPrincipal(p => p.Car);
this.HasKey(c => c.PersonId);
}
}
Encore une fois, vous pouvez également configurer depuis l'autre côté, faites juste attention à utiliser les bonnes méthodes :)