diff --git a/Documentation/docusaurus/docs/Entity-Framework/Model/Relationships/02_10_01_SinglePropertyNavigation_conventions/ReadMe.md b/Documentation/docusaurus/docs/Entity-Framework/Model/Relationships/02_10_01_SinglePropertyNavigation_conventions/ReadMe.md new file mode 100644 index 0000000..3d50370 --- /dev/null +++ b/Documentation/docusaurus/docs/Entity-Framework/Model/Relationships/02_10_01_SinglePropertyNavigation_conventions/ReadMe.md @@ -0,0 +1,216 @@ +--- +sidebar_label: '2.10.1. Single Property Navigation conventions' +sidebar_position: 9 +description: "explique comment utiliser un stub (méthode recommandée depuis EF Core 2.1)" +--- + +# Single Property Navigation conventions + +*18/01/2020 ⋅ Marc Chevaldonné* +[**▶ Browse Sample ↗**](https://codefirst.iut.uca.fr/git/mchSamples_.NET/mchsamples-.net-core/src/branch/master/p08_BDD_EntityFramework/ex_042_010_SinglePropertyNavigation_conventions) + +--- + +Cet exemple montre comment réaliser une navigation entre deux entités avec une propriété simple (sens unique) avec Entity Framework Core et l'*annotation de données* ou les *conventions d'écriture*. +Une version équivalente réalisée avec la *Fluent API* est disponible dans l'exemple +[**ex_042_011 : Single Property navigation with Fluent API**](../ex_042_011_SinglePropertyNavigation_FluentAPI) + +--- + +## Comment est construit cet exemple ? +* Le projet est de type .NET Core +* Il contient quatre classes : + * ```Nounours``` + * ```Information``` + * ```NounoursDBEntities``` + * ```NounoursDBEntitiesWithStub``` + +### Les classes entités : ```Nounours``` et ```Information``` + +Un ```Nounours``` contient différentes propriétés (```Nom```, ```DateDeNaissance```, ```NbPoils```) dont ```Information``` de type ```Information```. +Une ```Information``` possède deux propriétés de type ```string```, mais aucun lien vers ```Nounours```. +![Sql Server](./readme_files/ex_042_010_classDiagram.svg) + +Ce qu'il faut noter : +* ```Nounours``` possède un identifiant unique (qui peut être utilisé par convention d'écriture ou _data annotation_). +* ```Information``` possède un identifiant unique (qui peut être utilisé par convention d'écriture ou _data annotation_). +* ```Nounours``` possède une association vers ```Information``` +```csharp +public Information Information +{ + get; set; +} +``` +* ```Information``` __NE__ possède __PAS__ d'association vers ```Nounours``` + +### La classe ```NounoursDBEntities``` + +* Comme dans les exemples précédents, ```NounoursDBEntities``` dérive de ```DbContext```. +* ```NounoursDBEntities``` déclare un seul ```DbSet``` de ```Nounours```. +```csharp +public DbSet NounoursSet { get; set; } +``` +* Dans cet exemple, ```NounoursDBEntities``` __NE__ déclare __PAS__ de ```DbSet``` d'```Information``` (*toutefois, l'exemple fonctionnerait de la même manière si c'était le cas*). +##### Quelques explications supplémentaires : +* Même si ```NounoursDBEntities``` ne déclare pas de ```DbSet``` d'```Information```, **EntityFramework Core** détecte qu'il va falloir créer une table d'```Information``` lors de la migration. +Nous pourrons nous en apercevoir plus tard dans ce document lorsque nous regarderons le résultat de la table. +* Pour pouvoir relier une entité ```Nounours``` à une entité ```Information```, la propriété ```Information``` n'est pas suffisante. Il faut également une propriété dans la classe ```Nounours``` du type de l'identifiant de la classe ```Information``` pour faire le lien. +Mais **EntityFramework Core** se charge de sa création lors de la migration. (*Vous pouvez l'ajouter vous-même si vous préférez*) Elle est l'équivalent de : +```public int InformationId {get; set;}``` +mais n'a pas été écrite par le développeur : on parle alors de **shadow property**. + +### La classe ```NounoursDBEntitiesWithStub``` + +* ```NounoursDBEntitiesWithStub``` est une classe fille de ```NounoursDBEntites```. +Son rôle est de proposer un Stub en plus de ce que sait déjà faire sa classe mère. Elle ne sera donc utilisée que pour des tests unitaires ou fonctionnels. +En conséquence, elle reprend tout ce que fait sa classe mère et ne change que la méthode ```OnModelCreating``` qui appelle la méthode de la classe mère puis ajoute des instances d'entités, grâce à la méthode d'extension ```HasData```. +```csharp +protected override void OnModelCreating(ModelBuilder modelBuilder) +{ + base.OnModelCreating(modelBuilder); + + var info1 = new Information { InformationId = 1, MadeBy = "George Lucas", MadeIn = "Kashyyyk" }; + var info2 = new Information { InformationId = 2, MadeBy = "George Lucas", MadeIn = "Dagobah" }; + var info3 = new Information { InformationId = 3, MadeBy = "George Lucas", MadeIn = "Lune forestière d'Endor" }; + + modelBuilder.Entity().HasData(info1, info2, info3); + + modelBuilder.Entity().Property("InformationId"); + + modelBuilder.Entity().HasData( + new { UniqueId = Guid.Parse("{4422C524-B2CB-43EF-8263-990C3CEA7CAE}"), Nom = "Chewbacca", DateDeNaissance = new DateTime(1977, 5, 27), NbPoils = 1234567, InformationId = 1 }, + new { UniqueId = Guid.Parse("{A4F84D92-C20F-4F2D-B3F9-CA00EF556E72}"), Nom = "Yoda", DateDeNaissance = new DateTime(1980, 5, 21), NbPoils = 3, InformationId = 2 }, + new { UniqueId = Guid.Parse("{AE5FE535-F041-445E-B570-28B75BC78CB9}"), Nom = "Ewok", DateDeNaissance = new DateTime(1983, 5, 25), NbPoils = 3456789, InformationId = 3 } + ); +} +``` +Elle crée d'abord des entités d'```Information``` +```csharp +var info1 = new Information { InformationId = 1, MadeBy = "George Lucas", MadeIn = "Kashyyyk" }; +var info2 = new Information { InformationId = 2, MadeBy = "George Lucas", MadeIn = "Dagobah" }; +var info3 = new Information { InformationId = 3, MadeBy = "George Lucas", MadeIn = "Lune forestière d'Endor" }; +``` +et les ajoute à la base : +```csharp +modelBuilder.Entity().HasData(info1, info2, info3); +``` +Elle va ensuite créer des instances de ```Nounours``` et les ajouter à la base, mais pour cela, il lui faut donner l'identifiant de l'```Information``` à lier au ```Nounours```. +Nous sommes donc obligés dans ce cas de fournir la propriété ```InformationId``` dans ```Nounours``` pour pouvoir les lier. +Deux solutions existent alors : +1) soit vous rajoutez une propriété ```InformationId``` dans ```Nounours``` : mais c'est dommage d'avoir à le faire uniquement pour un stub. +2) soit vous rajoutez la **shadow property** directement dans cette méthode ```OnModelCreating``` avec la ligne suivante qui est alors équivalente et permet de rajouter une propriété à une entité : +```csharp +modelBuilder.Entity().Property("InformationId"); +``` +On peut ensuite créer les instances de ```Nounours``` et les relier aux instances de ```Information``` en utilisant la nouvelle propriété ```InformationId``` de ```Nounours``` correspondant à la clé primaire de ``` Information```. +```csharp +modelBuilder.Entity().HasData( + new { UniqueId = Guid.Parse("{4422C524-B2CB-43EF-8263-990C3CEA7CAE}"), Nom = "Chewbacca", DateDeNaissance = new DateTime(1977, 5, 27), NbPoils = 1234567, InformationId = 1 }, + new { UniqueId = Guid.Parse("{A4F84D92-C20F-4F2D-B3F9-CA00EF556E72}"), Nom = "Yoda", DateDeNaissance = new DateTime(1980, 5, 21), NbPoils = 3, InformationId = 2 }, + new { UniqueId = Guid.Parse("{AE5FE535-F041-445E-B570-28B75BC78CB9}"), Nom = "Ewok", DateDeNaissance = new DateTime(1983, 5, 25), NbPoils = 3456789, InformationId = 3 } + ); +``` + +### La classe ```Program``` +* ```NounoursDBEntitiesWithStub``` est ensuite utilisée dans ```Program``` pour remplir la base de manière tout à fait classique et ne nécessite aucun appel supplémentaire. +```csharp +using (NounoursDBEntities db = new NounoursDBEntities()) +{ + WriteLine("Contenu de la base :"); + foreach (var n in db.NounoursSet.Include(n => n.Information)) + { + WriteLine($"\t{n}"); + } + +} +``` +Toutefois, si on ne précise pas qu'on veut également récupérer les contenus des propriétés ```Information``` des ```Nounours```, **EF Core** ne le fera pas et ne chargera que les propriétés simples des ```Nounours```. +Pour forcer **EF Core** à charger également les données d'```Information```, on utilise la méthode d'extension ```Include``` : +```csharp +db.NounoursSet.Include(n => n.Information) +``` +```db.NounoursSet``` récupère les données de la table de ```Nounours``` et ```.Include(n => n.Information)``` permet de préciser qu'on veut également récupérer les entités d'```Information``` associées à ces ```Nounours```. +* La suite de l'exemple ajoute un nouveau ```Nounours``` et affiche le contenu de la base de données. +```csharp +db.NounoursSet.Add(new Nounours { Nom = "Porg", DateDeNaissance = new DateTime(2017, 07, 19), NbPoils = 123, Information = new Information { MadeIn = "Ahch-To", MadeBy = "Jake Lunt Davies" } }); +db.SaveChangesAsync(); +``` +```csharp +using (NounoursDBEntities db = new NounoursDBEntities()) +{ + WriteLine("Contenu de la base :"); + foreach (var n in db.NounoursSet.Include(n => n.Information)) + { + WriteLine($"\t{n}"); + } +} +``` +## Comment exécuter cet exemple ? +Pour tester cette application, n'oubliez pas les commandes comme présentées dans l'exemple [**1.1. Connection Strings**](/docs/Entity-Framework/Fundamentals/ConnectionStrings) : pour générer l'exemple, il vous faut d'abord préparer les migrations et les tables. + * Ouvrez la *Console du Gestionnaire de package*, pour cela, dirigez-vous dans le menu *Outils*, puis *Gestionnaire de package NuGet*, puis *Console du Gestionnaire de package*. + * Dans la console que vous venez d'ouvrir, déplacez-vous dans le dossier du projet .NET Core, ici : +``` +cd .\p08_BDD_EntityFramework\ex_042_010_SinglePropertyNavigation_conventions +``` + +:::note rappel + si vous n'avez pas installé correctement EntityFrameworkCore, il vous faudra peut-être utiliser également : + +* ```dotnet tool install --global dotnet-ef``` si vous utilisez la dernière version de .NET Core (3.1 aujourd'hui), + +* ```dotnet tool install --global dotnet-ef --version 3.0.0``` si vous vous utiliser spécifiquement .NET Core 3.0. +::: + + + * Migration : +``` +dotnet ef migrations add migration_ex_042_010 --context NounoursDBEntitiesWithStub +``` + * Création de la table : +``` +dotnet ef database update --context NounoursDBEntitiesWithStub +``` + * Génération et exécution +Vous pouvez maintenant générer et exécuter l'exemple [**ex_042_010_SinglePropertyNavigation_conventions ➚**](https://codefirst.iut.uca.fr/git/mchSamples_.NET/mchsamples-.net-core/src/branch/master/p08_BDD_EntityFramework/ex_042_010_SinglePropertyNavigation_conventions). + + * Le résultat de l'exécution va ressembler à : + ``` +Contenu de la base : + 4422c524-b2cb-43ef-8263-990c3cea7cae: Chewbacca (27/05/1977, 1234567 poils, fait à Kashyyyk par George Lucas) + a4f84d92-c20f-4f2d-b3f9-ca00ef556e72: Yoda (21/05/1980, 3 poils, fait à Dagobah par George Lucas) + ae5fe535-f041-445e-b570-28b75bc78cb9: Ewok (25/05/1983, 3456789 poils, fait à Lune forestière d'Endor par George Lucas) + + Ajout d'un nouveau nounours... + + Contenu de la base : + 4422c524-b2cb-43ef-8263-990c3cea7cae: Chewbacca (27/05/1977, 1234567 poils, fait à Kashyyyk par George Lucas) + a4f84d92-c20f-4f2d-b3f9-ca00ef556e72: Yoda (21/05/1980, 3 poils, fait à Dagobah par George Lucas) + ae5fe535-f041-445e-b570-28b75bc78cb9: Ewok (25/05/1983, 3456789 poils, fait à Lune forestière d'Endor par George Lucas) + dc53d93d-e15a-458d-94c9-bb3a61063377: Porg (19/07/2017, 123 poils, fait à Ahch-To par Jake Lunt Davies) + ``` + +:::note +L'identifiant du dernier ```Nounours``` sera vraisemblablement différent puisqu'il est créé par la base lors de l'insertion. +::: + +## Comment exécuter cet exemple sans le stub ? +Il suffit de faire exactement comme dans le paragraphe précédent, mais en choisissant le contexte ```NounoursDBEntities``` à la place de ```NounoursDBEntitiesWithStub``` : +``` +dotnet ef migrations add migration_ex_042_010 --context NounoursDBEntities +dotnet ef database update --context NounoursDBEntities +``` +Lors de l'exécution, le résultat sera évidemment différent puisqu'il n'y aura pas les 3 nounours du stub. + +## Comment vérifier quelles base et tables ont été créées et leur contenu ? +Pour vérifier le contenu de votre base SQLite, vous pouvez utiliser le programme *DB Browser* : +* Rendez-vous sur la page : https://sqlitebrowser.org/dl/ et téléchargez le programme *DB Browser*. +* Lancez *DB Browser for SQLite* +* Glissez-déposez au milieu de la fenêtre de *DB Browser for SQLite* le fichier *ex_042_010_SinglePropertyNavigation_conventions.Nounours.db* qui a été généré par l'exécution du programme et qui se trouve près de *ex_042_010_SinglePropertyNavigation_conventions.csproj*. +![DB Browser for SQLite](./readme_files/dbbrowser01.png) +* Vous pouvez remarquer que, même si ```NounoursDBEntites``` ne déclare pas de ```DbSet```, une table d'```Information``` a bien été créée. +* Choisissez ensuite l'onglet *Parcourir les données* +* Observez les résultats obtenus des deux tables +![DB Browser for SQLite](./readme_files/dbbrowser02.png) +![DB Browser for SQLite](./readme_files/dbbrowser03.png) + + diff --git a/Documentation/docusaurus/docs/Entity-Framework/Model/Relationships/02_10_01_SinglePropertyNavigation_conventions/readme_files/dbbrowser01.png b/Documentation/docusaurus/docs/Entity-Framework/Model/Relationships/02_10_01_SinglePropertyNavigation_conventions/readme_files/dbbrowser01.png new file mode 100644 index 0000000..f3ef8ac Binary files /dev/null and b/Documentation/docusaurus/docs/Entity-Framework/Model/Relationships/02_10_01_SinglePropertyNavigation_conventions/readme_files/dbbrowser01.png differ diff --git a/Documentation/docusaurus/docs/Entity-Framework/Model/Relationships/02_10_01_SinglePropertyNavigation_conventions/readme_files/dbbrowser02.png b/Documentation/docusaurus/docs/Entity-Framework/Model/Relationships/02_10_01_SinglePropertyNavigation_conventions/readme_files/dbbrowser02.png new file mode 100644 index 0000000..404f763 Binary files /dev/null and b/Documentation/docusaurus/docs/Entity-Framework/Model/Relationships/02_10_01_SinglePropertyNavigation_conventions/readme_files/dbbrowser02.png differ diff --git a/Documentation/docusaurus/docs/Entity-Framework/Model/Relationships/02_10_01_SinglePropertyNavigation_conventions/readme_files/dbbrowser03.png b/Documentation/docusaurus/docs/Entity-Framework/Model/Relationships/02_10_01_SinglePropertyNavigation_conventions/readme_files/dbbrowser03.png new file mode 100644 index 0000000..17164bb Binary files /dev/null and b/Documentation/docusaurus/docs/Entity-Framework/Model/Relationships/02_10_01_SinglePropertyNavigation_conventions/readme_files/dbbrowser03.png differ diff --git a/Documentation/docusaurus/docs/Entity-Framework/Model/Relationships/02_10_01_SinglePropertyNavigation_conventions/readme_files/ex_042_010_classDiagram.svg b/Documentation/docusaurus/docs/Entity-Framework/Model/Relationships/02_10_01_SinglePropertyNavigation_conventions/readme_files/ex_042_010_classDiagram.svg new file mode 100644 index 0000000..449ef02 --- /dev/null +++ b/Documentation/docusaurus/docs/Entity-Framework/Model/Relationships/02_10_01_SinglePropertyNavigation_conventions/readme_files/ex_042_010_classDiagram.svg @@ -0,0 +1,129 @@ + + + + + + + + + + + + + + + + + + + + + + + Nounours + + + + + + + + +UniqueId: Guid + + + + + + + + +Nom: string + + + + + + + + +DateDeNaissance: DateTime + + + + + + + + +NbPoils: int + + + + + + + + + + + + + + + + + + + + + + + Information + + + + + + + + +InformationId: int + + + + + + + + +MadeBy: string + + + + + + + + +MadeIn: string + + + + + + + + + + + + + + +Information + + + + + + + + 1 + + + + + diff --git a/Documentation/docusaurus/docs/Entity-Framework/Model/Relationships/intro.md b/Documentation/docusaurus/docs/Entity-Framework/Model/Relationships/intro.md new file mode 100644 index 0000000..32dc554 --- /dev/null +++ b/Documentation/docusaurus/docs/Entity-Framework/Model/Relationships/intro.md @@ -0,0 +1,36 @@ +--- +sidebar_label: 'Introduction' +sidebar_position: 1 +description: 'Avec ce chapitre, vous aurez plus de détails sur les relations entre entités' +--- + +# Relationships + +On parle de propriété de navigation dès qu'on a une propriété qui ne peut pas être une donnée simple (string, nombre, bool...). +Par défaut (*ie* conventions d'écriture), dans une relation, +c'est toujours la primary key qui est utilisée. +On peut utiliser une autre clé en passant par la notation Fluent. + + +Le plus commun c'est d'avoir des propriétés de navigation +définies sur les deux entités et une clé étrangère (foreign key) +du côté de l'entité dépendante. +* si a on une paire de propriétés de navigation sur les deux entités, l'une sera l'inverse de l'autre +* si l'entité dépendante possède une propriété dont le nom vérifie l'un des patrons suivants, alors elle est configurée comme une foreign key: + * `````` + * ```Id``` + * `````` + * ```Id``` +* si on a aucune propriété de clé étrangère, une shadow property est créée avec le nom `````` ou ``````. +* Single Property Navigation vs. Pair Navigation Property +* Cascade Delete +* ex01: single navigation property (conventions & data annotations) +* ex02: single navigation property (fluent api) +* ex03: one to one (conventions & data annotations) +* ex04: one to one (fluent api) +* ex05: one to many (conventions & data annotations) +* ex06: one to many (fluent api) +* ex07: many to many (conventions & data annotations) +* ex08: many to many (fluent api) +* ex09: shadow property +* ex10: dictionaries (fluent api) \ No newline at end of file