added sample about simple navigation property with naming conventions
continuous-integration/drone/push Build is passing Details

master
Marc CHEVALDONNE 3 years ago
parent dce52620a8
commit 04ece58db0

@ -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<Nounours> 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<Information>().HasData(info1, info2, info3);
modelBuilder.Entity<Nounours>().Property<int>("InformationId");
modelBuilder.Entity<Nounours>().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<Information>().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<Nounours>().Property<int>("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<Nounours>().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 &#10138;**](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<Information>```, 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)

@ -0,0 +1,129 @@
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="477.4189453125" height="122">
<defs />
<g>
<g transform="translate(-134,-246) scale(1,1)">
<rect fill="#C0C0C0" stroke="none" x="151" y="263" width="184.45556640625" height="102" opacity="0.2" />
</g>
<g transform="translate(-134,-246) scale(1,1)">
<rect fill="#ffffff" stroke="none" x="144" y="256" width="184.45556640625" height="102" />
</g>
<g transform="translate(-134,-246) scale(1,1)">
<path fill="none" stroke="#000000" d="M 144 256 L 328.45556640625 256 L 328.45556640625 358 L 144 358 L 144 256 Z Z" stroke-miterlimit="10" />
</g>
<g transform="translate(-134,-246) scale(1,1)">
<path fill="none" stroke="#000000" d="M 144 281 L 328.45556640625 281" stroke-miterlimit="10" />
</g>
<g transform="translate(-134,-246) scale(1,1)">
<path fill="none" stroke="#000000" d="M 144 349 L 328.45556640625 349" stroke-miterlimit="10" />
</g>
<g transform="translate(-134,-246) scale(1,1)">
<g>
<path fill="none" stroke="none" />
<text fill="#000000" stroke="none" font-family="Arial" font-size="13px" font-style="normal" font-weight="bold" text-decoration="none" x="208.044189453125" y="275.5">
Nounours
</text>
</g>
</g>
<g transform="translate(-134,-246) scale(1,1)">
<g>
<path fill="none" stroke="none" />
<text fill="#000000" stroke="none" font-family="Arial" font-size="13px" font-style="normal" font-weight="normal" text-decoration="none" x="149" y="298.5">
+UniqueId: Guid
</text>
</g>
</g>
<g transform="translate(-134,-246) scale(1,1)">
<g>
<path fill="none" stroke="none" />
<text fill="#000000" stroke="none" font-family="Arial" font-size="13px" font-style="normal" font-weight="normal" text-decoration="none" x="149" y="313.5">
+Nom: string
</text>
</g>
</g>
<g transform="translate(-134,-246) scale(1,1)">
<g>
<path fill="none" stroke="none" />
<text fill="#000000" stroke="none" font-family="Arial" font-size="13px" font-style="normal" font-weight="normal" text-decoration="none" x="149" y="328.5">
+DateDeNaissance: DateTime
</text>
</g>
</g>
<g transform="translate(-134,-246) scale(1,1)">
<g>
<path fill="none" stroke="none" />
<text fill="#000000" stroke="none" font-family="Arial" font-size="13px" font-style="normal" font-weight="normal" text-decoration="none" x="149" y="343.5">
+NbPoils: int
</text>
</g>
</g>
<g transform="translate(-134,-246) scale(1,1)">
<rect fill="#C0C0C0" stroke="none" x="495" y="263" width="113.4189453125" height="87" opacity="0.2" />
</g>
<g transform="translate(-134,-246) scale(1,1)">
<rect fill="#ffffff" stroke="none" x="488" y="256" width="113.4189453125" height="87" />
</g>
<g transform="translate(-134,-246) scale(1,1)">
<path fill="none" stroke="#000000" d="M 488 256 L 601.4189453125 256 L 601.4189453125 343 L 488 343 L 488 256 Z Z" stroke-miterlimit="10" />
</g>
<g transform="translate(-134,-246) scale(1,1)">
<path fill="none" stroke="#000000" d="M 488 281 L 601.4189453125 281" stroke-miterlimit="10" />
</g>
<g transform="translate(-134,-246) scale(1,1)">
<path fill="none" stroke="#000000" d="M 488 334 L 601.4189453125 334" stroke-miterlimit="10" />
</g>
<g transform="translate(-134,-246) scale(1,1)">
<g>
<path fill="none" stroke="none" />
<text fill="#000000" stroke="none" font-family="Arial" font-size="13px" font-style="normal" font-weight="bold" text-decoration="none" x="512.193603515625" y="275.5">
Information
</text>
</g>
</g>
<g transform="translate(-134,-246) scale(1,1)">
<g>
<path fill="none" stroke="none" />
<text fill="#000000" stroke="none" font-family="Arial" font-size="13px" font-style="normal" font-weight="normal" text-decoration="none" x="493" y="298.5">
+InformationId: int
</text>
</g>
</g>
<g transform="translate(-134,-246) scale(1,1)">
<g>
<path fill="none" stroke="none" />
<text fill="#000000" stroke="none" font-family="Arial" font-size="13px" font-style="normal" font-weight="normal" text-decoration="none" x="493" y="313.5">
+MadeBy: string
</text>
</g>
</g>
<g transform="translate(-134,-246) scale(1,1)">
<g>
<path fill="none" stroke="none" />
<text fill="#000000" stroke="none" font-family="Arial" font-size="13px" font-style="normal" font-weight="normal" text-decoration="none" x="493" y="328.5">
+MadeIn: string
</text>
</g>
</g>
<g transform="translate(-134,-246) scale(1,1)">
<path fill="none" stroke="#000000" d="M 329 305 L 487 300" stroke-miterlimit="10" />
</g>
<g transform="translate(-134,-246) scale(1,1)">
<path fill="none" stroke="#000000" d="M 476.9755559235945 304.5288542655974 L 487 300 L 476.7092640537389 296.1140311781579" stroke-miterlimit="10" />
</g>
<g transform="translate(-134,-246) scale(1,1)">
<g>
<path fill="none" stroke="none" />
<text fill="#000000" stroke="none" font-family="Arial" font-size="13px" font-style="normal" font-weight="normal" text-decoration="none" x="398.5" y="291.5">
+Information
</text>
</g>
</g>
<g transform="translate(-134,-246) scale(1,1)">
<g>
<path fill="none" stroke="none" />
<text fill="#000000" stroke="none" font-family="Arial" font-size="13px" font-style="normal" font-weight="normal" text-decoration="none" x="461.5" y="319.5">
1
</text>
</g>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 5.4 KiB

@ -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:
* ```<navigationPropertyName><principalKeyPropertyName>```
* ```<navigationPropertyName>Id```
* ```<principalEntityName><principalKeyPropertyName>```
* ```<principalEntityName>Id```
* si on a aucune propriété de clé étrangère, une shadow property est créée avec le nom ```<navigationPropertyName><principalKeyPropertyName>``` ou ```<principalEntityName><principalKeyPropertyName>```.
* 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)
Loading…
Cancel
Save