parent
7f78640ffb
commit
8edf070b5f
Before Width: | Height: | Size: 135 KiB After Width: | Height: | Size: 134 KiB |
Before Width: | Height: | Size: 96 KiB After Width: | Height: | Size: 101 KiB |
Before Width: | Height: | Size: 110 KiB After Width: | Height: | Size: 116 KiB |
@ -0,0 +1,223 @@
|
|||||||
|
# ex_042_011_SinglePropertyNavigation_FluentAPI
|
||||||
|
|
||||||
|
*19/01/2020 ⋅ Marc Chevaldonné*
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
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
|
||||||
|
[**ex_042_010 : Single Property navigation with data annotations**](../ex_042_010_SinglePropertyNavigation_conventions)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 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```.
|
||||||
|
<img src="../ex_042_010_SinglePropertyNavigation_conventions/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 virtual 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 ex_041_001 : 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*:
|
||||||
|
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**.
|
||||||
|
|
||||||
|
* 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](../ex_042_010_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](../ex_042_010_SinglePropertyNavigation_conventions/readme_files/dbbrowser02.png)
|
||||||
|
![DB Browser for SQLite](../ex_042_010_SinglePropertyNavigation_conventions/readme_files/dbbrowser03.png)
|
||||||
|
|
||||||
|
|
@ -0,0 +1,14 @@
|
|||||||
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
|
<PropertyGroup>
|
||||||
|
<OutputType>Exe</OutputType>
|
||||||
|
<TargetFramework>netcoreapp3.0</TargetFramework>
|
||||||
|
<StartWorkingDirectory>$(MSBuildProjectDirectory)</StartWorkingDirectory>
|
||||||
|
</PropertyGroup>
|
||||||
|
<ItemGroup>
|
||||||
|
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="3.1.0" />
|
||||||
|
<PackageReference Include="Microsoft.EntityFrameworkCore.SQLite" Version="3.1.0" />
|
||||||
|
<PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="3.1.0" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
</Project>
|
Loading…
Reference in new issue