ended ex_042_010

EFCore3_Reforged
Marc CHEVALDONNE 5 years ago
parent 19a358e2f4
commit 7f78640ffb

@ -1,5 +1,5 @@
# Entity Framework Core 3.0
*14/01/2020 ⋅ Marc Chevaldonné*
*18/01/2020 ⋅ Marc Chevaldonné*
---
Entity Framework (EF) Core est un ORM (Object-Relational Mapper) qui permet aux développeurs .NET de gérer de manière simple, légère et extensible, des bases de données.
@ -32,7 +32,7 @@ Ce chapitre s'attardera sur le lien entre le mod
* [**ex_042_008 : Data Seeding before Entity Framework 2.1**](ex_042_008_DataSeeding_before_EF2.1) : explique comment utiliser un stub (méthode qui était recommandée avant EF Core 2.1)
* [**ex_042_009 : Data Seeding**](ex_042_009_DataSeeding) : explique comment utiliser un stub (méthode recommandée depuis EF Core 2.1)
* [**Relationships**](Relationships.md) : en cliquant [ici](Relationships.md), vous aurez plus de détails sur les relations entre entités
* [**ex_042_010 : One To One relationship with data annotations**](ex_042_009_DataSeeding) :
* [**ex_042_010 : Single Property navigation with data annotations**](ex_042_010_SinglePropertyNavigation_conventions) : montre comment une relation d'association est traduite par **EF Core** lorsque cette association est unidirectionnelle entre deux entités.
3. *Schemas and migrations* :
Le but de ce chapitre sera de vous montrer comment garder votre modèle et votre base de données synchronisés.
4. *Querying (LINQ to SQL) and saving data* :

@ -76,11 +76,11 @@ cd .\p08_BDD_EntityFramework\ex_042_009_DataSeeding
* Migration :
```
dotnet ef migrations add migration_ex_042_009
dotnet ef migrations add migration_ex_042_009 --context NounoursDBEntitiesWithStub
```
* Création de la table :
```
dotnet ef database update
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_009_DataSeeding**.

@ -12,15 +12,6 @@ namespace ex_042_010_SinglePropertyNavigation_conventions
{
OutputEncoding = System.Text.Encoding.UTF8;
var info1 = new Information { MadeBy = "George Lucas", MadeIn = "Kashyyyk" };
var info2 = new Information { MadeBy = "George Lucas", MadeIn = "Dagobah" };
var info3 = new Information { MadeBy = "George Lucas", MadeIn = "Lune forestière d'Endor" };
var n1 = new Nounours { Nom = "Chewbacca", DateDeNaissance = new DateTime(1977, 5, 27), NbPoils = 1234567, Information = info1 };
var n2 = new Nounours { Nom = "Yoda", DateDeNaissance = new DateTime(1980, 5, 21), NbPoils = 3, Information = info2 };
var n3 = new Nounours { Nom = "Ewok", DateDeNaissance = new DateTime(1983, 5, 25), NbPoils = 3456789, Information = info3 };
try
{
using (NounoursDBEntities db = new NounoursDBEntities())

@ -1,9 +1,6 @@
# ex_042_010_SinglePropertyNavigation_conventions
**TO BE WRITTEN**
*17/01/2020 ⋅ Marc Chevaldonné*
*18/01/2020 ⋅ Marc Chevaldonné*
---
@ -19,7 +16,39 @@ Cet exemple montre comment réaliser une navigation entre deux entités avec une
* ```NounoursDBEntities```
* ```NounoursDBEntitiesWithStub```
Le contenu des classes ```Nounours``` et ```NounoursDBEntities``` correspond à ce qui a été vu dans les exemples précédents. Seule la classe ```NounoursDBEntitiesWithStub``` sera donc expliquée ici.
### 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="./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*).
##### 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```
@ -27,49 +56,78 @@ Le contenu des classes ```Nounours``` et ```NounoursDBEntities``` correspond à
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
using Microsoft.EntityFrameworkCore;
using System;
namespace ex_042_009_DataSeeding
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
class NounoursDBEntitiesWithStub : NounoursDBEntities
{
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
modelBuilder.Entity<Nounours>().HasData(
new Nounours { UniqueId = Guid.Parse("{4422C524-B2CB-43EF-8263-990C3CEA7CAE}"), Nom = "Chewbacca", DateDeNaissance = new DateTime(1977, 5, 27), NbPoils = 1234567 },
new Nounours { UniqueId = Guid.Parse("{A4F84D92-C20F-4F2D-B3F9-CA00EF556E72}"), Nom = "Yoda", DateDeNaissance = new DateTime(1980, 5, 21), NbPoils = 3 },
new Nounours { UniqueId = Guid.Parse("{AE5FE535-F041-445E-B570-28B75BC78CB9}"), Nom = "Ewok", DateDeNaissance = new DateTime(1983, 5, 25), NbPoils = 3456789 }
);
}
}
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 }
);
}
```
* __Note importante__ : remarquez que la création des instances d'entités donne aussi l'```UniqueId``` puisqu'il ne s'agit pas d'un ajout "classique" dans la base mais la table est créée avec ces instances.
* __Note importante__ : l'utilisation de ```UniqueId``` vous permettra d'ajouter des entités liées dans le Stub.
* __Explication__ : l'utilisaiton de ```HasData``` dans ```OnModelCreating``` fait que vos données stubbées feront parties de la migration : rien à voir avec un ajout depuis votre appli consommatrice.
* __Note__ : souvent, on en profite pour réécrire également ```OnConfiguring``` afin de changer de fournisseur (pour prendre par exemple un *SQLite in memory* puisque ce contexte stubbé est voué à être utilisé par des tests).
* Elle est ensuite utilisée dans ```Program``` pour remplir la base de manière tout à fait classique et ne nécessite aucun appel supplémentaire.
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 NounoursDBEntitiesWithStub())
using (NounoursDBEntities db = new NounoursDBEntities())
{
WriteLine("Contenu de la base :");
foreach (var n in db.NounoursSet)
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```.
## 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_009_DataSeeding
cd .\p08_BDD_EntityFramework\ex_042_010_SinglePropertyNavigation_conventions
```
*Note*:
si vous n'avez pas installé correctement EntityFrameworkCore, il vous faudra peut-être utiliser également :
@ -81,42 +139,33 @@ cd .\p08_BDD_EntityFramework\ex_042_009_DataSeeding
* Migration :
```
dotnet ef migrations add migration_ex_042_009
dotnet ef migrations add migration_ex_042_010 --context NounoursDBEntitiesWithStub
```
* Création de la table :
```
dotnet ef database update
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_009_DataSeeding**.
Vous pouvez maintenant générer et exécuter l'exemple **ex_042_010_SinglePropertyNavigation_conventions**.
* Le résultat de l'exécution va ressembler à :
```
Contenu de la base :
ae5fe535-f041-445e-b570-28b75bc78cb9: Ewok (25/05/1983, 3456789 poils)
4422c524-b2cb-43ef-8263-990c3cea7cae: Chewbacca (27/05/1977, 1234567 poils)
a4f84d92-c20f-4f2d-b3f9-ca00ef556e72: Yoda (21/05/1980, 3 poils)
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)```
```
* Comment vérifier le contenu des bases de données SQL Server ?
Vous pouvez vérifier le contenu de votre base en utilisant l'*Explorateur d'objets SQL Server*.
* Pour cela, allez dans le menu *Affichage* puis *Explorateur d'objets SQL Server*.
<img src="../ex_041_001_ConnectionStrings/readmefiles/sqlserver_01.png" width="500"/>
* Déployez dans l'*Explorateur d'objets SQL Server* :
* *SQL Server*,
* puis *(localdb)\MSSQLLocalDB ...*,
* puis *Bases de données*
* puis celle portant le nom de votre migration, dans mon cas : *ex_042_009_DataSeeding.Nounours.mdf*
* puis *Tables*
* Faites un clic droit sur la table *dbo.Nounours* puis choisissez *Afficher les données*
<img src="../ex_041_001_ConnectionStrings/readmefiles/sqlserver_02.png" width="460"/>
* Vous devriez maintenant pouvoir voir les données suivantes dans le tableau :
|UniqueId |Nom|Naissance|NbPoils
|---|---|---|---
|ae5fe535-f041-445e-b570-28b75bc78cb9|Ewok|25/05/1983|3456789
|4422c524-b2cb-43ef-8263-990c3cea7cae|Chewbacca|27/05/1977|1234567
|a4f84d92-c20f-4f2d-b3f9-ca00ef556e72|Yoda|21/05/1980|3
## 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)

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 7.3 KiB

Loading…
Cancel
Save