added Single Property Navigation with Fluent API md
continuous-integration/drone/push Build is passing
Details
continuous-integration/drone/push Build is passing
Details
parent
04ece58db0
commit
7fc8b526fb
@ -0,0 +1,251 @@
|
||||
---
|
||||
sidebar_label: '2.10.2. Single Property Navigation with Fluent API'
|
||||
sidebar_position: 3
|
||||
description: "explique comment relier simplement deux entités avec les annotations de données"
|
||||
---
|
||||
import Mermaid from '@theme/Mermaid';
|
||||
|
||||
# Single Property Navigation with Fluent API
|
||||
|
||||
*19/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_011_SinglePropertyNavigation_FluentAPI)
|
||||
|
||||
---
|
||||
|
||||
Cet exemple montre comment réaliser une navigation entre deux entités avec une propriété simple (sens unique) avec Entity Framework Core et la *Fluent API*.
|
||||
Une version équivalente réalisée avec la *Fluent API* est disponible dans l'exemple
|
||||
[**2.10.1. Single Property Navigation with naming conventions**](/docs/Entity-Framework/Model/Relationships/02_10_01_SinglePropertyNavigation_conventions/ReadMe.md)
|
||||
|
||||
---
|
||||
|
||||
## 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```.
|
||||
|
||||
<Mermaid chart={`
|
||||
classDiagram
|
||||
direction LR
|
||||
class Nounours{
|
||||
+UniqueId: Guid
|
||||
+Nom: string
|
||||
+DateDeNaissance: DateTime
|
||||
+NbPoils: int
|
||||
}
|
||||
class Information{
|
||||
+InformationId: int
|
||||
+MadeBy: string
|
||||
+MadeIn: string
|
||||
}
|
||||
Nounours--> "1" Information : +Information
|
||||
`}/>
|
||||
|
||||
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 fonctionerait de la même manière si c'était le cas*).
|
||||
* La méthode ```OnModelCreating``` est réécrite et écrasera donc une partie des *conventions d'écriture* ou des *annotations de données*. Ici, par exemple, elle donne quelques indications sur la classe ```Nounours``` comme nous l'avons déjà vu dans les exemples précédents :
|
||||
```csharp
|
||||
protected override void OnModelCreating(ModelBuilder modelBuilder)
|
||||
{
|
||||
modelBuilder.Entity<Nounours>().HasKey(n => n.UniqueId);
|
||||
modelBuilder.Entity<Nounours>().Property(n => n.UniqueId).ValueGeneratedOnAdd();
|
||||
|
||||
modelBuilder.Entity<Nounours>().Property(n => n.DateDeNaissance).HasColumnName("Naissance").HasColumnType("date");
|
||||
|
||||
//...
|
||||
|
||||
base.OnModelCreating(modelBuilder);
|
||||
}
|
||||
```
|
||||
* Puis, ce qui nous intéresse plus particulièrement ici, elle définit la relation entre ```Nounours``` et ```Information``` : une entité ```Nounours``` possède une entité ```Information``` sans navigation inverse.
|
||||
```csharp
|
||||
modelBuilder.Entity<Nounours>().HasOne(n => n.Information);
|
||||
```
|
||||
##### 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 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_011_SinglePropertyNavigation_FluentAPI
|
||||
```
|
||||
:::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_011 --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_011_SinglePropertyNavigation_FluentAPI ➚**](https://codefirst.iut.uca.fr/git/mchSamples_.NET/mchsamples-.net-core/src/branch/master/p08_BDD_EntityFramework/ex_042_011_SinglePropertyNavigation_FluentAPI).
|
||||
|
||||
* 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_011_SinglePropertyNavigation_FluentAPI.Nounours.db* qui a été généré par l'exécution du programme et qui se trouve près de *ex_042_011_SinglePropertyNavigation_FluentAPI.csproj*.
|
||||
![DB Browser for SQLite](./02_10_01_SinglePropertyNavigation_conventions/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](./02_10_01_SinglePropertyNavigation_conventions/readme_files/dbbrowser02.png)
|
||||
![DB Browser for SQLite](./02_10_01_SinglePropertyNavigation_conventions/readme_files/dbbrowser03.png)
|
||||
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,15 @@
|
||||
import React, { useEffect } from "react";
|
||||
import mermaid from "mermaid";
|
||||
|
||||
mermaid.initialize({
|
||||
startOnLoad: true
|
||||
});
|
||||
|
||||
const Mermaid = ({ chart }) => {
|
||||
useEffect(() => {
|
||||
mermaid.contentLoaded();
|
||||
}, []);
|
||||
return <div className="mermaid">{chart}</div>;
|
||||
};
|
||||
|
||||
export default Mermaid;
|
Loading…
Reference in new issue