# ex_042_003_EF_CF_Fluent_API
*03/01/2020 &sdot; 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*