commit
882cfde4ff
@ -1,24 +0,0 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup>
|
||||
<TargetFramework>netcoreapp3.0</TargetFramework>
|
||||
<AssemblyName>ex_042_003_EF_CodeFirst_Fluent_API</AssemblyName>
|
||||
<OutputType>Exe</OutputType>
|
||||
<PackageId>ex_042_003_EF_CodeFirst_Fluent_API</PackageId>
|
||||
<RuntimeFrameworkVersion>3.0.1</RuntimeFrameworkVersion>
|
||||
<GenerateAssemblyConfigurationAttribute>false</GenerateAssemblyConfigurationAttribute>
|
||||
<GenerateAssemblyCompanyAttribute>false</GenerateAssemblyCompanyAttribute>
|
||||
<GenerateAssemblyProductAttribute>false</GenerateAssemblyProductAttribute>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="3.1.0" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="3.1.0">
|
||||
<PrivateAssets>All</PrivateAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" Version="3.1.0" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer.Design" Version="1.1.6" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="3.1.0">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
</PackageReference>
|
||||
</ItemGroup>
|
||||
</Project>
|
@ -1,21 +0,0 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup>
|
||||
<TargetFramework>netcoreapp3.0</TargetFramework>
|
||||
<AssemblyName>ex_042_004_EF_CF_InitializationStrategy</AssemblyName>
|
||||
<OutputType>Exe</OutputType>
|
||||
<PackageId>ex_042_004_EF_CF_InitializationStrategy</PackageId>
|
||||
<RuntimeFrameworkVersion>3.0.1</RuntimeFrameworkVersion>
|
||||
<GenerateAssemblyConfigurationAttribute>false</GenerateAssemblyConfigurationAttribute>
|
||||
<GenerateAssemblyCompanyAttribute>false</GenerateAssemblyCompanyAttribute>
|
||||
<GenerateAssemblyProductAttribute>false</GenerateAssemblyProductAttribute>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="3.1.0" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" Version="3.1.0" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer.Design" Version="1.1.6" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="3.1.0">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
</PackageReference>
|
||||
</ItemGroup>
|
||||
</Project>
|
@ -1,21 +0,0 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup>
|
||||
<TargetFramework>netcoreapp3.0</TargetFramework>
|
||||
<AssemblyName>ex_042_005_EF_CF_Seeding_Data</AssemblyName>
|
||||
<OutputType>Exe</OutputType>
|
||||
<PackageId>ex_042_005_EF_CF_Seeding_Data</PackageId>
|
||||
<RuntimeFrameworkVersion>3.0.1</RuntimeFrameworkVersion>
|
||||
<GenerateAssemblyConfigurationAttribute>false</GenerateAssemblyConfigurationAttribute>
|
||||
<GenerateAssemblyCompanyAttribute>false</GenerateAssemblyCompanyAttribute>
|
||||
<GenerateAssemblyProductAttribute>false</GenerateAssemblyProductAttribute>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="3.1.0" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" Version="3.1.0" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer.Design" Version="1.1.6" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="3.1.0">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
</PackageReference>
|
||||
</ItemGroup>
|
||||
</Project>
|
@ -1,21 +0,0 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup>
|
||||
<TargetFramework>netcoreapp3.0</TargetFramework>
|
||||
<AssemblyName>ex_042_007_EF_CF_One_to_One_FluentAPI</AssemblyName>
|
||||
<OutputType>Exe</OutputType>
|
||||
<PackageId>ex_042_007_EF_CF_One_to_One_FluentAPI</PackageId>
|
||||
<RuntimeFrameworkVersion>3.0.1</RuntimeFrameworkVersion>
|
||||
<GenerateAssemblyConfigurationAttribute>false</GenerateAssemblyConfigurationAttribute>
|
||||
<GenerateAssemblyCompanyAttribute>false</GenerateAssemblyCompanyAttribute>
|
||||
<GenerateAssemblyProductAttribute>false</GenerateAssemblyProductAttribute>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="3.1.0" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" Version="3.1.0" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer.Design" Version="1.1.6" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="3.1.0">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
</PackageReference>
|
||||
</ItemGroup>
|
||||
</Project>
|
@ -1,21 +0,0 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup>
|
||||
<TargetFramework>netcoreapp3.0</TargetFramework>
|
||||
<AssemblyName>ex_042_008_EF_CF_One_to_Many</AssemblyName>
|
||||
<OutputType>Exe</OutputType>
|
||||
<PackageId>ex_042_008_EF_CF_One_to_Many</PackageId>
|
||||
<RuntimeFrameworkVersion>3.0.1</RuntimeFrameworkVersion>
|
||||
<GenerateAssemblyConfigurationAttribute>false</GenerateAssemblyConfigurationAttribute>
|
||||
<GenerateAssemblyCompanyAttribute>false</GenerateAssemblyCompanyAttribute>
|
||||
<GenerateAssemblyProductAttribute>false</GenerateAssemblyProductAttribute>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="3.1.0" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" Version="3.1.0" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer.Design" Version="1.1.6" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="3.1.0">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
</PackageReference>
|
||||
</ItemGroup>
|
||||
</Project>
|
@ -0,0 +1,282 @@
|
||||
# Entity Framework Core 3.0
|
||||
*25/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.
|
||||
EF permet de gérer de nombreux *providers* (SQL Server, SQLite, Cosmos, ...) de manière transparente.
|
||||
EF vous permet également de mettre à jour vos bases de données et d'exécuter des requêtes sans avoir à écrire la moindre requête SQL. Vous pouvez passer par LINQ to SQL qui apportera plus de lisibilité et permettra au compilateur de vous aider à détecter vos erreurs.
|
||||
|
||||
---
|
||||
*Note:*
|
||||
Différentes solutions existent avec EF pour gérer une base de données dont le modèle existe par exemple. Dans ces exemples, je ne traiterai que la partie *Code First*, c'est-à-dire le cas où le modèle est créé à partir de vos classes.
|
||||
|
||||
---
|
||||
## Plan
|
||||
Les exemples sont organisés selon le plan suivant:
|
||||
1. *Fundamentals* :
|
||||
Dans cette partie, je donnerai quelques notions pour se connecter à une base à l'aide de chaîne de connection (*connection strings*), comment utiliser des *providers de tests...*.
|
||||
Il s'agira en conséquence d'exemples simples manquants d'explications sur certains points, car ils seront présentés plus tard.
|
||||
* [**ex_041_001 : Connection Strings**](ex_041_001_ConnectionStrings) : montre comment utiliser une chaîne de connexion SQL Server ou SQLite.
|
||||
* [**ex_041_004 : Testing in memory**](ex_041_004_TestingInMemory) : présente comment utiliser des fournisseurs en mémoire pour éviter la surchage de la création d'une base de données en particulier dans le cas de tests unitaires. Cet exemple est composé de 4 projets.
|
||||
2. *Model* :
|
||||
Ce chapitre s'attardera sur le lien entre le modèle et la base de données. En effet, avec EF, l'accès aux données se fait via le modèle, c'est-à-dire l'ensemble de vos classes (qui seront reliées à des tables créées plus ou moins automatiquement)
|
||||
ainsi qu'un contexte (```DbContext```) qui représentera une session de connexion avec votre (ou vos) base(s) de données.
|
||||
Je présenterai en conséquence tout d'abord comment écrire des classes pour votre modèle, puis comment écrire les différentes relations classiques (aggrégation, *one to one*, *one to many*, *many to many*, mais aussi les dictionnaires), comment gérer les héritages entre classes du modèle dans la base de données, etc.
|
||||
* [**ex_042_001 : conventions d'écriture**](ex_042_001_EF_CF_conventions) : explique quelles sont les conventions d'écriture utilisées pour la transformation d'une entité en table.
|
||||
* [**ex_042_002 : data annotations**](ex_042_002_EF_CF_data_annotations) : explique comment utiliser les *data annotations* pour personnaliser la transformation d'une entité en table.
|
||||
* [**ex_042_003 : Fluent API**](ex_042_003_EF_CF_Fluent_API) : explique comment utiliser la *Fluent API* pour personnaliser la transformation d'une entité en table.
|
||||
* [**ex_042_004 : Keys with conventions**](ex_042_004_Keys_conventions) : explique comment créer les clés primaires d'une entité lorsqu'on utilise les conventions d'écriture.
|
||||
* [**ex_042_005 : Keys with data annotations**](ex_042_005_Keys_data_annotations) : explique comment créer les clés primaires d'une entité lorsqu'on utilise les *data annotations*.
|
||||
* [**ex_042_006 : Keys with Fluent API**](ex_042_006_Keys_FluentAPI) : explique comment créer les clés primaires d'une entité lorsqu'on utilise la *Fluent API*.
|
||||
* [**ex_042_007 : Value Generation**](ex_042_007_ValueGeneration) : explique comment faire générer des valeurs automatiquement lors de l'insertion ou de la mise à jour
|
||||
* [**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 : 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, en utilisant les conventions d'écriture et/ou les annotations de données.
|
||||
* [**ex_042_011 : Single Property navigation with Fluent API**](ex_042_011_SinglePropertyNavigation_FluentAPI) : montre comment une relation d'association est traduite par *EF Core* lorsque cette association est unidirectionnelle entre deux entités, en utilisant la *FLuent API*.
|
||||
* [**ex_042_012 : One To One with data annotations**](ex_042_012_OneToOne_conventions) : montre comment une relation d'association *One To One* est traduite par *EF Core* lorsque cette association est bidirectionnelle entre deux entités, en utilisant l'*annotation de données*.
|
||||
* [**ex_042_013 : One To One with Fluent API**](ex_042_013_OneToOne_FluentAPI) : montre comment une relation d'association *One To One* est traduite par *EF Core* lorsque cette association est bidirectionnelle entre deux entités, en utilisant la *FluentAPI*.
|
||||
* [**ex_042_014 : One To Many with data annotations**](ex_042_014_OneToMany_dataAnnotations) : montre comment une relation d'association *One To Many* est traduite par *EF Core* en utilisant l'*annotation de données*.
|
||||
* [**ex_042_015 : One To Many with conventions**](ex_042_015_OneToMany_conventions) : montre comment une relation d'association *One To Many* est traduite par *EF Core* en utilisant les *conventions d'écriture*.
|
||||
* [**ex_042_016 : One To Many with Fluent API**](ex_042_016_OneToMany_FluentAPI) : montre comment une relation d'association *One To Many* est traduite par *EF Core* en utilisant la *Fluent API*.
|
||||
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* :
|
||||
*Language INtegrated Query* (LINQ) est un outil de requête sur collections et sa version LINQ to SQL vous permet de passer très facilement à un système de requêtes sur les bases de données.
|
||||
Les requêtes LINQ sont automatiquement traduites en requêtes SQL, vous évitant ainsi d'avoir à écrire vos requêtes vous-mêmes. Elles sont dès lors beaucoup plus lisibles et faciles à écrire.
|
||||
Ce chapitre présente comment charger des données, réaliser du filtrage, de manière synchrone ou asynchrone, etc.
|
||||
Il montre bien sûr également comment réaliser le symétrique : mettre à jour, supprimer ou ajouter de nouvelles données dans la base.
|
||||
5. *Database providers* :
|
||||
EF vous permet de gérer votre base de données indépendamment du *provider*. Ce chapitre montrera donc comment utiliser différents providers parmi lesquels Microsoft SQL Server, SQLite ou encore InMemory dont le but est de permettre de tester la base en mémoire, sans passer par un *provider*.
|
||||
|
||||
---
|
||||
|
||||
## Quelle version utiliser ?
|
||||
Ces exemples sont écrits pour .NET Core 3.0, mais vous pouvez utiliser EF Core avec différents types projets. Voici les recommendations actuelles de Microsoft quant à l'utilisation des version d'EF.
|
||||
|
||||
|**EF Core** |**1.x** |**2.x** |**3.x**
|
||||
|----------------|--------|-----------|---------------
|
||||
|.NET Standard |1.3 |2.0 |2.1
|
||||
|.NET Core |1.0 |2.0 |3.0
|
||||
|.NET Framework |4.5.1 |4.7.2 |(not supported)
|
||||
|Mono |4.6 |5.4 |6.4
|
||||
|Xamarin.iOS |10.0 |10.14 |12.16
|
||||
|Xamarin.Android |7.0 |8.0 |10.0
|
||||
|UWP |10.0 |10.0.16299 |to be defined
|
||||
|Unity |2018.1 |2018.1 |to be defined
|
||||
|
||||
*Comment lire ce tableau ?*
|
||||
|
||||
Si vous voulez utiliser EF Core 3.0 avec une bibliothèque de classes écrites en .NET Standard, celle-ci doit utiliser au moins .NET Standard 2.1.
|
||||
|
||||
Si vous voulez utiliser EF Core 3.0 avec un projet Xamarin.iOS, celui-ci doit être au moins en version 12.16.
|
||||
|
||||
Si vous voulez utiliser EF Core dans une application UWP, vous ne pouvez pour le moment utiliser que EF Core 1.x ou 2.x.
|
||||
|
||||
---
|
||||
*Note :*
|
||||
|
||||
Je n'ai pas l'intention de mettre à jour les exemples pour Entity Framework 6 ou pour .NET Framework, puisque la version 5 du framework va unifier .NET Framework et .NET Core. En conséquence, EF Core sera la nouvelle "norme".
|
||||
|
||||
|
||||
---
|
||||
## Comment commencer ?
|
||||
Un petit tutoriel rapide pour savoir comment créer un projet...
|
||||
##### Prérequis
|
||||
Il vous faut au moins la **[version 3.0 du SDK de .NET Core](https://dotnet.microsoft.com/download)**, mais celle-ci est certainement déjà installée si vous avez installé Visual Studio 2019 16.3 ou plus.
|
||||
##### Créez un nouveau projet
|
||||
Vous pouvez ensuite créer un nouveau projet .NET Core 3.x, pour cela :
|
||||
* lancez Visual Studio
|
||||
* créez un nouveau projet de type **Console App (.NET Core)** en C#
|
||||
|
||||
##### Installez EntityFramework Core
|
||||
Pour ce tutoriel, nous pouvons utiliser SqlServer comme *provider*.
|
||||
* Pour cela, cliquez droit sur le projet, puis sélectionnez *Gérer les packages NuGet...*.
|
||||
* Sous l'onglet *Parcourir*, dans la barre de recherche, rentrez *Microsoft.EntityFrameworkCore*
|
||||
* Sélectionnez le premier nuget dans sa version la plus récente et lancez l'installation.
|
||||
* Répétez les deux dernières opérations pour les packages :
|
||||
* *Microsoft.EntityFrameworkCore.Design*
|
||||
* *Microsoft.EntityFrameworkCore.SqlServer*
|
||||
* *Microsoft.EntityFrameworkCore.SqlServer.Design*
|
||||
|
||||
##### Créez un modèle
|
||||
* Ajoutez une nouvelle classe ```Nounours``` au projet.
|
||||
* Ajoutez le code suivant à cette classe :
|
||||
```csharp
|
||||
using System;
|
||||
|
||||
namespace tutoRapideEFCore
|
||||
{
|
||||
class Nounours
|
||||
{
|
||||
public int Id { get; set; }
|
||||
public string Nom { get; set; }
|
||||
public DateTime Naissance { get; set; }
|
||||
public int NbPoils { get; set; }
|
||||
}
|
||||
}
|
||||
```
|
||||
* Ajoutez une nouvelle classe ```NounoursContext``` qui servira de contexte à notre modèle.
|
||||
* Ajoutez le code suivante à cette classe :
|
||||
```csharp
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
namespace tutoRapideEFCore
|
||||
{
|
||||
class NounoursContext : DbContext
|
||||
{
|
||||
public DbSet<Nounours> Nounours { get; set; }
|
||||
|
||||
protected override void OnConfiguring(DbContextOptionsBuilder options)
|
||||
=> options.UseSqlServer(@"Server=(localdb)\mssqllocaldb;Database=myFirstDatabase.mdf;Trusted_Connection=True;");
|
||||
}
|
||||
}
|
||||
|
||||
```
|
||||
##### Créez la base de données
|
||||
* 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 (*eg* si votre projet s'apelle **tutoRapideEFCore**) :
|
||||
```
|
||||
cd tutoRapideEFCore
|
||||
```
|
||||
* tapez ensuite les commandes suivantes :
|
||||
```
|
||||
dotnet ef migrations add myFirstMigration
|
||||
dotnet ef database update
|
||||
```
|
||||
*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.
|
||||
|
||||
|
||||
##### Utilisez votre base de données via Entity Framework Core
|
||||
* Editez *Program.cs* et ajoutez le code suivant :
|
||||
```csharp
|
||||
static void Main(string[] args)
|
||||
{
|
||||
Nounours chewie = new Nounours { Nom = "Chewbacca", Naissance = new DateTime(1977, 5, 27), NbPoils = 1234567 };
|
||||
Nounours yoda = new Nounours { Nom = "Yoda", Naissance = new DateTime(1980, 5, 21), NbPoils = 3 };
|
||||
Nounours ewok = new Nounours { Nom = "Ewok", Naissance = new DateTime(1983, 5, 25), NbPoils = 3456789 };
|
||||
Nounours c3po = new Nounours { Nom = "C3PO", Naissance = new DateTime(1977, 5, 27), NbPoils = 0 };
|
||||
|
||||
using (var context = new NounoursContext())
|
||||
{
|
||||
// Crée des nounours et les insère dans la base
|
||||
Console.WriteLine("Creates and inserts new Nounours");
|
||||
context.Add(chewie);
|
||||
context.Add(yoda);
|
||||
context.Add(ewok);
|
||||
context.Add(c3po);
|
||||
context.SaveChanges();
|
||||
}
|
||||
}
|
||||
```
|
||||
Maintenant, lorsque vous lancerez l'application, la base de données contiendra 3 nounours. La base crée automatiquement des identifiants pour chaque Nounours.
|
||||
* Editez *Program.cs* pour rajouter les lignes suivantes à la fin de la méthode ```Main``` :
|
||||
```csharp
|
||||
// Lit le premier nounours de la base dont le nom commence par 'e'
|
||||
Console.WriteLine("Creates and executes a query retrieving the first Nounours of the database whose name starts with an \"e\":");
|
||||
var eNounours = context.Nounours
|
||||
.Where(n => n.Nom.StartsWith("e"))
|
||||
.First();
|
||||
Console.WriteLine($"{eNounours.Nom} (born in {eNounours.Naissance.Year})");
|
||||
```
|
||||
Cette requête LINQ vous permet de lire le premier nounours de la base de nounours triés par ordre alphabétique de noms.
|
||||
Ceci nécessite de rajouter au-début du fichier *Program.cs* la ligne suivante :
|
||||
```csharp
|
||||
using System.Linq;
|
||||
```
|
||||
* Editez *Program.cs* pour rajouter les lignes suivantes à la fin de la méthode ```Main``` :
|
||||
```csharp
|
||||
// Met à jour le nom du second nounours de la base
|
||||
Console.WriteLine("Updates the name of the second nounours");
|
||||
eNounours.Nom = "Wicket";
|
||||
context.SaveChanges();
|
||||
```
|
||||
Cette partie du code montre comment mettre à jour un élément de la base de données.
|
||||
* Editez *Program.cs* pour rajouter les lignes suivantes à la fin de la méthode ```Main``` :
|
||||
```csharp
|
||||
// récupère le nounours qui n'en est pas un et le supprime de la base
|
||||
Console.WriteLine("Deletes one item from de database");
|
||||
var droid = context.Nounours
|
||||
.SingleOrDefault(n => n.Nom.Equals("C3PO"));
|
||||
context.Remove(droid);
|
||||
context.SaveChanges();
|
||||
```
|
||||
Cette partie du code montre comment supprimer un élément de la base de données.
|
||||
|
||||
Voici un récapitulatif du fichier *Program.cs* :
|
||||
```csharp
|
||||
using System;
|
||||
using System.Linq;
|
||||
|
||||
namespace tutoRapideEFCore
|
||||
{
|
||||
class Program
|
||||
{
|
||||
static void Main(string[] args)
|
||||
{
|
||||
Nounours chewie = new Nounours { Nom = "Chewbacca", Naissance = new DateTime(1977, 5, 27), NbPoils = 1234567 };
|
||||
Nounours yoda = new Nounours { Nom = "Yoda", Naissance = new DateTime(1980, 5, 21), NbPoils = 3 };
|
||||
Nounours ewok = new Nounours { Nom = "Ewok", Naissance = new DateTime(1983, 5, 25), NbPoils = 3456789 };
|
||||
Nounours c3po = new Nounours { Nom = "C3PO", Naissance = new DateTime(1977, 5, 27), NbPoils = 0 };
|
||||
|
||||
using (var context = new NounoursContext())
|
||||
{
|
||||
// Crée des nounours et les insère dans la base
|
||||
Console.WriteLine("Creates and inserts new Nounours");
|
||||
context.Add(chewie);
|
||||
context.Add(yoda);
|
||||
context.Add(ewok);
|
||||
context.Add(c3po);
|
||||
context.SaveChanges();
|
||||
|
||||
// Lit le premier nounours de la base dont le nom commence par 'e'
|
||||
Console.WriteLine("Creates and executes a query retrieving the first Nounours of the database whose name starts with an \"e\":");
|
||||
var eNounours = context.Nounours
|
||||
.Where(n => n.Nom.StartsWith("e"))
|
||||
.First();
|
||||
Console.WriteLine($"{eNounours.Nom} (born in {eNounours.Naissance.Year})");
|
||||
|
||||
// Met à jour le nom du second nounours de la base
|
||||
Console.WriteLine("Updates the name of the second nounours");
|
||||
eNounours.Nom = "Wicket";
|
||||
context.SaveChanges();
|
||||
|
||||
// récupère le nounours qui n'en est pas un et le supprime de la base
|
||||
Console.WriteLine("Deletes one item from de database");
|
||||
var droid = context.Nounours
|
||||
.SingleOrDefault(n => n.Nom.Equals("C3PO"));
|
||||
context.Remove(droid);
|
||||
context.SaveChanges();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
* Exécutez votre application pour vérifier son bon fonctionnement.
|
||||
|
||||
##### Vérifiez le contenu de la base de données avec l'Explorateur d'objets 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*.
|
||||
* 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 : *myFirstDatabase.Nounours.mdf*
|
||||
* puis *Tables*
|
||||
* Faites un clic droit sur la table *dbo.Nounours* puis choisissez *Afficher les données*
|
||||
* Vous devriez maintenant pouvoir voir les données suivantes dans le tableau :
|
||||
|
||||
|Id |Nom |Naissance |NbPoils
|
||||
|---|---|---|---
|
||||
|1|Chewbacca|27/05/1977 00:00:00|1234567
|
||||
|2|Yoda|21/05/1980 00:00:00|3
|
||||
|3|Wicket|25/05/1983 00:00:00|3456789
|
||||
|
||||
Vous pouvez constater que l'Ewok a bien été renommé Wicket, et que C3PO a bien été supprimé.
|
||||
Notez qu'il est également possible d'utiliser l'*Explorateur d'objets SQL Server* pour ajouter, modifier ou supprimer des données dans les tables.
|
@ -0,0 +1,30 @@
|
||||
# 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)
|
@ -0,0 +1,147 @@
|
||||
# ex_041_001_ConnectionStrings
|
||||
*31/12/2019 ⋅ Marc Chevaldonné*
|
||||
*Dernière mise à jour : 09/01/2020 ⋅ Marc Chevaldonné*
|
||||
|
||||
---
|
||||
|
||||
Cet exemple a pour but de présenter les chaîne de connexion (*connection strings*).
|
||||
* Les *connection strings* servent à se connecter à une base de données.
|
||||
* Elles diffèrent en fonction des *providers*
|
||||
* Parfois, elles nécessitent des informations telles que un nom d'utilisateur et un mot de passe qu'on peut vouloir cacher
|
||||
|
||||
Dans cet exemple, j'ai voulu montrer deux *connection strings* : une pour SQL Server et une autre pour SQLite.
|
||||
|
||||
---
|
||||
## Configuration
|
||||
Il faut penser à ajouter les NuGet suivants :
|
||||
* Microsoft.EntityFrameworkCore : pour le projet en général
|
||||
* Microsoft.EntityFrameworkCore.SqlServer : pour le *provider* SQL Server
|
||||
* Microsoft.EntityFrameworkCore.Sqlite : pour le *provider* SQLite
|
||||
* Microsoft.EntityFrameworkCore.Tools : pour bénéficier des outils de Design, de migrations, etc.
|
||||
|
||||
|
||||
De plus, pour SQLite, il faut penser également à rajouter
|
||||
dans l'exemple :
|
||||
* le chemin du *starting working directory* : on peut le faire par exemple en modifiant le **.csproj** en ajoutant la ligne suivante :
|
||||
```xml
|
||||
<StartWorkingDirectory>$(MSBuildProjectDirectory)</StartWorkingDirectory>
|
||||
```
|
||||
|
||||
## Comment fonctionne l'exemple ?
|
||||
Ce projet contient les classes suivantes :
|
||||
* ```Nounours``` : elle est la classe du modèle que j'ai faite la plus simple possible. J'expliquerai dans un exemple ultérieure son mode de fonctionnement. Elle contient un ```Id```et un ```Nom``` qui donneront deux colonnes dans une table Nounours avec les mêmes noms.
|
||||
* ```SqlServerContext``` : première classe qui dérive de ```DbContext``` et qui va permettre de réaliser la connexion avec la base de données de type MSSqlServer
|
||||
* ```SQLiteContext``` : deuxième classe qui dérive de ```DbContext```et qui va permettre de réaliser la connexion avec la base de données de type SQLite.
|
||||
|
||||
Dans les deux classes qui dérivent de ```DbContext```, on doit donner une *connection string*. Celle-ci est donnée via la méthode protégée et virtuelle ```OnConfiguring```,
|
||||
via l'instance de ```DbContextOptionsBuilder```, à travers l'une des méthodes d'extension :
|
||||
* ```UseSqlServer``` : pour SqlServer, où on peut voir une *connection string* plus ou moins complexe indiquant qu'elle est en local (```Server=(localdb)\mssqllocaldb;```), ainsi que le nom de la base de données (```Database=ex_041_001_ConnectionStrings.Nounours.mdf;```)
|
||||
* ```UseSqlite``` : pour SQLite, où on peut voir le nom de la base de données ```Data Source=ex_041_001_ConnectionStrings.Nounours.db``` qui sera placée par défaut dans le dossier du projet si vous l'exécutez depuis Visual Studio.
|
||||
|
||||
C'est tout ce que cet exemple souhaite mettre en valeur : les chaînes de connexion.
|
||||
|
||||
## Comment générer et exécuter l'exemple ?
|
||||
Pour générer l'exemple, il vous faut d'abord préparer les migrations et les tables.
|
||||
|
||||
* (__Windows__ Visual Studio 2019) : 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*.
|
||||
* (__MacOSX__ Visual Studio 2019 For Mac) : ouvrez un Terminal.
|
||||
* Dans la console que vous venez d'ouvrir, déplacez-vous dans le dossier du projet, ici :
|
||||
```
|
||||
cd .\p08_BDD_EntityFramework\ex_041_001_ConnectionStrings
|
||||
```
|
||||
|
||||
*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.
|
||||
|
||||
|
||||
### Migrations
|
||||
*Note :* normalement, la commande pour effectuer une migration est :
|
||||
```
|
||||
dotnet ef migrations add monNomDeMigration
|
||||
```
|
||||
mais comme ici, nous sommes dans le cas particulier où nous avons deux contextes, nous devons préciser les noms des ```DbContext```à migrer :
|
||||
```
|
||||
dotnet ef migrations add ex_041_001_SqlServer --context SqlServerContext
|
||||
dotnet ef migrations add ex_041_001_SQLite --context SQLiteContext
|
||||
```
|
||||
*Note : sous MacOSX, n'utilisez que SQLite, soit :*
|
||||
```
|
||||
dotnet ef migrations add ex_041_001_SQLite --context SQLiteContext
|
||||
```
|
||||
### Création des tables
|
||||
Tapez ensuite les commandes suivantes :
|
||||
```
|
||||
dotnet ef database update --context SqlServerContext
|
||||
dotnet ef database update --context SQLiteContext
|
||||
```
|
||||
*Note : sous MacOSX, n'utilisez que SQLite, soit :*
|
||||
```
|
||||
dotnet ef database update --context SQLiteContext
|
||||
```
|
||||
### Génération et exécution
|
||||
Vous pouvez maintenant générer et exécuter l'exemple.
|
||||
|
||||
Le résultat de l'exécution peut donner :
|
||||
* sur Windows :
|
||||
```
|
||||
Creates and inserts new Nounours with SqlServer
|
||||
1 - Chewbacca
|
||||
2 - Yoda
|
||||
3 - Ewok
|
||||
Creates and inserts new Nounours with SQLite
|
||||
1 - Chewbacca
|
||||
2 - Yoda
|
||||
3 - Ewok
|
||||
```
|
||||
* sur MacOSX :
|
||||
```
|
||||
Creates and inserts new Nounours with SQLite
|
||||
1 - Chewbacca
|
||||
2 - Yoda
|
||||
3 - Ewok
|
||||
```
|
||||
|
||||
## Comment vérifier le contenu des bases de données SQL Server et SQLite ?
|
||||
### SqlServer (seulement sur Windows)
|
||||
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="./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 : *myFirstDatabase.Nounours.mdf*
|
||||
* puis *Tables*
|
||||
* Faites un clic droit sur la table *dbo.Nounours* puis choisissez *Afficher les données*
|
||||
<img src="./readmefiles/sqlserver_02.png" width="460"/>
|
||||
|
||||
* Vous devriez maintenant pouvoir voir les données suivantes dans le tableau :
|
||||
|
||||
|Id |Nom
|
||||
|---|---
|
||||
|1|Chewbacca
|
||||
|2|Yoda
|
||||
|3|Ewok
|
||||
|
||||
Notez qu'il est également possible d'utiliser l'*Explorateur d'objets SQL Server* pour ajouter, modifier ou supprimer des données dans les tables.
|
||||
|
||||
### SQLite (Windows et MacOSX)
|
||||
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_041_001_ConnectionStrings.Nounours.db* qui a été généré par l'exécution du programme et qui se trouve près de *ex_041_001_ConnectionStrings.csproj*.
|
||||
![DB Browser for SQLite](./readmefiles/dbbrowser_01.png)
|
||||
* Choisissez l'onglet *Parcourir les données*
|
||||
* Observez les résultats obtenus
|
||||
![DB Browser for SQLite](./readmefiles/dbbrowser_02.png)
|
||||
|
||||
|
||||
|
||||
|
||||
---
|
||||
Copyright © 2019-2020 Marc Chevaldonné
|
@ -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.SqlServer" Version="3.1.0" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="3.1.0" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="3.1.0" />
|
||||
</ItemGroup>
|
||||
</Project>
|
After Width: | Height: | Size: 76 KiB |
After Width: | Height: | Size: 72 KiB |
After Width: | Height: | Size: 61 KiB |
After Width: | Height: | Size: 78 KiB |
@ -1,21 +1,20 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>netcoreapp3.0</TargetFramework>
|
||||
<AssemblyName>ex_042_006_EF_CF_One_to_One</AssemblyName>
|
||||
<OutputType>Exe</OutputType>
|
||||
<PackageId>ex_042_006_EF_CF_One_to_One</PackageId>
|
||||
<RuntimeFrameworkVersion>3.0.1</RuntimeFrameworkVersion>
|
||||
<GenerateAssemblyConfigurationAttribute>false</GenerateAssemblyConfigurationAttribute>
|
||||
<GenerateAssemblyCompanyAttribute>false</GenerateAssemblyCompanyAttribute>
|
||||
<GenerateAssemblyProductAttribute>false</GenerateAssemblyProductAttribute>
|
||||
<TargetFramework>netcoreapp3.0</TargetFramework>
|
||||
<StartWorkingDirectory>$(MSBuildProjectDirectory)</StartWorkingDirectory>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="3.1.0" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" Version="3.1.0" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer.Design" Version="1.1.6" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="3.1.0">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="3.1.0" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\ex_041_004_TestingInMemory\ex_041_004_TestingInMemory.csproj" />
|
||||
</ItemGroup>
|
||||
</Project>
|
@ -0,0 +1,557 @@
|
||||
# ex_041_004_TestingInMemory
|
||||
*02/01/2020 ⋅ Marc Chevaldonné*
|
||||
*Dernière mise à jour : 09/01/2020 ⋅ Marc Chevaldonné*
|
||||
|
||||
---
|
||||
|
||||
Lorsqu'on cherche à tester notre code et nos accès à la base de données, on n'a pas nécessairement envie de créer la base juste pour les tests.
|
||||
Pour cela, il existe des solutions et des fournisseurs permettant de tester les bases sans avoir à réellement les créer :
|
||||
* le fournisseur **InMemory** permet ceci mais de manière approximative, car **InMemory** n'est pas une base de données relationnelle : il y a donc des limitations.
|
||||
* **SQLite** possède un mode *In-Memory* qui lui, permet de tester une base de données relationnelle, sans avoir à créer une base de données.
|
||||
|
||||
**Je conseille donc l'utilisation de _SQL in Memory_ plutôt que InMemory, puisqu'il permet de tester une base relationnelle.**
|
||||
|
||||
Cet exemple montre comment utiliser **InMemory** et **SQLite in-memory** à travers une injection de dépendance. En d'autres termes, vous continuez à définir votre chaîne de connexion sur une base de données, mais vous permettez néanmoins l'utilisation, à la demande, de **InMemory** pour des tests.
|
||||
Puisque ce fournisseur devient intéressant dans le cas de tests, j'ai donc ajouté un 2ème projet lié à cet exemple, permettant d'avoir accès à des tests unitaires utilisant **InMemory** ou **SQLite in-memory**.
|
||||
Pour le reste de l'exemple, celui-ci n'apporte rien de nouveau par rapport à l'exemple ex_041_001 concernant l'utilisation d'**Entity Framework Core**.
|
||||
|
||||
---
|
||||
|
||||
## Pourquoi autant de projets dans cet exemple ?
|
||||
|
||||
Quatre projets constituent cet exemple :
|
||||
* **ex_041_004_TestingInMemory** est une bibliothèque de classes .NET Standard contentant le modèle, ie la classe ```Nounours``` et la classe ```NounoursContext```
|
||||
* **ex_041_004_ConsoleTests_w_SqlServer** est une application console .NET Core qui prouve le fonctionnement "normal" de la base de données (ie comme dans l'exemple ex_041_001) en exploitant la bibliothèque de classes **ex_041_004_TestingInMemory** (ne fonctionne que sur Windows)
|
||||
* **ex_041_004_ConsoleTests_w_SQLite** est une application console .NET Core qui prouve le fonctionnement "normal" de la base de données (ie comme dans l'exemple ex_041_001) en exploitant la bibliothèque de classes **ex_041_004_TestingInMemory** (cross-platform)
|
||||
* **ex_041_004_UnitTests_w_InMemory** est une application de tests unitaires xUnit exploitant le fournisseur **InMemory** et la bibliothèque de classes **ex_041_004_TestingInMemory**
|
||||
* **ex_041_004_UnitTests_w_SQLiteInMemory** est une application de tests unitaires xUnit exploitant le fournisseur **SQLite in memory** et la bibliothèque de classes **ex_041_004_TestingInMemory**
|
||||
|
||||
Vous pouvez donc exécuter cet exemple de quatre manières :
|
||||
* via **ex_041_004_ConsoleTests_w_SqlServer** comme dans l'exemple ex_041_001 avec ```dotnet ef``` (seulement sur Windows)
|
||||
* via **ex_041_004_ConsoleTests_w_SQLite** comme dans l'exemple ex_041_001 avec ```dotnet ef```
|
||||
* via les tests unitaires de **ex_041_004_UnitTests_w_InMemory**
|
||||
* via les tests unitaires de **ex_041_004_UnitTests_w_SQLiteInMemory**
|
||||
|
||||
## Comment a été construit cet exemple ?
|
||||
|
||||
### bibliothèque .NET Standard ex_041_004_TestingInMemory
|
||||
Cet exemple est tout d'abord construit de la même manière que l'exemple *ex_041_001_ConnectionStrings*.
|
||||
Il ne faut pas oublier les NuGet nécessaires :
|
||||
* Microsoft.EntityFrameworkCore : pour le projet en général
|
||||
* Microsoft.EntityFrameworkCore.SqlServer : pour le *provider* SQL Server
|
||||
* Microsoft.EntityFrameworkCore.Tools : pour bénéficier des outils de Design, de migrations, etc.
|
||||
|
||||
J'ai ensuite décidé de renommer ma classe dérivant de ```DbContext``` en ```NounoursContext``` car je n'ai plus de raison de faire la différence entre SqlServer et SQLite.
|
||||
On obtient ainsi la classe ```NounoursContext``` suivante :
|
||||
```csharp
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
namespace ex_041_004_TestingInMemory
|
||||
{
|
||||
public class NounoursContext : DbContext
|
||||
{
|
||||
public DbSet<Nounours> Nounours { get; set; }
|
||||
|
||||
protected override void OnConfiguring(DbContextOptionsBuilder options)
|
||||
=> options.UseSqlServer(@"Server=(localdb)\mssqllocaldb;Database=ex_041_004_TestingInMemory.Nounours.mdf;Trusted_Connection=True;");
|
||||
}
|
||||
}
|
||||
```
|
||||
On la modifie pour permettre d'injecter un autre fournisseur, tout en gardant celui-ci par défaut. On peut utiliser pour cela la propriété ```IsConfigured``` sur ```DbContextOptionsBuilder```.
|
||||
La méthode ```OnConfiguring``` de ```NounoursContext``` est alors modifiée de la manière suivante :
|
||||
```csharp
|
||||
protected override void OnConfiguring(DbContextOptionsBuilder options)
|
||||
{
|
||||
if (!options.IsConfigured)
|
||||
{
|
||||
options.UseSqlServer(@"Server=(localdb)\mssqllocaldb;Database=ex_041_004_TestingInMemory.Nounours.mdf;Trusted_Connection=True;");
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Dès lors, si rien n'est configuré, c'est donc le fournisseur *SqlServer* qui sera utilisé.
|
||||
Mais nous pouvons aussi permettre à l'utilisateur d'en injecter un autre. Pour cela, nous ajoutons deux constructeurs à notre classe :
|
||||
* ```NounoursContext()``` : le constructeur par défaut ne fait rien, et en conséquence fera que la configuration *SqlServer* sera utilisée,
|
||||
* ```NounoursContext(DbContextOptions<NounoursContext> options)``` : ce constructeur par défaut permettra d'injecter une autre fabrique de fournisseur, et permettra à l'utilisateur d'injecter n'importe quel autre fournisseur, dont **InMemory**.
|
||||
|
||||
Les constructeurs injectés sont donc :
|
||||
```csharp
|
||||
public NounoursContext()
|
||||
{ }
|
||||
|
||||
public NounoursContext(DbContextOptions<NounoursContext> options)
|
||||
: base(options)
|
||||
{ }
|
||||
```
|
||||
La classe ```NounoursContext``` peut donc s'utiliser comme précédemment, sans changement, et la base *SqlServer* sera utilisée ; ou alors, on pourra injecter un autre fournisseur.
|
||||
|
||||
### application console .NET Core ex_041_004_ConsoleTests_w_SqlServer (seulement pour Windows)
|
||||
|
||||
L'application console .NET Core **ex_041_004_ConsoleTests_w_SqlServer** fait référence à la bibliothèque .NET Standard précédente pour pouvoir consommer ```Nounours``` et ```NounoursContext```.
|
||||
Sa seule classe est donc ```Program``` et peut donc être codée de la manière suivante :
|
||||
```csharp
|
||||
using System;
|
||||
using ex_041_004_TestingInMemory;
|
||||
|
||||
namespace ex_041_004_ConsoleTests_w_SqlServer
|
||||
{
|
||||
class Program
|
||||
{
|
||||
static void Main(string[] args)
|
||||
{
|
||||
Nounours chewie = new Nounours { Nom = "Chewbacca" };
|
||||
Nounours yoda = new Nounours { Nom = "Yoda" };
|
||||
Nounours ewok = new Nounours { Nom = "Ewok" };
|
||||
|
||||
using (var context = new NounoursContext())
|
||||
{
|
||||
// Crée des nounours et les insère dans la base
|
||||
Console.WriteLine("Creates and inserts new Nounours");
|
||||
context.Add(chewie);
|
||||
context.Add(yoda);
|
||||
context.Add(ewok);
|
||||
context.SaveChanges();
|
||||
}
|
||||
|
||||
using (var context = new NounoursContext())
|
||||
{
|
||||
foreach(var n in context.Nounours)
|
||||
{
|
||||
Console.WriteLine($"{n.Id} - {n.Nom}");
|
||||
}
|
||||
context.SaveChanges();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
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_041_004_ConsoleTests_w_SqlServer
|
||||
```
|
||||
*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 : comme la classe dérivant de ```DbContext``` n'est pas dans l'application Console, nous devons préciser dans quel projet elle se trouve en ajoutant ```--project ../ex_041_004_TestingInMemory```.
|
||||
```
|
||||
dotnet ef migrations add migration_ex_041_004 --project ../ex_041_004_TestingInMemory
|
||||
```
|
||||
* Création de la table : comme pour la migration, il faut préciser dans quel projet se trouve l'instance de ```DbContext```.
|
||||
```
|
||||
dotnet ef database update --project ../ex_041_004_TestingInMemory
|
||||
```
|
||||
* Génération et exécution
|
||||
Vous pouvez maintenant générer et exécuter l'exemple **ex_041_004_ConsoleTests_w_SqlServer**.
|
||||
|
||||
* Le résultat de l'exécution doit ressembler à :
|
||||
```
|
||||
Creates and inserts new Nounours
|
||||
1 - Chewbacca
|
||||
2 - Yoda
|
||||
3 - Ewok
|
||||
```
|
||||
* 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_041_004_TestingInMemory.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 :
|
||||
|
||||
|Id |Nom
|
||||
|---|---
|
||||
|1|Chewbacca
|
||||
|2|Yoda
|
||||
|3|Ewok
|
||||
|
||||
### application console .NET Core ex_041_004_ConsoleTests_w_SQLite
|
||||
|
||||
L'application console .NET Core **ex_041_004_ConsoleTests_w_SQLite** fait référence à la bibliothèque .NET Standard précédente pour pouvoir consommer ```Nounours``` et ```SQLiteNounoursContext```.
|
||||
Ses deux seules classes sont donc ```Program``` et ```SQLiteNounoursContext``` et sont codées de la manière suivante :
|
||||
```csharp
|
||||
using System;
|
||||
using ex_041_004_TestingInMemory;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
namespace ex_041_004_ConsoleTests_w_SQLite
|
||||
{
|
||||
class Program
|
||||
{
|
||||
static void Main(string[] args)
|
||||
{
|
||||
Nounours chewie = new Nounours { Nom = "Chewbacca" };
|
||||
Nounours yoda = new Nounours { Nom = "Yoda" };
|
||||
Nounours ewok = new Nounours { Nom = "Ewok" };
|
||||
|
||||
using (var context = new SQLiteNounoursContext())
|
||||
{
|
||||
// Crée des nounours et les insère dans la base
|
||||
Console.WriteLine("Creates and inserts new Nounours");
|
||||
context.Add(chewie);
|
||||
context.Add(yoda);
|
||||
context.Add(ewok);
|
||||
context.SaveChanges();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public class SQLiteNounoursContext : NounoursContext
|
||||
{
|
||||
protected override void OnConfiguring(DbContextOptionsBuilder options)
|
||||
{
|
||||
if(!options.IsConfigured)
|
||||
{
|
||||
options.UseSqlite($"Data Source=ex_041_004_SQLite.Nounours.db");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
La classe ```SQLiteNounoursContext``` a pour but de permettre l'appel de ```dotnet ef``` sans avoir à utiliser le ```NounoursContext``` qui utilise SqlServer. En effet, pour pouvoir
|
||||
mettre à jour la base SQLite, EFCore demande pour le moment, un ```DbContext``` correspondant à la base à mettre à jour dans la méthode ```OnConfiguring```.
|
||||
|
||||
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*.
|
||||
Ou bien ouvrez le terminal (sous MacOSX)
|
||||
* Dans la console ou le terminal que vous venez d'ouvrir, déplacez-vous dans le dossier du projet .NET Core, ici :
|
||||
```
|
||||
cd .\p08_BDD_EntityFramework\ex_041_004_ConsoleTests_w_SQLite
|
||||
```
|
||||
*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 : comme la classe dérivant de ```DbContext``` se trouve dans l'application Console, nous n'avons pas à préciser dans quel projet elle se trouve.
|
||||
```
|
||||
dotnet ef migrations add migration_ex_041_004 --project ../ex_041_004_TestingInMemory
|
||||
```
|
||||
* Création de la table :
|
||||
```
|
||||
dotnet ef database update --project ../ex_041_004_TestingInMemory
|
||||
```
|
||||
* Génération et exécution
|
||||
Vous pouvez maintenant générer et exécuter l'exemple **ex_041_004_ConsoleTests_w_SQLite**.
|
||||
|
||||
* Le résultat de l'exécution doit ressembler à :
|
||||
```
|
||||
Creates and inserts new Nounours
|
||||
1 - Chewbacca
|
||||
2 - Yoda
|
||||
3 - Ewok
|
||||
```
|
||||
* Comment vérifier le contenu des bases de données SQLite ?
|
||||
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_041_004_ConsoleTests_w_SQLite.Nounours.db* qui a été généré par l'exécution du programme et qui se trouve près de *ex_041_004_ConsoleTests_w_SQLite.csproj*.
|
||||
![DB Browser for SQLite](./readmefiles/dbbrowser_01.png)
|
||||
* Choisissez l'onglet *Parcourir les données*
|
||||
* Observez les résultats obtenus
|
||||
![DB Browser for SQLite](./readmefiles/dbbrowser_02.png)
|
||||
* Vous devriez maintenant pouvoir voir les données suivantes dans le tableau :
|
||||
|
||||
|Id |Nom
|
||||
|---|---
|
||||
|1|Chewbacca
|
||||
|2|Yoda
|
||||
|3|Ewok
|
||||
|
||||
### Configuration des tests unitaires avec InMemory
|
||||
Le test unitaire est de type *xUnit* et va permettre d'injecter le fournisseur **InMemory**.
|
||||
* On crée un nouveau projet de tests unitaires (*xUnit*)
|
||||
* On lui ajoute le package NuGet : *Microsoft.EntityFrameworkCore.InMemory*
|
||||
* On ajoute également une référence au projet précédent (*ex_041_004_InMemory.exe*)
|
||||
* On peut ensuite écrire un premier test comme suit :
|
||||
```csharp
|
||||
using ex_041_004_InMemory;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using System.Linq;
|
||||
using Xunit;
|
||||
|
||||
namespace ex_041_004_UnitTests
|
||||
{
|
||||
public class NounoursDB_Tests
|
||||
{
|
||||
[Fact]
|
||||
public void Add_Test()
|
||||
{
|
||||
var options = new DbContextOptionsBuilder<NounoursContext>()
|
||||
.UseInMemoryDatabase(databaseName: "Add_Test_database")
|
||||
.Options;
|
||||
|
||||
//prepares the database with one instance of the context
|
||||
using (var context = new NounoursContext(options))
|
||||
{
|
||||
Nounours chewie = new Nounours { Nom = "Chewbacca" };
|
||||
Nounours yoda = new Nounours { Nom = "Yoda" };
|
||||
Nounours ewok = new Nounours { Nom = "Ewok" };
|
||||
|
||||
context.Nounours.Add(chewie);
|
||||
context.Nounours.Add(yoda);
|
||||
context.Nounours.Add(ewok);
|
||||
context.SaveChanges();
|
||||
}
|
||||
|
||||
//prepares the database with one instance of the context
|
||||
using (var context = new NounoursContext(options))
|
||||
{
|
||||
Assert.Equal(3, context.Nounours.Count());
|
||||
Assert.Equal("Chewbacca", context.Nounours.First().Nom);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Ce premier test permet d'ajouter 3 nounours et :
|
||||
* de vérifier qu'il y a bien trois nounours ajoutés
|
||||
```csharp
|
||||
Assert.Equal(3, context.Nounours.Count());
|
||||
```
|
||||
* de vérifier que le premier s'appelle bien Chewbacca :
|
||||
```csharp
|
||||
Assert.Equal("Chewbacca", context.Nounours.First().Nom);
|
||||
```
|
||||
|
||||
Notez que le choix du fournisseur est bien fait au démarrage du test avec la création du ```DbContextOptionsBuilder``` :
|
||||
```csharp
|
||||
var options = new DbContextOptionsBuilder<NounoursContext>()
|
||||
.UseInMemoryDatabase(databaseName: "Add_Test_database")
|
||||
.Options;
|
||||
```
|
||||
et que l'injection est effectuée plus bas :
|
||||
```csharp
|
||||
using (var context = new NounoursContext(options))
|
||||
{
|
||||
//...
|
||||
}
|
||||
```
|
||||
|
||||
On peut ensuite ajouter un autre test, par exemple :
|
||||
```csharp
|
||||
[Fact]
|
||||
public void Modify_Test()
|
||||
{
|
||||
var options = new DbContextOptionsBuilder<NounoursContext>()
|
||||
.UseInMemoryDatabase(databaseName: "Modify_Test_database")
|
||||
.Options;
|
||||
|
||||
//prepares the database with one instance of the context
|
||||
using (var context = new NounoursContext(options))
|
||||
{
|
||||
Nounours chewie = new Nounours { Nom = "Chewbacca" };
|
||||
Nounours yoda = new Nounours { Nom = "Yoda" };
|
||||
Nounours ewok = new Nounours { Nom = "Ewok" };
|
||||
|
||||
context.Nounours.Add(chewie);
|
||||
context.Nounours.Add(yoda);
|
||||
context.Nounours.Add(ewok);
|
||||
context.SaveChanges();
|
||||
}
|
||||
|
||||
//prepares the database with one instance of the context
|
||||
using (var context = new NounoursContext(options))
|
||||
{
|
||||
string nameToFind = "ew";
|
||||
Assert.Equal(2, context.Nounours.Where(n => n.Nom.ToLower().Contains(nameToFind)).Count());
|
||||
nameToFind = "ewo";
|
||||
Assert.Equal(1, context.Nounours.Where(n => n.Nom.ToLower().Contains(nameToFind)).Count());
|
||||
var ewok = context.Nounours.Where(n => n.Nom.ToLower().Contains(nameToFind)).First();
|
||||
ewok.Nom = "Wicket";
|
||||
context.SaveChanges();
|
||||
}
|
||||
|
||||
//prepares the database with one instance of the context
|
||||
using (var context = new NounoursContext(options))
|
||||
{
|
||||
string nameToFind = "ew";
|
||||
Assert.Equal(1, context.Nounours.Where(n => n.Nom.ToLower().Contains(nameToFind)).Count());
|
||||
nameToFind = "wick";
|
||||
Assert.Equal(1, context.Nounours.Where(n => n.Nom.ToLower().Contains(nameToFind)).Count());
|
||||
}
|
||||
}
|
||||
```
|
||||
Ce cas de test :
|
||||
* vérifie d'abord qu'il y a deux nounours dont le nom contient la chaîne "ew",
|
||||
```csharp
|
||||
string nameToFind = "ew";
|
||||
Assert.Equal(2, context.Nounours.Where(n => n.Nom.ToLower().Contains(nameToFind)).Count());
|
||||
```
|
||||
* vérifie qu'il y a un nounours dont le nom contient la chaîne "ewo"
|
||||
```csharp
|
||||
nameToFind = "ewo";
|
||||
Assert.Equal(1, context.Nounours.Where(n => n.Nom.ToLower().Contains(nameToFind)).Count());
|
||||
```
|
||||
* modifie le nom de ce nounours en "Wicket"
|
||||
```csharp
|
||||
var ewok = context.Nounours.Where(n => n.Nom.ToLower().Contains(nameToFind)).First();
|
||||
ewok.Nom = "Wicket";
|
||||
```
|
||||
* enregistre les changements
|
||||
```csharp
|
||||
context.SaveChanges();
|
||||
```
|
||||
* vérifie ensuite qu'il n'y a plus qu'un Nounours dont le nom contient la chaîne "ew"
|
||||
```csharp
|
||||
string nameToFind = "ew";
|
||||
Assert.Equal(1, context.Nounours.Where(n => n.Nom.ToLower().Contains(nameToFind)).Count());
|
||||
```
|
||||
* vérifie qu'il y a un Nounours dont le nom contient la chaîne "wick"
|
||||
```csharp
|
||||
nameToFind = "wick";
|
||||
Assert.Equal(1, context.Nounours.Where(n => n.Nom.ToLower().Contains(nameToFind)).Count());
|
||||
```
|
||||
|
||||
### Configuration des tests unitaires avec SQLite in memory
|
||||
Le projet se construit exactement da la même manière que le précédent à quelques exceptions près que voici.
|
||||
* package NuGet :
|
||||
à la place du NuGet *Microsoft.EntityFrameworkCore.InMemory*, il faut ajouter *Microsoft.EntityFrameworkCore.Sqlite*
|
||||
* ouverture de la connexion :
|
||||
au début des tests, il faut penser à ouvrir la connexion avec la base en mémoire SQLite qui doit rester ouverte durant tout le test.
|
||||
```csharp
|
||||
//connection must be opened to use In-memory database
|
||||
var connection = new SqliteConnection("DataSource=:memory:");
|
||||
connection.Open();
|
||||
```
|
||||
* avant de commencer à traiter avec la base en mémoire, on peut vérifier qu'elle a bien été créée :
|
||||
```csharp
|
||||
//context.Database.OpenConnection();
|
||||
context.Database.EnsureCreated();
|
||||
```
|
||||
Au final, les tests ressemblent à :
|
||||
```csharp
|
||||
using ex_041_004_TestingInMemory;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using System.Linq;
|
||||
using Xunit;
|
||||
using Microsoft.Data.Sqlite;
|
||||
|
||||
|
||||
namespace ex_041_004_UnitTests_w_SQLiteInMemory
|
||||
{
|
||||
public class NounoursDB_Tests
|
||||
{
|
||||
[Fact]
|
||||
public void Add_Test()
|
||||
{
|
||||
//connection must be opened to use In-memory database
|
||||
var connection = new SqliteConnection("DataSource=:memory:");
|
||||
connection.Open();
|
||||
|
||||
var options = new DbContextOptionsBuilder<NounoursContext>()
|
||||
.UseSqlite(connection)
|
||||
.Options;
|
||||
|
||||
//prepares the database with one instance of the context
|
||||
using (var context = new NounoursContext(options))
|
||||
{
|
||||
//context.Database.OpenConnection();
|
||||
context.Database.EnsureCreated();
|
||||
|
||||
Nounours chewie = new Nounours { Nom = "Chewbacca" };
|
||||
Nounours yoda = new Nounours { Nom = "Yoda" };
|
||||
Nounours ewok = new Nounours { Nom = "Ewok" };
|
||||
|
||||
context.Nounours.Add(chewie);
|
||||
context.Nounours.Add(yoda);
|
||||
context.Nounours.Add(ewok);
|
||||
context.SaveChanges();
|
||||
}
|
||||
|
||||
//uses another instance of the context to do the tests
|
||||
using (var context = new NounoursContext(options))
|
||||
{
|
||||
context.Database.EnsureCreated();
|
||||
|
||||
Assert.Equal(3, context.Nounours.Count());
|
||||
Assert.Equal("Chewbacca", context.Nounours.First().Nom);
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Modify_Test()
|
||||
{
|
||||
//connection must be opened to use In-memory database
|
||||
var connection = new SqliteConnection("DataSource=:memory:");
|
||||
connection.Open();
|
||||
|
||||
var options = new DbContextOptionsBuilder<NounoursContext>()
|
||||
.UseSqlite(connection)
|
||||
.Options;
|
||||
|
||||
//prepares the database with one instance of the context
|
||||
using (var context = new NounoursContext(options))
|
||||
{
|
||||
//context.Database.OpenConnection();
|
||||
context.Database.EnsureCreated();
|
||||
|
||||
Nounours chewie = new Nounours { Nom = "Chewbacca" };
|
||||
Nounours yoda = new Nounours { Nom = "Yoda" };
|
||||
Nounours ewok = new Nounours { Nom = "Ewok" };
|
||||
|
||||
context.Nounours.Add(chewie);
|
||||
context.Nounours.Add(yoda);
|
||||
context.Nounours.Add(ewok);
|
||||
context.SaveChanges();
|
||||
}
|
||||
|
||||
//uses another instance of the context to do the tests
|
||||
using (var context = new NounoursContext(options))
|
||||
{
|
||||
context.Database.EnsureCreated();
|
||||
|
||||
string nameToFind = "ew";
|
||||
Assert.Equal(2, context.Nounours.Where(n => n.Nom.ToLower().Contains(nameToFind)).Count());
|
||||
nameToFind = "wo";
|
||||
Assert.Equal(1, context.Nounours.Where(n => n.Nom.ToLower().Contains(nameToFind)).Count());
|
||||
var ewok = context.Nounours.Where(n => n.Nom.ToLower().Contains(nameToFind)).First();
|
||||
ewok.Nom = "Wicket";
|
||||
context.SaveChanges();
|
||||
}
|
||||
|
||||
//uses another instance of the context to do the tests
|
||||
using (var context = new NounoursContext(options))
|
||||
{
|
||||
context.Database.EnsureCreated();
|
||||
|
||||
string nameToFind = "ew";
|
||||
Assert.Equal(1, context.Nounours.Where(n => n.Nom.ToLower().Contains(nameToFind)).Count());
|
||||
nameToFind = "wick";
|
||||
Assert.Equal(1, context.Nounours.Where(n => n.Nom.ToLower().Contains(nameToFind)).Count());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### exécution des tests unitaires
|
||||
Vous pouvez maintenant exécuter les tests unitaires via l'*Eplorateur de tests*.
|
||||
* Dans le menu *Test*, choisissez *Explorateur de tests*
|
||||
<img src="./readmefiles/readme_01.png" width="460"/>
|
||||
* Cliquez sur "Exécuter tous les tests"
|
||||
<img src="./readmefiles/readme_02.png" width="460"/>
|
||||
* Observez le bon fonctionnement
|
||||
|
||||
|
||||
---
|
||||
Copyright © 2019-2020 Marc Chevaldonné
|
@ -0,0 +1,11 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>netstandard2.0</TargetFramework>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="3.1.0" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" Version="3.1.0" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="3.1.0" />
|
||||
</ItemGroup>
|
||||
</Project>
|
After Width: | Height: | Size: 34 KiB |
After Width: | Height: | Size: 100 KiB |
@ -0,0 +1,80 @@
|
||||
using ex_041_004_TestingInMemory;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using System.Linq;
|
||||
using Xunit;
|
||||
|
||||
namespace ex_041_004_UnitTests_w_InMemory
|
||||
{
|
||||
public class NounoursDB_Tests
|
||||
{
|
||||
[Fact]
|
||||
public void Add_Test()
|
||||
{
|
||||
var options = new DbContextOptionsBuilder<NounoursContext>()
|
||||
.UseInMemoryDatabase(databaseName: "Add_Test_database")
|
||||
.Options;
|
||||
|
||||
//prepares the database with one instance of the context
|
||||
using (var context = new NounoursContext(options))
|
||||
{
|
||||
Nounours chewie = new Nounours { Nom = "Chewbacca" };
|
||||
Nounours yoda = new Nounours { Nom = "Yoda" };
|
||||
Nounours ewok = new Nounours { Nom = "Ewok" };
|
||||
|
||||
context.Nounours.Add(chewie);
|
||||
context.Nounours.Add(yoda);
|
||||
context.Nounours.Add(ewok);
|
||||
context.SaveChanges();
|
||||
}
|
||||
|
||||
//prepares the database with one instance of the context
|
||||
using (var context = new NounoursContext(options))
|
||||
{
|
||||
Assert.Equal(3, context.Nounours.Count());
|
||||
Assert.Equal("Chewbacca", context.Nounours.First().Nom);
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Modify_Test()
|
||||
{
|
||||
var options = new DbContextOptionsBuilder<NounoursContext>()
|
||||
.UseInMemoryDatabase(databaseName: "Modify_Test_database")
|
||||
.Options;
|
||||
|
||||
//prepares the database with one instance of the context
|
||||
using (var context = new NounoursContext(options))
|
||||
{
|
||||
Nounours chewie = new Nounours { Nom = "Chewbacca" };
|
||||
Nounours yoda = new Nounours { Nom = "Yoda" };
|
||||
Nounours ewok = new Nounours { Nom = "Ewok" };
|
||||
|
||||
context.Nounours.Add(chewie);
|
||||
context.Nounours.Add(yoda);
|
||||
context.Nounours.Add(ewok);
|
||||
context.SaveChanges();
|
||||
}
|
||||
|
||||
//prepares the database with one instance of the context
|
||||
using (var context = new NounoursContext(options))
|
||||
{
|
||||
string nameToFind = "ew";
|
||||
Assert.Equal(2, context.Nounours.Where(n => n.Nom.ToLower().Contains(nameToFind)).Count());
|
||||
nameToFind = "ewo";
|
||||
Assert.Equal(1, context.Nounours.Where(n => n.Nom.ToLower().Contains(nameToFind)).Count());
|
||||
var ewok = context.Nounours.Where(n => n.Nom.ToLower().Contains(nameToFind)).First();
|
||||
ewok.Nom = "Wicket";
|
||||
context.SaveChanges();
|
||||
}
|
||||
|
||||
//prepares the database with one instance of the context
|
||||
using (var context = new NounoursContext(options))
|
||||
{
|
||||
string nameToFind = "ew";
|
||||
Assert.Equal(1, context.Nounours.Where(n => n.Nom.ToLower().Contains(nameToFind)).Count());
|
||||
nameToFind = "wick";
|
||||
Assert.Equal(1, context.Nounours.Where(n => n.Nom.ToLower().Contains(nameToFind)).Count());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,102 @@
|
||||
using ex_041_004_TestingInMemory;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using System.Linq;
|
||||
using Xunit;
|
||||
using Microsoft.Data.Sqlite;
|
||||
|
||||
|
||||
namespace ex_041_004_UnitTests_w_SQLiteInMemory
|
||||
{
|
||||
public class NounoursDB_Tests
|
||||
{
|
||||
[Fact]
|
||||
public void Add_Test()
|
||||
{
|
||||
//connection must be opened to use In-memory database
|
||||
var connection = new SqliteConnection("DataSource=:memory:");
|
||||
connection.Open();
|
||||
|
||||
var options = new DbContextOptionsBuilder<NounoursContext>()
|
||||
.UseSqlite(connection)
|
||||
.Options;
|
||||
|
||||
//prepares the database with one instance of the context
|
||||
using (var context = new NounoursContext(options))
|
||||
{
|
||||
//context.Database.OpenConnection();
|
||||
context.Database.EnsureCreated();
|
||||
|
||||
Nounours chewie = new Nounours { Nom = "Chewbacca" };
|
||||
Nounours yoda = new Nounours { Nom = "Yoda" };
|
||||
Nounours ewok = new Nounours { Nom = "Ewok" };
|
||||
|
||||
context.Nounours.Add(chewie);
|
||||
context.Nounours.Add(yoda);
|
||||
context.Nounours.Add(ewok);
|
||||
context.SaveChanges();
|
||||
}
|
||||
|
||||
//uses another instance of the context to do the tests
|
||||
using (var context = new NounoursContext(options))
|
||||
{
|
||||
context.Database.EnsureCreated();
|
||||
|
||||
Assert.Equal(3, context.Nounours.Count());
|
||||
Assert.Equal("Chewbacca", context.Nounours.First().Nom);
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Modify_Test()
|
||||
{
|
||||
//connection must be opened to use In-memory database
|
||||
var connection = new SqliteConnection("DataSource=:memory:");
|
||||
connection.Open();
|
||||
|
||||
var options = new DbContextOptionsBuilder<NounoursContext>()
|
||||
.UseSqlite(connection)
|
||||
.Options;
|
||||
|
||||
//prepares the database with one instance of the context
|
||||
using (var context = new NounoursContext(options))
|
||||
{
|
||||
//context.Database.OpenConnection();
|
||||
context.Database.EnsureCreated();
|
||||
|
||||
Nounours chewie = new Nounours { Nom = "Chewbacca" };
|
||||
Nounours yoda = new Nounours { Nom = "Yoda" };
|
||||
Nounours ewok = new Nounours { Nom = "Ewok" };
|
||||
|
||||
context.Nounours.Add(chewie);
|
||||
context.Nounours.Add(yoda);
|
||||
context.Nounours.Add(ewok);
|
||||
context.SaveChanges();
|
||||
}
|
||||
|
||||
//uses another instance of the context to do the tests
|
||||
using (var context = new NounoursContext(options))
|
||||
{
|
||||
context.Database.EnsureCreated();
|
||||
|
||||
string nameToFind = "ew";
|
||||
Assert.Equal(2, context.Nounours.Where(n => n.Nom.ToLower().Contains(nameToFind)).Count());
|
||||
nameToFind = "wo";
|
||||
Assert.Equal(1, context.Nounours.Where(n => n.Nom.ToLower().Contains(nameToFind)).Count());
|
||||
var ewok = context.Nounours.Where(n => n.Nom.ToLower().Contains(nameToFind)).First();
|
||||
ewok.Nom = "Wicket";
|
||||
context.SaveChanges();
|
||||
}
|
||||
|
||||
//uses another instance of the context to do the tests
|
||||
using (var context = new NounoursContext(options))
|
||||
{
|
||||
context.Database.EnsureCreated();
|
||||
|
||||
string nameToFind = "ew";
|
||||
Assert.Equal(1, context.Nounours.Where(n => n.Nom.ToLower().Contains(nameToFind)).Count());
|
||||
nameToFind = "wick";
|
||||
Assert.Equal(1, context.Nounours.Where(n => n.Nom.ToLower().Contains(nameToFind)).Count());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,223 @@
|
||||
# ex_042_001_EF_CF_conventions
|
||||
*02/01/2020 ⋅ Marc Chevaldonné*
|
||||
|
||||
---
|
||||
|
||||
Le lien entre une base de données et vos classes à l'aide de **Entity Framework Core** se fait via une classe dérivant de ```DbContext```.
|
||||
Cette classe doit contenir des ```DbSet<T>```. Chaque ```DbSet<T>``` correspond à une table et ```T``` correspond à une de vos classes qu'on appelle *entité*.
|
||||
Le lien entre les tables et les entités est géré plus ou moins automatiquement par le *framework*.
|
||||
**EF Core** permet de lire/écrire les instances d'entité de la base de données ; permet de créer des tables pour les entités via les migrations (pour les bases de données relationnelles) ;
|
||||
les types exposés dans les propriétés d'une entité deviennent automatiquement des entités (mais pas forcément des tables)
|
||||
|
||||
**Entity Framework Core** propose 3 solutions différentes pour relier des entités à des tables de la base de données :
|
||||
* **conventions d'écriture** : c'est la solution la plus simple, qui analyse le code de votre classe pour en déduire de quelle manière la relier à la table.
|
||||
* **data annotations** : elle se base sur des décorateurs (appelés *data annotations*) que vous placez autour de vos propriétés pour indiquer de quelle manière les relier aux colonnes de votre table. Les *data annotations* écrasent les conventions d'écriture.
|
||||
* **Fluent API** : directement dans le code de votre ```DbContext``` (plus précisément, dans la méthode ```OnModelCreating```), vous précisez comment se fait le *mapping* entre votre entité et votre table. La *Fluent API* écrase les conventions d'écriture et les *data annotations*.
|
||||
|
||||
En d'autres termes, si vous n'écrivez rien, **EF Core** utilise les conventions d'écriture ; si vous n'utilisez que des *data annotations*, elles sont prioritaires sur les convetions d'écriture ; si vous utilisez la *Fluent API*, elle est prioritaire sur les deux autres méthodes.
|
||||
Mais on peut faire un mix des différentes méthodes.
|
||||
|
||||
Cet exemple montre de quelle manière les **conventions d'écriture** sont utilisées pour transformer une entité en table.
|
||||
Il montre notamment :
|
||||
* comment le nom de la table est choisi
|
||||
* s'il est possible d'ignorer une propriété de l'entité
|
||||
* comment le nom et le type d'une colonne de la table sont choisis
|
||||
* comment un identifiant unique est choisi et généré.
|
||||
|
||||
Cet exemple est répété d'une manière très similaire en utilisant les autres méthodes de *mapping* entre entité et table :
|
||||
* **ex_042_002_EF_CF_data_annotations** : avec les *data annotations*
|
||||
* **ex_042_003_EF_CF_Fluent_API** : avec la *Fluent API*
|
||||
|
||||
---
|
||||
|
||||
## Comment est construit cet exemple ?
|
||||
* Le projet est de type .NET Core
|
||||
* Il contient deux classes :
|
||||
* ```Nounours```
|
||||
* ```NounoursDBEntities```
|
||||
|
||||
### La classe ```NounoursDBEntities```
|
||||
* Cette classe dérive de ```DbContext```. Elle contient des ```DbSet<T>``` où ```T``` est une entité. Elle contient autant de ```DbSet<T>``` que de tables.
|
||||
```csharp
|
||||
class NounoursDBEntities : DbContext
|
||||
{
|
||||
public DbSet<Nounours> NounoursSet { get; set; }
|
||||
}
|
||||
```
|
||||
ici, on indique donc qu'il y aura une table de ```Nounours```.
|
||||
* **EF Core** utilisera cette classe pour faire la création de la base de données et de ses tables. Pour cela, elle utilise la chaîne de connexion donnée dans la méthode ```OnConfiguring```.
|
||||
```csharp
|
||||
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
|
||||
{
|
||||
optionsBuilder.UseSqlServer(@"Server=(localdb)\mssqllocaldb;Database=ex_042_001_EF_CF_conventions.Nounours.mdf;Trusted_Connection=True;");
|
||||
}
|
||||
```
|
||||
=> cf **ex_041_001_ConnectionStrings** pour en savoir plus sur les chaîne de connexion
|
||||
* Dans cet exemple, la table de ```Nounours``` est créée en utilisant les conventions d'écriture de la classe ```Nounours```.
|
||||
|
||||
### La classe ```Nounours```
|
||||
* ```Nounours``` est une entité, on parle aussi de classe POCO, i.e. Plain Old CLR Object.
|
||||
```csharp
|
||||
public class Nounours
|
||||
{
|
||||
public int ID
|
||||
{
|
||||
get; set;
|
||||
}
|
||||
|
||||
public string Nom
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
public DateTime DateDeNaissance
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
public int NbPoils
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
}
|
||||
```
|
||||
* Elle contient 3 propriétés en lecture/écriture : ```Nom```, ```DateDeNaissance``` et ```NbPoils```. Nous parlerons de la 4ème (```ID```) dans un moment.
|
||||
* **Entity Framework Core** va utiliser la classe POCO ```Nounours``` pour créer une table dans la base de données, lorsque le ```DbSet``` va être créé.
|
||||
* L'utilisation des conventions d'écriture d'Entity Framework font que la table qui va être créée aura :
|
||||
* un nom correspondant au nom de la classe POCO : ici "Nounours"
|
||||
* une colonne pour chaque propriété publique : ici, "Nom", "DateDeNaissance" et "NbPoils"
|
||||
* De plus, en rajoutant une propriété de type int et avec le nom "ID", Entity Framework ajoute directement une colonne "ID" et l'utilise comme clé primaire.
|
||||
|
||||
**En résumé** :
|
||||
* le nom de la table est choisi automatiquement (c'est le nom de l'entité)
|
||||
* toutes les propriétés ayant un getter et un setter publiques sont des colonnes de la table
|
||||
* le nom des colonnes est choisi automatiquement (il s'agit du nom des propriétés)
|
||||
* il n'est pas possible d'ignorer une propriété
|
||||
* le type d'une colonne est imposé par un *mapping* automatique entre les types .NET et ceux de la base de données. Par exemple :
|
||||
* un ```DateTime``` est transformé en ```datetime2(7)```
|
||||
* un ```string``` est transformé en ```nvarchar(max)```
|
||||
* si c'est une clé, elle est transformée en ```nvarchar(450)```
|
||||
* ...
|
||||
* si la classe possède une propriété de type ```int``` s'appelant ```ID```, elle est automatiquement utilisée comme clé primaire générée par lz base de données lors de l'insertion
|
||||
|
||||
### La classe ```Program```
|
||||
Cette classe est le point d'entrée du programme :
|
||||
* Elle crée des instances de ```Nounours```
|
||||
```csharp
|
||||
Nounours chewie = new Nounours { Nom = "Chewbacca", DateDeNaissance = new DateTime(1977, 5, 27), NbPoils = 1234567 };
|
||||
Nounours yoda = new Nounours { Nom = "Yoda", DateDeNaissance = new DateTime(1980, 5, 21), NbPoils = 3 };
|
||||
Nounours ewok = new Nounours { Nom = "Ewok", DateDeNaissance = new DateTime(1983, 5, 25), NbPoils = 3456789 };
|
||||
```
|
||||
* Elle démarre une connexion à la base de données
|
||||
```csharp
|
||||
using (NounoursDBEntities db = new NounoursDBEntities())
|
||||
{
|
||||
//...
|
||||
}
|
||||
```
|
||||
* Elle vérifie si la table est vide, et si ce n'est pas le cas, elle la vide.
|
||||
Notez l'accès à la table de ```Nounours``` via ```db.NounoursSet```.
|
||||
Notez également que tant que ```SaveChanges``` n'est pas appelée, les suppressions ne sont pas effectives dans la base, seulement en local dans le programme.
|
||||
_Cette partie de l'exemple ne s'exécutera que si la base existe déjà, par exemple lors d'une deuxième exécution._
|
||||
```csharp
|
||||
if (db.NounoursSet.Count() > 0)
|
||||
{
|
||||
WriteLine("La base n'est pas vide !");
|
||||
foreach (var n in db.NounoursSet)
|
||||
{
|
||||
WriteLine($"\t{n}");
|
||||
}
|
||||
WriteLine("début du nettoyage...");
|
||||
|
||||
foreach (var n in db.NounoursSet.ToArray())
|
||||
{
|
||||
WriteLine($"Suppression de {n}");
|
||||
db.NounoursSet.Remove(n);
|
||||
}
|
||||
|
||||
WriteLine("Base avant sauvegarde des changements :");
|
||||
foreach (var n in db.NounoursSet)
|
||||
{
|
||||
WriteLine($"\t{n}");
|
||||
}
|
||||
db.SaveChanges();
|
||||
WriteLine("Base après sauvegarde des changements :");
|
||||
foreach (var n in db.NounoursSet)
|
||||
{
|
||||
WriteLine($"\t{n}");
|
||||
}
|
||||
}
|
||||
```
|
||||
* Elle ajoute ensuite les ```Nounours```et sauvegarde les changements pour que ceux-ci soit effectivement ajoutés à la base.
|
||||
```csharp
|
||||
db.NounoursSet.AddRange(new Nounours[] { chewie, yoda, ewok });
|
||||
|
||||
db.SaveChanges();
|
||||
WriteLine("Base après ajout des 3 nounours et sauvegarde des changements :");
|
||||
foreach (var n in db.NounoursSet)
|
||||
{
|
||||
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_001_EF_CF_conventions
|
||||
```
|
||||
*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_001
|
||||
```
|
||||
* Création de la table :
|
||||
```
|
||||
dotnet ef database update
|
||||
```
|
||||
* Génération et exécution
|
||||
Vous pouvez maintenant générer et exécuter l'exemple **ex_042_001_EF_CF_conventions**.
|
||||
|
||||
* Le résultat de l'exécution doit ressembler à :
|
||||
```
|
||||
Base après ajout des 3 nounours et sauvegarde des changements :
|
||||
1: Chewbacca (27/05/1977, 1234567 poils)
|
||||
2: Yoda (21/05/1980, 3 poils)
|
||||
3: Ewok (25/05/1983, 3456789 poils)
|
||||
```
|
||||
_Note : les identifiants peuvent varier en fonction du nombre d'exécutions_
|
||||
|
||||
* 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_001_EF_CF_conventions.NounoursSet.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 :
|
||||
|
||||
|ID |Nom|DateDeNaissance|NbPoils
|
||||
|---|---|---|---
|
||||
|1|Chewbacca|27/05/1977 00:00:00|1234567
|
||||
|2|Yoda|21/05/1980 00:00:00|3
|
||||
|3|Ewok|25/05/1983 00:00:00|3456789
|
||||
|
||||
*Note: les identifiants peuvent varier en fonction du nombre d'exécution de l'exemple depuis la création de la base de données.*
|
@ -0,0 +1,13 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
<TargetFramework>netcoreapp3.0</TargetFramework>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="3.1.0" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" Version="3.1.0" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="3.1.0" />
|
||||
</ItemGroup>
|
||||
</Project>
|
@ -0,0 +1,309 @@
|
||||
# ex_042_002_EF_CF_data_annotations
|
||||
*03/01/2020 ⋅ Marc Chevaldonné*
|
||||
|
||||
---
|
||||
|
||||
Le lien entre une base de données et vos classes à l'aide de **Entity Framework Core** se fait via une classe dérivant de ```DbContext```.
|
||||
Cette classe doit contenir des ```DbSet<T>```. Chaque ```DbSet<T>``` correspond à une table et ```T``` correspond à une de vos classes qu'on appelle *entité*.
|
||||
Le lien entre les tables et les entités est géré plus ou moins automatiquement par le *framework*.
|
||||
**EF Core** permet de lire/écrire les instances d'entité de la base de données ; permet de créer des tables pour les entités via les migrations (pour les bases de données relationnelles) ;
|
||||
les types exposés dans les propriétés d'une entité deviennent automatiquement des entités (mais pas forcément des tables)
|
||||
|
||||
**Entity Framework Core** propose 3 solutions différentes pour relier des entités à des tables de la base de données :
|
||||
* **conventions d'écriture** : c'est la solution la plus simple, qui analyse le code de votre classe pour en déduire de quelle manière la relier à la table.
|
||||
* **data annotations** : elle se base sur des décorateurs (appelés *data annotations*) que vous placez autour de vos propriétés pour indiquer de quelle manière les relier aux colonnes de votre table. Les *data annotations* écrasent les conventions d'écriture.
|
||||
* **Fluent API** : directement dans le code de votre ```DbContext``` (plus précisément, dans la méthode ```OnModelCreating```), vous précisez comment se fait le *mapping* entre votre entité et votre table. La *Fluent API* écrase les conventions d'écriture et les *data annotations*.
|
||||
|
||||
En d'autres termes, si vous n'écrivez rien, **EF Core** utilise les conventions d'écriture ; si vous n'utilisez que des *data annotations*, elles sont prioritaires sur les convetions d'écriture ; si vous utilisez la *Fluent API*, elle est prioritaire sur les deux autres méthodes.
|
||||
Mais on peut faire un mix des différentes méthodes.
|
||||
|
||||
Cet exemple montre de quelle manière les **data annotations** sont utilisées pour transformer une entité en table.
|
||||
Il montre notamment :
|
||||
* comment choisir le nom de la table
|
||||
* comment ignorer une propriété de l'entité
|
||||
* comment le nom et le type d'une colonne de la table peuvent être modifiés
|
||||
* comment un identifiant unique est choisi et généré.
|
||||
|
||||
Cet exemple est répété d'une manière très similaire en utilisant les autres méthodes de *mapping* entre entité et table :
|
||||
* **ex_042_001_EF_CF_conventions** : avec les *conventions d'écriture*
|
||||
* **ex_042_003_EF_CF_Fluent_API** : avec la *Fluent API*
|
||||
|
||||
---
|
||||
|
||||
## Comment est construit cet exemple ?
|
||||
* Le projet est de type .NET Core
|
||||
* Il contient deux classes :
|
||||
* ```Nounours```
|
||||
* ```NounoursDBEntities```
|
||||
|
||||
### La classe ```NounoursDBEntities```
|
||||
* Cette classe dérive de ```DbContext```. Elle contient des ```DbSet<T>``` où ```T``` est une entité. Elle contient autant de ```DbSet<T>``` que de tables.
|
||||
```csharp
|
||||
class NounoursDBEntities : DbContext
|
||||
{
|
||||
public DbSet<Nounours> NounoursSet { get; set; }
|
||||
}
|
||||
```
|
||||
ici, on indique donc qu'il y aura une table de ```Nounours```.
|
||||
* **EF Core** utilisera cette classe pour faire la création de la base de données et de ses tables. Pour cela, elle utilise la chaîne de connexion donnée dans la méthode ```OnConfiguring```.
|
||||
```csharp
|
||||
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
|
||||
{
|
||||
optionsBuilder.UseSqlServer(@"Server=(localdb)\mssqllocaldb;Database=ex_042_001_EF_CF_conventions.Nounours.mdf;Trusted_Connection=True;");
|
||||
}
|
||||
```
|
||||
=> cf **ex_041_001_ConnectionStrings** pour en savoir plus sur les chaîne de connexion
|
||||
* Dans cet exemple, la table de ```Nounours``` est créée en utilisant les *data annotations* et les conventions d'écriture de la classe ```Nounours```.
|
||||
|
||||
### La classe ```Nounours```
|
||||
* ```Nounours``` est une entité, on parle aussi de classe POCO, i.e. Plain Old CLR Object.
|
||||
```csharp
|
||||
[Table("TableNounours")]
|
||||
public class Nounours
|
||||
{
|
||||
[Key]
|
||||
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
|
||||
public Guid UniqueId
|
||||
{
|
||||
get; set;
|
||||
}
|
||||
|
||||
[Required]
|
||||
[MaxLength(256)]
|
||||
//[Column("name", Order=0, TypeName ="varchar(200)")]
|
||||
public string Nom
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
[Column("Naissance", TypeName = "date")]
|
||||
public DateTime DateDeNaissance
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
[NotMapped]
|
||||
public int NbPoils
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return $"{UniqueId}: {Nom} ({DateDeNaissance:dd/MM/yyyy}, {NbPoils} poils)";
|
||||
}
|
||||
}
|
||||
```
|
||||
* Elle contient 3 propriétés en lecture/écriture : ```Nom```, ```DateDeNaissance``` et ```NbPoils```. Nous parlerons de la 4ème (```UniqueId```) dans un moment.
|
||||
* **Entity Framework Core** va utiliser la classe POCO ```Nounours``` pour créer une table dans la base de données, lorsque le ```DbSet``` va être créé.
|
||||
* S'il y a des *data annotations*, elles sont prioritaires sur l'utilisation des conventions d'écriture d'Entity Framework.
|
||||
|
||||
Un petit tour des annotations utilisées dans l'exemple et quelques indications sur celles manquantes :
|
||||
* la classe ```Nounours``` est précédée de l'annotation ```[Table("TableNounours")]``` : la table créée n'aura donc pas le nom de la classe (comme le permettent les conventions d'écriture), mais le nom indiqué dans l'annotation :
|
||||
```csharp
|
||||
[Table("TableNounours")]
|
||||
public class Nounours
|
||||
{
|
||||
//...
|
||||
}
|
||||
```
|
||||
* on peut changer le nom, le type ou l'ordre d'une colonne en précédant une propriété de l'annotation ```[Column(...)]```. Dans l'exemple ci-dessous,
|
||||
la colonne ne s'appellera pas "DateDeNaissance" (comme le permettraient les conventions d'écriture), mais "Naissance" grâce à l'annotation.
|
||||
```csharp
|
||||
[Column("Naissance")]
|
||||
public DateTime DateDeNaissance
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
```
|
||||
* On pourrait aller plus loin en indiquant l'ordre et le changement de type, par exemple :
|
||||
```csharp
|
||||
[Column("name", Order=0, TypeName=varchar(200))]
|
||||
public string Nom
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
```
|
||||
* Dans la classe ```Nounours```, la propriété ```DateDeNaissance``` est annotée de façon à modifier le type par défaut de *mapping* (la partie *time* sera évincée) et le nom de colonne sera modifé en "Naissance".
|
||||
```csharp
|
||||
[Column("Naissance", TypeName = "date")]
|
||||
public DateTime DateDeNaissance
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
```
|
||||
* L'annotation ```[Required]``` permet de rendre obligatoire une propriété. Une propriété peut être optionnelle seulement si elle peut avoir la valeur ```null```. Dans l'exemple ci-dessous, le nom est obligatoire.
|
||||
```csharp
|
||||
[Required]
|
||||
public string Nom
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
```
|
||||
* L'annotation ```[MaxLength(256)]``` permet d'imposer une taille max à une chaîne de caractères. Dans l'exemple ci-dessous, le nom ne doit pas avoir plus de 256 caractères.
|
||||
```csharp
|
||||
[MaxLength(256)]
|
||||
public string Nom
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
```
|
||||
* L'annotation ```[NotMapped]``` permet d'indiquer qu'une propriété ne doit pas être mappée en colonne de table. Par exemple, ici, le nombre de poils ne sera pas mappé en colonne.
|
||||
```csharp
|
||||
[NotMapped]
|
||||
public int NbPoils
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
```
|
||||
* L'annotation ```[Key]``` transforme n'importe quelle propriété en clé primaire.
|
||||
Si on ajoute l'annotation ```[DatabaseGenerated(DatabaseGeneratedOption.Identity)]```, on précise par exemple que la clé primaire sera générée par la base, lors de l'insertation dans la table.
|
||||
Notez que la clé n'est donc, contrairement aux conventions d'écriture, pas nécessairement un ```int```. Elle peut-être un ```Guid```, un ```string```, etc. Son nom peut-être autre chose que "ID".
|
||||
*Note : on peut aussi faire des clés composées, comme je le montrerai dans un autre exemple.*
|
||||
Dans l'exemple ci-dessous, un ```Nounours```possédera une clé unique de type ```Guid``` générée par la base et placée dans la colonne "UniqueId".
|
||||
```csharp
|
||||
[Key]
|
||||
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
|
||||
public Guid UniqueId
|
||||
{
|
||||
get; set;
|
||||
}
|
||||
```
|
||||
|
||||
**En résumé**, avec les *data annotations* :
|
||||
* on peut changer le nom de la table
|
||||
* on peut changer le nom, le type et l'ordre d'une colonne
|
||||
* on peut rendre une propriété obligatoire ou optionnelle
|
||||
* on peut empêcher le *mapping* d'une propriété
|
||||
* on peut imposer une taille max pour les chaînes de caractères
|
||||
* on peut transformer une propriété en clé primaire et demander à la base de la générée lors de l'insertion.
|
||||
|
||||
### La classe ```Program```
|
||||
Cette classe est le point d'entrée du programme :
|
||||
* Elle crée des instances de ```Nounours```
|
||||
```csharp
|
||||
Nounours chewie = new Nounours { Nom = "Chewbacca", DateDeNaissance = new DateTime(1977, 5, 27), NbPoils = 1234567 };
|
||||
Nounours yoda = new Nounours { Nom = "Yoda", DateDeNaissance = new DateTime(1980, 5, 21), NbPoils = 3 };
|
||||
Nounours ewok = new Nounours { Nom = "Ewok", DateDeNaissance = new DateTime(1983, 5, 25), NbPoils = 3456789 };
|
||||
```
|
||||
* Elle démarre une connexion à la base de données
|
||||
```csharp
|
||||
using (NounoursDBEntities db = new NounoursDBEntities())
|
||||
{
|
||||
//...
|
||||
}
|
||||
```
|
||||
* Elle vérifie si la table est vide, et si ce n'est pas le cas, elle la vide.
|
||||
Notez l'accès à la table de ```Nounours``` via ```db.NounoursSet```.
|
||||
Notez également que tant que ```SaveChanges``` n'est pas appelée, les suppressions ne sont pas effectives dans la base, seulement en local dans le programme.
|
||||
_Cette partie de l'exemple ne s'exécutera que si la base existe déjà, par exemple lors d'une deuxième exécution._
|
||||
```csharp
|
||||
if (db.NounoursSet.Count() > 0)
|
||||
{
|
||||
WriteLine("La base n'est pas vide !");
|
||||
foreach (var n in db.NounoursSet)
|
||||
{
|
||||
WriteLine($"\t{n}");
|
||||
}
|
||||
WriteLine("début du nettoyage...");
|
||||
|
||||
foreach (var n in db.NounoursSet.ToArray())
|
||||
{
|
||||
WriteLine($"Suppression de {n}");
|
||||
db.NounoursSet.Remove(n);
|
||||
}
|
||||
|
||||
WriteLine("Base avant sauvegarde des changements :");
|
||||
foreach (var n in db.NounoursSet)
|
||||
{
|
||||
WriteLine($"\t{n}");
|
||||
}
|
||||
db.SaveChanges();
|
||||
WriteLine("Base après sauvegarde des changements :");
|
||||
foreach (var n in db.NounoursSet)
|
||||
{
|
||||
WriteLine($"\t{n}");
|
||||
}
|
||||
}
|
||||
```
|
||||
* Elle ajoute ensuite les ```Nounours```et sauvegarde les changements pour que ceux-ci soit effectivement ajoutés à la base.
|
||||
```csharp
|
||||
db.NounoursSet.AddRange(new Nounours[] { chewie, yoda, ewok });
|
||||
|
||||
db.SaveChanges();
|
||||
WriteLine("Base après ajout des 3 nounours et sauvegarde des changements :");
|
||||
foreach (var n in db.NounoursSet)
|
||||
{
|
||||
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_002_EF_CF_data_annotations
|
||||
```
|
||||
*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_002
|
||||
```
|
||||
* Création de la table :
|
||||
```
|
||||
dotnet ef database update
|
||||
```
|
||||
* Génération et exécution
|
||||
Vous pouvez maintenant générer et exécuter l'exemple **ex_042_002_EF_CF_data_annotations**.
|
||||
|
||||
* Le résultat de l'exécution peut ressembler à :
|
||||
```
|
||||
Base après ajout des 3 nounours et sauvegarde des changements :
|
||||
fbc3f7c5-a333-4d07-f7f2-08d7907e5437: Chewbacca (27/05/1977, 1234567 poils)
|
||||
63b18e10-a683-4144-f7f3-08d7907e5437: Yoda (21/05/1980, 3 poils)
|
||||
c4eab29b-315b-416e-f7f4-08d7907e5437: Ewok (25/05/1983, 3456789 poils)
|
||||
```
|
||||
_Note : les identifiants seront bien sûr différents_
|
||||
|
||||
* 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_002_EF_CF_data_annotations.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
|
||||
|---|---|---
|
||||
|fbc3f7c5-a333-4d07-f7f2-08d7907e5437|Chewbacca|27/05/1977
|
||||
|63b18e10-a683-4144-f7f3-08d7907e5437|Yoda|21/05/1980
|
||||
|c4eab29b-315b-416e-f7f4-08d7907e5437|Ewok|25/05/1983
|
||||
|
||||
*Notes: les identifiants seront bien sûr différents.*
|
||||
*Notez l'absence de la colonne "NbPoils"*
|
||||
*Notez le nom de la colonne "Naissance" et le formatage de la date*
|
@ -0,0 +1,12 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
<TargetFramework>netcoreapp3.0</TargetFramework>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="3.1.0" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" Version="3.1.0" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="3.1.0" />
|
||||
</ItemGroup>
|
||||
</Project>
|
@ -0,0 +1,308 @@
|
||||
# ex_042_003_EF_CF_Fluent_API
|
||||
*03/01/2020 ⋅ Marc Chevaldonné*
|
||||
|
||||
---
|
||||
|
||||
Le lien entre une base de données et vos classes à l'aide de **Entity Framework Core** se fait via une classe dérivant de ```DbContext```.
|
||||
Cette classe doit contenir des ```DbSet<T>```. Chaque ```DbSet<T>``` correspond à une table et ```T``` correspond à une de vos classes qu'on appelle *entité*.
|
||||
Le lien entre les tables et les entités est géré plus ou moins automatiquement par le *framework*.
|
||||
**EF Core** permet de lire/écrire les instances d'entité de la base de données ; permet de créer des tables pour les entités via les migrations (pour les bases de données relationnelles) ;
|
||||
les types exposés dans les propriétés d'une entité deviennent automatiquement des entités (mais pas forcément des tables)
|
||||
|
||||
**Entity Framework Core** propose 3 solutions différentes pour relier des entités à des tables de la base de données :
|
||||
* **conventions d'écriture** : c'est la solution la plus simple, qui analyse le code de votre classe pour en déduire de quelle manière la relier à la table.
|
||||
* **data annotations** : elle se base sur des décorateurs (appelés *data annotations*) que vous placez autour de vos propriétés pour indiquer de quelle manière les relier aux colonnes de votre table. Les *data annotations* écrasent les conventions d'écriture.
|
||||
* **Fluent API** : directement dans le code de votre ```DbContext``` (plus précisément, dans la méthode ```OnModelCreating```), vous précisez comment se fait le *mapping* entre votre entité et votre table. La *Fluent API* écrase les conventions d'écriture et les *data annotations*.
|
||||
|
||||
En d'autres termes, si vous n'écrivez rien, **EF Core** utilise les conventions d'écriture ; si vous n'utilisez que des *data annotations*, elles sont prioritaires sur les convetions d'écriture ; si vous utilisez la *Fluent API*, elle est prioritaire sur les deux autres méthodes.
|
||||
Mais on peut faire un mix des différentes méthodes.
|
||||
|
||||
Cet exemple montre de quelle manière la **Fluent API** est utilisée pour transformer une entité en table.
|
||||
Il montre notamment :
|
||||
* comment choisir le nom de la table
|
||||
* comment ignorer une propriété de l'entité
|
||||
* comment le nom et le type d'une colonne de la table peuvent être modifiés
|
||||
* comment un identifiant unique est choisi et généré.
|
||||
|
||||
Cette méthode est préférable dans les deux cas principaux suivants (et en particulier le 1er) :
|
||||
* nous n'avons pas accès au code source de la classe Nounours ou bien nous n'avons pas le droit de le modifier, et les conventions d'écriture ne
|
||||
correspondent pas à ce que nous souhaitons réaliser.
|
||||
* nous ne souhaitons pas "polluer" la classe POCO avec des annotations de données.
|
||||
L'inconvénient majeur et évident de cette méthode est que la lecture et la maintenance sont plus compliquées.
|
||||
|
||||
Cet exemple est répété d'une manière très similaire en utilisant les autres méthodes de *mapping* entre entité et table :
|
||||
* **ex_042_001_EF_CF_conventions** : avec les *conventions d'écriture*
|
||||
* **ex_042_002_EF_CF_data_annotations** : avec les *data annotations*
|
||||
|
||||
---
|
||||
|
||||
## Comment est construit cet exemple ?
|
||||
* Le projet est de type .NET Core
|
||||
* Il contient deux classes :
|
||||
* ```Nounours```
|
||||
* ```NounoursDBEntities```
|
||||
|
||||
### La classe ```Nounours```
|
||||
* ```Nounours``` est une entité, on parle aussi de classe POCO, i.e. Plain Old CLR Object.
|
||||
```csharp
|
||||
public class Nounours
|
||||
{
|
||||
public Guid UniqueId
|
||||
{
|
||||
get; set;
|
||||
}
|
||||
|
||||
public string Nom
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
public DateTime DateDeNaissance
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
public int NbPoils
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
}
|
||||
```
|
||||
* Elle contient 4 propriétés en lecture/écriture : ```UniqueId```, ```Nom```, ```DateDeNaissance``` et ```NbPoils```.
|
||||
* **Entity Framework Core** va utiliser la classe POCO ```Nounours``` pour créer une table dans la base de données, lorsque le ```DbSet``` va être créé.
|
||||
* L'utilisation des conventions d'écriture d'Entity Framework pourrait s'appliquer car il n'y a pas de *data annotations* (cf. ex_042_002) ; mais comme la classe ```NounoursDbEntities``` utilise la *Fluent API*, elles seront écrasées.
|
||||
|
||||
### La classe ```NounoursDBEntities```
|
||||
* Cette classe dérive de ```DbContext```. Elle contient des ```DbSet<T>``` où ```T``` est une entité. Elle contient autant de ```DbSet<T>``` que de tables.
|
||||
```csharp
|
||||
class NounoursDBEntities : DbContext
|
||||
{
|
||||
public DbSet<Nounours> NounoursSet { get; set; }
|
||||
}
|
||||
```
|
||||
ici, on indique donc qu'il y aura une table de ```Nounours```.
|
||||
* **EF Core** utilisera cette classe pour faire la création de la base de données et de ses tables. Pour cela, elle utilise la chaîne de connexion donnée dans la méthode ```OnConfiguring```.
|
||||
```csharp
|
||||
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
|
||||
{
|
||||
optionsBuilder.UseSqlServer(@"Server=(localdb)\mssqllocaldb;Database=ex_042_003_EF_CF_Fluent_API.Nounours.mdf;Trusted_Connection=True;");
|
||||
}
|
||||
```
|
||||
=> cf **ex_041_001_ConnectionStrings** pour en savoir plus sur les chaîne de connexion
|
||||
* Dans cet exemple, la table de ```Nounours``` est créée en utilisant la *Fluent API* et les conventions d'écriture de la classe ```Nounours```.
|
||||
|
||||
* La **Fluent API** est utilisée à travers la méthode ```OnModelCreating```.
|
||||
```csharp
|
||||
protected override void OnModelCreating(ModelBuilder modelBuilder)
|
||||
{
|
||||
modelBuilder.Entity<Nounours>().ToTable("TableNounours");
|
||||
|
||||
modelBuilder.Entity<Nounours>().HasKey(n => n.UniqueId);
|
||||
|
||||
modelBuilder.Entity<Nounours>().Property(n => n.UniqueId).ValueGeneratedOnAdd();
|
||||
|
||||
modelBuilder.Entity<Nounours>().Property(n => n.Nom).IsRequired()
|
||||
.HasMaxLength(256);
|
||||
|
||||
modelBuilder.Entity<Nounours>().Property(n => n.DateDeNaissance).HasColumnName("Naissance").HasColumnType("date");
|
||||
|
||||
modelBuilder.Entity<Nounours>().Ignore(n => n.NbPoils);
|
||||
|
||||
base.OnModelCreating(modelBuilder);
|
||||
}
|
||||
```
|
||||
L'instance de ```ModelBuilder``` permet ensuite d'atteindre les entités avec la méthode générique ```Entity<T>()```où ```T```est l'entité.
|
||||
A partir d'une entité, on peut ensuite atteindre une propriété à l'aide de la méthode ```Property(...)``` pouvant prendre en paramètre
|
||||
soit le nom de la propriété, soit une expression lambda (recommandé pour éviter les erreurs de compilation).
|
||||
Ensuite, à l'aide des méthodes d'extension, on peut définir des contraintes sur les entités et leurs propriétés.
|
||||
|
||||
|
||||
|
||||
Si on prend chaque ligne de la méthode ```OnModelCreating``` en détail, on peut voir :
|
||||
* le choix du nom de la table associée à l'entité ```Nounours```
|
||||
*équivalent du ```[Table("TableNounours")]``` avec les annotations de données*
|
||||
```csharp
|
||||
modelBuilder.Entity<Nounours>().ToTable("TableNounours");
|
||||
```
|
||||
* on peut changer le nom et le type d'une colonne avec les méthodes d'extension ```HasColumnName``` et ```HasColumnType``` sur la propriété de l'entité.
|
||||
Dans l'exemple ci-dessous, la colonne ne s'appellera pas "DateDeNaissance" (comme le permettraient les conventions d'écriture), mais "Naissance" grâce à la *Fluent API* et son type sera ```date``` au lieu de ```datetime2(7)``` avec les conventions d'écriture.
|
||||
*équivalent avec une annotation de ```[Column("Naissance", TypeName="date")]``` devant la propriété ```DateDeNaissance``` de ```Nounours```*
|
||||
```csharp
|
||||
modelBuilder.Entity<Nounours>().Property(n => n.DateDeNaissance)
|
||||
.HasColumnName("Naissance")
|
||||
.HasColumnType("date");
|
||||
```
|
||||
* on peut rendre une propriété obligatoire (les propriétés sont optionnelles par défaut si elles sont *nullable*) avec la méthode d'extension ```IsRequired()```.
|
||||
Dans l'exemple ci-dessous, le nom est obligatoire.
|
||||
*équivalent de ```[Required]``` devant la propriété ```Nom``` de ```Nounours```*
|
||||
```csharp
|
||||
modelBuilder.Entity<Nounours>().Property(n => n.Nom).IsRequired();
|
||||
```
|
||||
* on peut imposer une taille max à une chaîne de caractères avec la méthode d'extension ```HasMaxLength(...)```.
|
||||
Dans l'exemple ci-dessous, le nom ne doit pas avoir plus de 256 caractères.
|
||||
*équivalent de ```[MaxLength(256)]``` devant la propriété ```Nom``` de ```Nounours```*
|
||||
```csharp
|
||||
modelBuilder.Entity<Nounours>().Property(n => n.Nom).HasMaxLength(256);
|
||||
```
|
||||
* on peut chaîner les méthodes d'extension.
|
||||
Dans l'exemple ci-dessous, le nom est obligatoire et ne doit pas avoir plus de 256 caractères.
|
||||
```csharp
|
||||
modelBuilder.Entity<Nounours>().Property(n => n.Nom)
|
||||
.IsRequired()
|
||||
.HasMaxLength(256);
|
||||
```
|
||||
* on peut préciser qu'on ne veut pas qu'une propriété soit transformée en colonne de la table avec la méthode d'extension ```Ignore()```.
|
||||
Par exemple, ici, le nombre de poils ne sera pas mappé en colonne.
|
||||
*équivalent de ```[NotMapped]``` devant la propriété ```NbPoils``` de ```Nounours```*
|
||||
```csharp
|
||||
modelBuilder.Entity<Nounours>().Ignore(n => n.NbPoils);
|
||||
```
|
||||
* on peut préciser qu'une propriété doit être utilisée en tant que clé primaire avec la méthode d'extension ```HasKey``` prenant en paramètre le nom de la propriété à prendre en compte.
|
||||
*équivalent de ```[Key]``` devant la propriété à utiliser en clé primaire*
|
||||
```csharp
|
||||
modelBuilder.Entity<Nounours>().HasKey(n => n.UniqueId);
|
||||
```
|
||||
On peut ensuite préciser qu'on veut que cette clé soit générée par la base lors de l'insertion à l'aide de la méthode d'extension ```ValueGeneratedOnAdd()```.
|
||||
*équivalent de ```[DatabaseGenerated(DatabaseGeneratedOption.Identity)]``` devant la propriété à utiliser en clé primaire*
|
||||
```csharp
|
||||
modelBuilder.Entity<Nounours>().Property(n => n.UniqueId).ValueGeneratedOnAdd();
|
||||
```
|
||||
Notez que la clé n'est donc, contrairement aux conventions d'écriture, pas nécessairement un ```int```. Elle peut-être un ```Guid```, un ```string```, etc. Son nom peut-être autre chose que "ID".
|
||||
*Note : on peut aussi faire des clés composées, comme je le montrerai dans un autre exemple.*
|
||||
Dans l'exemple ci-dessous, un ```Nounours```possédera une clé unique de type ```Guid``` générée par la base et placée dans la colonne "UniqueId".
|
||||
```csharp
|
||||
modelBuilder.Entity<Nounours>().HasKey(n => n.UniqueId);
|
||||
modelBuilder.Entity<Nounours>().Property(n => n.UniqueId).ValueGeneratedOnAdd();
|
||||
```
|
||||
|
||||
**En résumé**, avec la *Fluent API* :
|
||||
* on peut changer le nom de la table
|
||||
* on peut changer le nom et le type d'une colonne
|
||||
* on peut rendre une propriété obligatoire ou optionnelle
|
||||
* on peut empêcher le *mapping* d'une propriété
|
||||
* on peut imposer une taille max pour les chaînes de caractères
|
||||
* on peut transformer une propriété en clé primaire et demander à la base de la générée lors de l'insertion.
|
||||
|
||||
### La classe ```Program```
|
||||
Cette classe est le point d'entrée du programme :
|
||||
* Elle crée des instances de ```Nounours```
|
||||
```csharp
|
||||
Nounours chewie = new Nounours { Nom = "Chewbacca", DateDeNaissance = new DateTime(1977, 5, 27), NbPoils = 1234567 };
|
||||
Nounours yoda = new Nounours { Nom = "Yoda", DateDeNaissance = new DateTime(1980, 5, 21), NbPoils = 3 };
|
||||
Nounours ewok = new Nounours { Nom = "Ewok", DateDeNaissance = new DateTime(1983, 5, 25), NbPoils = 3456789 };
|
||||
```
|
||||
* Elle démarre une connexion à la base de données
|
||||
```csharp
|
||||
using (NounoursDBEntities db = new NounoursDBEntities())
|
||||
{
|
||||
//...
|
||||
}
|
||||
```
|
||||
* Elle vérifie si la table est vide, et si ce n'est pas le cas, elle la vide.
|
||||
Notez l'accès à la table de ```Nounours``` via ```db.NounoursSet```.
|
||||
Notez également que tant que ```SaveChanges``` n'est pas appelée, les suppressions ne sont pas effectives dans la base, seulement en local dans le programme.
|
||||
_Cette partie de l'exemple ne s'exécutera que si la base existe déjà, par exemple lors d'une deuxième exécution._
|
||||
```csharp
|
||||
if (db.NounoursSet.Count() > 0)
|
||||
{
|
||||
WriteLine("La base n'est pas vide !");
|
||||
foreach (var n in db.NounoursSet)
|
||||
{
|
||||
WriteLine($"\t{n}");
|
||||
}
|
||||
WriteLine("début du nettoyage...");
|
||||
|
||||
foreach (var n in db.NounoursSet.ToArray())
|
||||
{
|
||||
WriteLine($"Suppression de {n}");
|
||||
db.NounoursSet.Remove(n);
|
||||
}
|
||||
|
||||
WriteLine("Base avant sauvegarde des changements :");
|
||||
foreach (var n in db.NounoursSet)
|
||||
{
|
||||
WriteLine($"\t{n}");
|
||||
}
|
||||
db.SaveChanges();
|
||||
WriteLine("Base après sauvegarde des changements :");
|
||||
foreach (var n in db.NounoursSet)
|
||||
{
|
||||
WriteLine($"\t{n}");
|
||||
}
|
||||
}
|
||||
```
|
||||
* Elle ajoute ensuite les ```Nounours```et sauvegarde les changements pour que ceux-ci soit effectivement ajoutés à la base.
|
||||
```csharp
|
||||
db.NounoursSet.AddRange(new Nounours[] { chewie, yoda, ewok });
|
||||
|
||||
db.SaveChanges();
|
||||
WriteLine("Base après ajout des 3 nounours et sauvegarde des changements :");
|
||||
foreach (var n in db.NounoursSet)
|
||||
{
|
||||
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_003_EF_CF_Fluent_API
|
||||
```
|
||||
*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_003
|
||||
```
|
||||
* Création de la table :
|
||||
```
|
||||
dotnet ef database update
|
||||
```
|
||||
* Génération et exécution
|
||||
Vous pouvez maintenant générer et exécuter l'exemple **ex_042_003_EF_CF_Fluent_API**.
|
||||
|
||||
* Le résultat de l'exécution pourra ressembler à :
|
||||
```
|
||||
Base après ajout des 3 nounours et sauvegarde des changements :
|
||||
30817fff-8736-457d-db18-08d7908d7986: Chewbacca (27/05/1977, 1234567 poils)
|
||||
aa4469c4-a6c8-4077-db19-08d7908d7986: Yoda (21/05/1980, 3 poils)
|
||||
69cb5892-6750-4629-db1a-08d7908d7986: Ewok (25/05/1983, 3456789 poils)
|
||||
```
|
||||
*Notes: les identifiants seront bien sûr différents.*
|
||||
|
||||
* 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_003_EF_CF_Fluent_API.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
|
||||
|---|---|---
|
||||
|30817fff-8736-457d-db18-08d7908d7986|Chewbacca|27/05/1977
|
||||
|aa4469c4-a6c8-4077-db19-08d7908d7986|Yoda|21/05/1980
|
||||
|69cb5892-6750-4629-db1a-08d7908d7986|Ewok|25/05/1983
|
||||
|
||||
*Notes: les identifiants seront bien sûr différents.*
|
||||
*Notez l'absence de la colonne "NbPoils"*
|
||||
*Notez le nom de la colonne "Naissance" et le formatage de la date*
|
@ -0,0 +1,12 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
<TargetFramework>netcoreapp3.0</TargetFramework>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="3.1.0" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" Version="3.1.0" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="3.1.0" />
|
||||
</ItemGroup>
|
||||
</Project>
|
@ -0,0 +1,116 @@
|
||||
# ex_042_004_Keys_conventions
|
||||
*06/01/2020 ⋅ Marc Chevaldonné*
|
||||
|
||||
---
|
||||
|
||||
Cet exemple traite des clés primaires associées aux entités.
|
||||
|
||||
Prérequis : je n'explique pas à travers cet exemple les principes de base d'**Entity Framework Core** et en particulier les chaînes de connexion et le lien entre entité et table.
|
||||
Pour plus de renseignements sur :
|
||||
* les chaînes de connexion : *ex_041_001_ConnectionStrings*
|
||||
* les liens entre entités et tables : *ex_042_001_EF_CF_conventions*, *ex_042_002_EF_CF_data_annotations* et *ex_042_003_EF_CF_Fluent_API*
|
||||
|
||||
Cet exemple montre le cas particulier de la gestion des clés primaires lors de l'utilisation des **conventions d'écriture**.
|
||||
Vous pourrez trouver une version plus ou moins équivalente avec les *data annotations* ici : **ex_042_005_Keys_data_annotations**.
|
||||
Vous pourrez trouver une version plus ou moins équivalente avec la *Fluent API* ici : **ex_042_006_Keys_FluentAPI**.
|
||||
|
||||
---
|
||||
|
||||
## Les clés primaires
|
||||
Une clé permet de rendre unique chaque instance d'une entité. La plupart des entités n'ont qu'une seule clé qui est alors transformée en *clé primaire* pour les bases de données relationnelles.
|
||||
*Note: une entité peut avoir d'autres clés, on parle d'__alternate keys__. Elles seront présentées dans les exemples sur les relations entre entités.*
|
||||
Si on utilise les *conventions d'écriture*, une propriété pour être transformée en clé doit respecter les contraintes suivantes :
|
||||
* elle doit être nommée ```Id``` ou ```ID```,
|
||||
* elle doit être nommée ```<typeDeLEntite>Id```, e.g. ```NounoursId```.
|
||||
|
||||
Les autres contraintes sur une clé dans le cas de l'utilisation des *conventions d'écriture* sont :
|
||||
* elle doit être de type ```int```, ```string```, ```byte[]```. Toutefois, certains types nécessitent l'utilisation de converteurs pour être utilisés avec certains fournisseurs. Je conseille donc l'utilisation de ```int``` qui marche avec la grande majorité des fournisseurs.
|
||||
* elle est générée lors de l'insertion en base.
|
||||
|
||||
Les autres modes (*data annotations* et *Fluent API*) offrent plus de solutions quant à la gestion des clés.
|
||||
|
||||
## La classe ```Nounours```
|
||||
La classe ```Nounours``` utilise les conventions d'écriture.
|
||||
* Par défaut, les propriétés utilisées comme clés primaires sont en mode **Generated on add**.
|
||||
Une nouvelle valeur est donc générée lors de l'insertion d'une nouvelle entité en base. Les valeurs des autres propriétés ne sont pas générées lors de l'insertion ou de la mise à jour.
|
||||
* Dans cette classe, j'ai respecté la contrainte de nommage qui propose ```Id``` ou ```ID```
|
||||
```csharp
|
||||
public int ID
|
||||
{
|
||||
get; set;
|
||||
}
|
||||
```
|
||||
|
||||
## La classe ```Cylon```
|
||||
La classe ```Cylon``` utilise les conventions d'écriture.
|
||||
* Dans cette classe, j'ai respecté la contrainte de nommage qui propose ```<TypeDeLEntité>Id```
|
||||
```csharp
|
||||
public int CylonId
|
||||
{
|
||||
get; set;
|
||||
}
|
||||
```
|
||||
|
||||
### La classe ```Program```
|
||||
Cette classe est le point d'entrée du programme :
|
||||
* Elle crée des instances de ```Nounours``` et de ```Cylon``` et les ajoute en base après avoir nettoyé les tables au préalables.
|
||||
* Elle affiche les ```Nounours``` et les ```Cylon```.
|
||||
*Notez la génération des identifiants.*
|
||||
|
||||
## 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_004_Keys_conventions
|
||||
```
|
||||
*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_004
|
||||
```
|
||||
* Création de la table :
|
||||
```
|
||||
dotnet ef database update
|
||||
```
|
||||
* Génération et exécution
|
||||
Vous pouvez maintenant générer et exécuter l'exemple **ex_042_004_Keys_conventions**.
|
||||
|
||||
* 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_004_Keys_conventions.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"/>
|
||||
|
||||
* Le résultat de l'exécution peut être :
|
||||
```
|
||||
database after cleaning and adding 3 Nounours and 9 Cylons and saving changes :
|
||||
Nounours 1: Chewbacca (27/05/1977, 1234567 poils)
|
||||
Nounours 2: Yoda (21/05/1980, 3 poils)
|
||||
Nounours 3: Ewok (25/05/1983, 3456789 poils)
|
||||
Cylon 1: John Cavil, Number 1
|
||||
Cylon 2: Leoben Conoy, Number 2
|
||||
Cylon 3: D'Anna Biers, Number 3
|
||||
Cylon 4: Simon, Number 4
|
||||
Cylon 5: Aaron Doral, Number 5
|
||||
Cylon 6: Caprica 6, Number 6
|
||||
Cylon 7: Daniel, Number 7
|
||||
Cylon 8: Boomer, Number 8
|
||||
Cylon 9: Athena, Number 8
|
||||
```
|
||||
*Note: les identifiants peuvent varier en fonction du nombre d'exécution de l'exemple depuis la création de la base de données.*
|
@ -0,0 +1,12 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
<TargetFramework>netcoreapp3.0</TargetFramework>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="3.1.0" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" Version="3.1.0" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="3.1.0" />
|
||||
</ItemGroup>
|
||||
</Project>
|
@ -0,0 +1,131 @@
|
||||
# ex_042_005_Keys_data_annotations_
|
||||
*06/01/2020 ⋅ Marc Chevaldonné*
|
||||
|
||||
---
|
||||
|
||||
Cet exemple traite des clés primaires associées aux entités.
|
||||
|
||||
Prérequis : je n'explique pas à travers cet exemple les principes de base d'**Entity Framework Core** et en particulier les chaînes de connexion et le lien entre entité et table.
|
||||
Pour plus de renseignements sur :
|
||||
* les chaînes de connexion : *ex_041_001_ConnectionStrings*
|
||||
* les liens entre entités et tables : *ex_042_001_EF_CF_conventions*, *ex_042_002_EF_CF_data_annotations* et *ex_042_003_EF_CF_Fluent_API*
|
||||
|
||||
Cet exemple montre le cas particulier de la gestion des clés primaires lors de l'utilisation des **data annotations**.
|
||||
Vous pourrez trouver une version plus ou moins équivalente avec les *conventions d'écriture* ici : **ex_042_004_Keys_conventions**.
|
||||
Vous pourrez trouver une version plus ou moins équivalente avec la *Fluent API* ici : **ex_042_006_Keys_FluentAPI**.
|
||||
|
||||
---
|
||||
|
||||
## Les clés primaires
|
||||
Une clé permet de rendre unique chaque instance d'une entité. La plupart des entités n'ont qu'une seule clé qui est alors transformée en *clé primaire* pour les bases de données relationnelles.
|
||||
*Note: une entité peut avoir d'autres clés, on parle d'__alternate keys__. Elles seront présentées dans les exemples sur les relations entre entités.*
|
||||
Si on utilise les *data annotations*, une propriété pour être transformée en clé doit respecter les contraintes suivantes :
|
||||
* aucune contrainte sur le nommage de la propriété ; j'ai par exemple choisi ```UniqueId``` pour ```Nounours```, et ```FrakId``` pour ```Cylon```.
|
||||
* elle doit être précédée de l'annotation ```[Key]```
|
||||
* elle peut être générée par la base et dans ce cas elle doit être précédée de l'annotation ```[DatabaseGenerated(DatabaseGeneratedOption.Identity)]```, ou ne pas être générée par la base et dans ce cas être précédée de l'annotation ```[DatabaseGenerated(DatabaseGeneratedOption.None)]```.
|
||||
Dans ce dernier cas, c'est à l'utilisateur de gérer ses propres clés et leur unicité dans la base.
|
||||
* elle peut être de différents types ```int```, ```string```, ```Guid```, ```byte[]```... attention toutefois si vous choisissez de laisser la table générer les valeurs car certains fournisseurs ne savent pas générer tous les types.
|
||||
|
||||
## La classe ```Nounours```
|
||||
La classe ```Nounours``` utilise les *data annotations* et laisse le soin à la base de générer les clés uniques.
|
||||
```csharp
|
||||
[Key]
|
||||
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
|
||||
public int UniqueId
|
||||
{
|
||||
get; set;
|
||||
}
|
||||
```
|
||||
|
||||
## La classe ```Cylon```
|
||||
La classe ```Cylon``` utilise les *data annotations* et laisse le soin à l'utilisateur de gérer ses clés uniques.
|
||||
```csharp
|
||||
[Key]
|
||||
[DatabaseGenerated(DatabaseGeneratedOption.None)]
|
||||
public int FrakId
|
||||
{
|
||||
get; set;
|
||||
}
|
||||
```
|
||||
|
||||
### La classe ```Program```
|
||||
Cette classe est le point d'entrée du programme :
|
||||
* Elle crée des instances de ```Nounours``` et de ```Cylon``` et les ajoute en base après avoir nettoyé les tables au préalables.
|
||||
Notez que l'utilisateur n'a pas besoin de donner une valeur à ```Nounours.UniqueId``` puisque la base s'en charge, alors qu'il doit donner une valeur à ```Cylon.FrakId``` car la base de ne génère pas les clés.
|
||||
Si vous ne donnez pas une valeur à ```Cylon.FrakId```, alors la valeur par défaut est donnée (```0```). Il n'y aura pas de problème si cet identifiant n'a pas été donné, mais dès le deuxième ```Cylon```, vous aurez une exception.
|
||||
*Note : la valeur par défaut pour ```int``` est ```0``` ; pour ```Guid```, ```Guid.Empty``` ; pour ```string```, ```null```... *
|
||||
```csharp
|
||||
Nounours chewie = new Nounours { Nom = "Chewbacca", DateDeNaissance = new DateTime(1977, 5, 27), NbPoils = 1234567 };
|
||||
Nounours yoda = new Nounours { Nom = "Yoda", DateDeNaissance = new DateTime(1980, 5, 21), NbPoils = 3 };
|
||||
Nounours ewok = new Nounours { Nom = "Ewok", DateDeNaissance = new DateTime(1983, 5, 25), NbPoils = 3456789 };
|
||||
|
||||
Cylon c1 = new Cylon { FrakId = 2, Name = "John Cavil", Generation = 1 };
|
||||
Cylon c2 = new Cylon { FrakId = 4, Name = "Leoben Conoy", Generation = 2 };
|
||||
Cylon c3 = new Cylon { FrakId = 6, Name = "D'Anna Biers", Generation = 3 };
|
||||
Cylon c4 = new Cylon { FrakId = 8, Name = "Simon", Generation = 4 };
|
||||
Cylon c5 = new Cylon { FrakId = 10, Name = "Aaron Doral", Generation = 5 };
|
||||
Cylon c6 = new Cylon { FrakId = 12, Name = "Caprica 6", Generation = 6 };
|
||||
Cylon c7 = new Cylon { FrakId = 14, Name = "Daniel", Generation = 7 };
|
||||
Cylon c8 = new Cylon { FrakId = 16, Name = "Boomer", Generation = 8 };
|
||||
Cylon c9 = new Cylon { FrakId = 17, Name = "Athena", Generation = 8 };
|
||||
```
|
||||
* Elle affiche les ```Nounours``` et les ```Cylon```.
|
||||
*Notez la génération des identifiants pour la classe ```Nounours``` uniquement : si vous exécutez plusieurs fois l'exemple, les clés des ```Nounours``` changent mais pas celles des ```Cylon```.*
|
||||
|
||||
## 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_005_Keys_data_annotations
|
||||
```
|
||||
*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_005
|
||||
```
|
||||
* Création de la table :
|
||||
```
|
||||
dotnet ef database update
|
||||
```
|
||||
* Génération et exécution
|
||||
Vous pouvez maintenant générer et exécuter l'exemple **ex_042_005_Keys_data_annotations**.
|
||||
|
||||
* 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_005_Keys_data_annotations.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"/>
|
||||
|
||||
* Le résultat de l'exécution peut être :
|
||||
```
|
||||
database after cleaning and adding 3 Nounours and 9 Cylons and saving changes :
|
||||
Nounours 1: Chewbacca (27/05/1977, 1234567 poils)
|
||||
Nounours 2: Yoda (21/05/1980, 3 poils)
|
||||
Nounours 3: Ewok (25/05/1983, 3456789 poils)
|
||||
Cylon 2: John Cavil, Number 1
|
||||
Cylon 4: Leoben Conoy, Number 2
|
||||
Cylon 6: D'Anna Biers, Number 3
|
||||
Cylon 8: Simon, Number 4
|
||||
Cylon 10: Aaron Doral, Number 5
|
||||
Cylon 12: Caprica 6, Number 6
|
||||
Cylon 14: Daniel, Number 7
|
||||
Cylon 16: Boomer, Number 8
|
||||
Cylon 17: Athena, Number 8
|
||||
```
|
||||
*Note: les identifiants des ```Nounours``` peuvent varier en fonction du nombre d'exécution de l'exemple depuis la création de la base de données, mais pas ceux des ```Cylon``` puisqu'ils sont gérés par l'utilisateur.*
|
@ -0,0 +1,195 @@
|
||||
# ex_042_006_Keys_FluentAPI
|
||||
*07/01/2020 ⋅ Marc Chevaldonné*
|
||||
|
||||
---
|
||||
|
||||
Cet exemple traite des clés primaires associées aux entités.
|
||||
|
||||
Prérequis : je n'explique pas à travers cet exemple les principes de base d'**Entity Framework Core** et en particulier les chaînes de connexion et le lien entre entité et table.
|
||||
Pour plus de renseignements sur :
|
||||
* les chaînes de connexion : *ex_041_001_ConnectionStrings*
|
||||
* les liens entre entités et tables : *ex_042_001_EF_CF_conventions*, *ex_042_002_EF_CF_data_annotations* et *ex_042_003_EF_CF_Fluent_API*
|
||||
|
||||
Cet exemple montre le cas particulier de la gestion des clés primaires lors de l'utilisation la **Fluent API**.
|
||||
Vous pourrez trouver une version plus ou moins équivalente avec les *conventions d'écriture* ici : **ex_042_004_Keys_conventions**.
|
||||
Vous pourrez trouver une version plus ou moins équivalente avec la *data annotations* ici : **ex_042_005_Keys_data_annotations**.
|
||||
|
||||
---
|
||||
|
||||
## Les clés primaires
|
||||
Une clé permet de rendre unique chaque instance d'une entité. La plupart des entités n'ont qu'une seule clé qui est alors transformée en *clé primaire* pour les bases de données relationnelles.
|
||||
*Note: une entité peut avoir d'autres clés, on parle d'__alternate keys__. Elles seront présentées dans les exemples sur les relations entre entités.*
|
||||
Si on utilise la *Fluent API*, une propriété pour être transformée en clé doit respecter les contraintes suivantes
|
||||
(toutes les modifications se font dans la méthode ```OnModelCreating``` de votre classe dérivant de ```DbContext```) :
|
||||
* aucune contrainte sur le nommage de la propriété ; j'ai par exemple choisi ```UniqueId``` pour ```Nounours```, et ```FrakId``` pour ```Cylon```.
|
||||
* on définit qu'une propriété est une clé primaire avec la méthode d'extension ```HasKey``` sur la classe ```Entity```.
|
||||
```csharp
|
||||
//définition de la clé primaire de Nounours
|
||||
modelBuilder.Entity<Nounours>().HasKey(n => n.UniqueId);
|
||||
```
|
||||
```csharp
|
||||
//définition de la clé primaire de Cylon
|
||||
modelBuilder.Entity<Cylon>().HasKey(c => c.FrakId);
|
||||
```
|
||||
* elle peut être générée par la base et dans ce cas, on ajoute à la ```Property``` la méthode d'extension ```ValueGeneratedOnAdd()```.
|
||||
```csharp
|
||||
//définition du mode de génération de la clé : génération à l'insertion
|
||||
modelBuilder.Entity<Nounours>().Property(n => n.UniqueId).ValueGeneratedOnAdd();
|
||||
```
|
||||
* elle peut ne pas être générée par la base et être gérée par l'utilisateur, dans ce cas on utilise la méthode d'extension ```ValueGeneratedNever()```.
|
||||
Dans ce dernier cas, c'est à l'utilisateur de gérer ses propres clés et leur unicité dans la base.
|
||||
```csharp
|
||||
//définition du mode de génération de la clé : génération gérée par l'utilisateur (jamais par la base)
|
||||
modelBuilder.Entity<Cylon>().Property(c => c.FrakId).ValueGeneratedNever();
|
||||
```
|
||||
* elle peut être de différents types ```int```, ```string```, ```Guid```, ```byte[]```... attention toutefois si vous choisissez de laisser la table générer les valeurs car certains fournisseurs ne savent pas générer tous les types.
|
||||
|
||||
## Les clés primaires composites
|
||||
En *Fluent API* (et seulement en *Fluent API*), on peut définir des clés composites, c'est-à-dire se basant sur plusieurs propriétés.
|
||||
Pour cela, on utilise également ```HasKey```et on l'associe à un type anonyme, comme par exemple pour la classe ```Ordinateur``` dans cet exemple, dont la clé est un composite des propriétés ```CodeId``` et ```Modele```.
|
||||
Ainsi, dans la méthode ```OnModelCreating``` de votre classe dérivant de ```DbContext```, on ajoute :
|
||||
```csharp
|
||||
//définition d'une clé primaire composite pour Ordinateur
|
||||
modelBuilder.Entity<Ordinateur>().HasKey(o => new { o.CodeId, o.Modele });
|
||||
```
|
||||
Les clés composites ne peuvent pas être générées par la base.
|
||||
|
||||
## Les classes POCO ```Nounours```, ```Cylon``` et ```Ordinateur```
|
||||
Lorsqu'on utilise la *Fluent API*, ces classes n'ont aucune annotation. La classe ```Ordinateur``` n'a pas de propriété "Id".
|
||||
|
||||
## La classe ```DBEntites : DbContext```
|
||||
La classe dérivant de ```DbContext``` ressemble dès lors dans notre exemple à :
|
||||
```csharp
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
namespace ex_042_006_Keys_FluentAPI
|
||||
{
|
||||
class DBEntities : DbContext
|
||||
{
|
||||
public DbSet<Nounours> NounoursSet { get; set; }
|
||||
public DbSet<Cylon> CylonsSet { get; set; }
|
||||
public DbSet<Ordinateur> Ordinateurs { get; set; }
|
||||
|
||||
|
||||
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
|
||||
{
|
||||
optionsBuilder.UseSqlServer(@"Server=(localdb)\mssqllocaldb;Database=ex_042_006_Keys_FluentAPI.Nounours.mdf;Trusted_Connection=True;");
|
||||
}
|
||||
|
||||
protected override void OnModelCreating(ModelBuilder modelBuilder)
|
||||
{
|
||||
//définition de la clé primaire de Nounours
|
||||
modelBuilder.Entity<Nounours>().HasKey(n => n.UniqueId);
|
||||
//définition du mode de génération de la clé : génération à l'insertion
|
||||
modelBuilder.Entity<Nounours>().Property(n => n.UniqueId).ValueGeneratedOnAdd();
|
||||
|
||||
//définition de la clé primaire de Cylon
|
||||
modelBuilder.Entity<Cylon>().HasKey(c => c.FrakId);
|
||||
//définition du mode de génération de la clé : génération gérée par l'utilisateur (jamais par la base)
|
||||
modelBuilder.Entity<Cylon>().Property(c => c.FrakId).ValueGeneratedNever();
|
||||
|
||||
//définition d'une clé primaire composite pour Ordinateur
|
||||
modelBuilder.Entity<Ordinateur>().HasKey(o => new { o.CodeId, o.Modele });
|
||||
//une clé composite ne peut pas être générée par la base
|
||||
|
||||
base.OnModelCreating(modelBuilder);
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## La classe ```Program```
|
||||
Cette classe est le point d'entrée du programme :
|
||||
* Elle crée des instances de ```Nounours```, de ```Cylon``` et d'```Ordinateur``` et les ajoute en base après avoir nettoyé les tables au préalables.
|
||||
Notez que l'utilisateur n'a pas besoin de donner une valeur à ```Nounours.UniqueId``` puisque la base s'en charge, alors qu'il doit donner une valeur à ```Cylon.FrakId``` car la base de ne génère pas les clés.
|
||||
Si vous ne donnez pas une valeur à ```Cylon.FrakId```, alors la valeur par défaut est donnée (```0```). Il n'y aura pas de problème si cet identifiant n'a pas été donné, mais dès le deuxième ```Cylon```, vous aurez une exception.
|
||||
_Note : la valeur par défaut pour ```int``` est ```0``` ; pour ```Guid```, ```Guid.Empty``` ; pour ```string```, ```null```..._
|
||||
Notez que l'utilisateur doit garantir pour ses instances d'```Ordinateur``` l'unicité des couples ```CodeId```/```Modele```, puisqu'il s'agit d'une clé composite.
|
||||
```csharp
|
||||
Nounours chewie = new Nounours { Nom = "Chewbacca", DateDeNaissance = new DateTime(1977, 5, 27), NbPoils = 1234567 };
|
||||
Nounours yoda = new Nounours { Nom = "Yoda", DateDeNaissance = new DateTime(1980, 5, 21), NbPoils = 3 };
|
||||
Nounours ewok = new Nounours { Nom = "Ewok", DateDeNaissance = new DateTime(1983, 5, 25), NbPoils = 3456789 };
|
||||
|
||||
Cylon c1 = new Cylon { FrakId = 2, Name = "John Cavil", Generation = 1 };
|
||||
Cylon c2 = new Cylon { FrakId = 4, Name = "Leoben Conoy", Generation = 2 };
|
||||
Cylon c3 = new Cylon { FrakId = 6, Name = "D'Anna Biers", Generation = 3 };
|
||||
Cylon c4 = new Cylon { FrakId = 8, Name = "Simon", Generation = 4 };
|
||||
Cylon c5 = new Cylon { FrakId = 10, Name = "Aaron Doral", Generation = 5 };
|
||||
Cylon c6 = new Cylon { FrakId = 12, Name = "Caprica 6", Generation = 6 };
|
||||
Cylon c7 = new Cylon { FrakId = 14, Name = "Daniel", Generation = 7 };
|
||||
Cylon c8 = new Cylon { FrakId = 16, Name = "Boomer", Generation = 8 };
|
||||
Cylon c9 = new Cylon { FrakId = 17, Name = "Athena", Generation = 8 };
|
||||
|
||||
Ordinateur o1 = new Ordinateur { Année = 2019, Modele = "MacBook Pro", CodeId = "IUT_1" };
|
||||
Ordinateur o2 = new Ordinateur { Année = 2017, Modele = "MacBook Pro", CodeId = "IUT_2" };
|
||||
Ordinateur o3 = new Ordinateur { Année = 2016, Modele = "MacBook Pro", CodeId = "IUT_4" };
|
||||
Ordinateur o4 = new Ordinateur { Année = 2019, Modele = "Dell Latitude", CodeId = "IUT_1" };
|
||||
Ordinateur o5 = new Ordinateur { Année = 2012, Modele = "Dell Latitude", CodeId = "IUT_2" };
|
||||
Ordinateur o6 = new Ordinateur { Année = 2013, Modele = "Dell Latitude", CodeId = "IUT_3" };
|
||||
```
|
||||
* Elle affiche les ```Nounours```, les ```Cylon``` et les ```Ordinateur```.
|
||||
*Notez la génération des identifiants pour la classe ```Nounours``` uniquement : si vous exécutez plusieurs fois l'exemple, les clés des ```Nounours``` changent mais pas celles des ```Cylon```.*
|
||||
|
||||
## 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_006_Keys_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_006
|
||||
```
|
||||
* Création de la table :
|
||||
```
|
||||
dotnet ef database update
|
||||
```
|
||||
* Génération et exécution
|
||||
Vous pouvez maintenant générer et exécuter l'exemple **ex_042_006_Keys_FluentAPI**.
|
||||
|
||||
* 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_006_Keys_FluentAPI.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"/>
|
||||
|
||||
* Le résultat de l'exécution peut être :
|
||||
```
|
||||
database after cleaning and adding 3 Nounours and 9 Cylons and saving changes :
|
||||
Nounours 1: Chewbacca (27/05/1977, 1234567 poils)
|
||||
Nounours 2: Ewok (25/05/1983, 3456789 poils)
|
||||
Nounours 3: Yoda (21/05/1980, 3 poils)
|
||||
Cylon 2: John Cavil, Number 1
|
||||
Cylon 4: Leoben Conoy, Number 2
|
||||
Cylon 6: D'Anna Biers, Number 3
|
||||
Cylon 8: Simon, Number 4
|
||||
Cylon 10: Aaron Doral, Number 5
|
||||
Cylon 12: Caprica 6, Number 6
|
||||
Cylon 14: Daniel, Number 7
|
||||
Cylon 16: Boomer, Number 8
|
||||
Cylon 17: Athena, Number 8
|
||||
Computer IUT_1 (Dell Latitude, 2019)
|
||||
Computer IUT_1 (MacBook Pro, 2019)
|
||||
Computer IUT_2 (Dell Latitude, 2012)
|
||||
Computer IUT_2 (MacBook Pro, 2017)
|
||||
Computer IUT_3 (Dell Latitude, 2013)
|
||||
Computer IUT_4 (MacBook Pro, 2016)
|
||||
```
|
||||
*Note: les identifiants des ```Nounours``` peuvent varier en fonction du nombre d'exécution de l'exemple depuis la création de la base de données, mais pas ceux des ```Cylon``` puisqu'ils sont gérés par l'utilisateur.*
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in new issue