ended ex_042_012

EFCore3_Reforged
Marc CHEVALDONNE 5 years ago
parent ac4913d32a
commit bd505a2981

@ -413,8 +413,6 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ex_039_002_LINQ_to_XML", "e
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ex_040_001_LINQ_to_Json", "ex_040_001_LINQ_to_Json\ex_040_001_LINQ_to_Json.csproj", "{B506ECD8-853D-4E69-8F0A-AFCDB7FEABF3}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ex_042_006_EF_CF_One_to_One", "ex_042_006_EF_CF_One_to_One\ex_042_006_EF_CF_One_to_One.csproj", "{0770D7BB-F7FA-4E44-900C-F72F33110AF9}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ex_042_007_EF_CF_One_to_One_FluentAPI", "ex_042_007_EF_CF_One_to_One_FluentAPI\ex_042_007_EF_CF_One_to_One_FluentAPI.csproj", "{4D079A62-B303-4900-BEC1-994BC26BCCA2}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ex_042_008_EF_CF_One_to_Many", "ex_042_008_EF_CF_One_to_Many\ex_042_008_EF_CF_One_to_Many.csproj", "{91FD20B6-250C-437B-9FBE-DA4D2B87DE03}"
@ -580,7 +578,7 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ex_042_010_SinglePropertyNa
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ex_042_011_SinglePropertyNavigation_FluentAPI", "p08_BDD_EntityFramework\ex_042_011_SinglePropertyNavigation_FluentAPI\ex_042_011_SinglePropertyNavigation_FluentAPI.csproj", "{CCC912CB-2962-441E-AB6B-97B0CB32AF11}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ex_042_012_OneToOne_conventions", "p08_BDD_EntityFramework\ex_042_012_OneToOne_conventions\ex_042_012_OneToOne_conventions.csproj", "{762C349D-5685-43FA-A077-2F3BDD07C898}"
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ex_042_012_OneToOne_conventions", "p08_BDD_EntityFramework\ex_042_012_OneToOne_conventions\ex_042_012_OneToOne_conventions.csproj", "{762C349D-5685-43FA-A077-2F3BDD07C898}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
@ -3488,26 +3486,6 @@ Global
{B506ECD8-853D-4E69-8F0A-AFCDB7FEABF3}.Release|x64.Build.0 = Release|Any CPU
{B506ECD8-853D-4E69-8F0A-AFCDB7FEABF3}.Release|x86.ActiveCfg = Release|Any CPU
{B506ECD8-853D-4E69-8F0A-AFCDB7FEABF3}.Release|x86.Build.0 = Release|Any CPU
{0770D7BB-F7FA-4E44-900C-F72F33110AF9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{0770D7BB-F7FA-4E44-900C-F72F33110AF9}.Debug|Any CPU.Build.0 = Debug|Any CPU
{0770D7BB-F7FA-4E44-900C-F72F33110AF9}.Debug|ARM.ActiveCfg = Debug|Any CPU
{0770D7BB-F7FA-4E44-900C-F72F33110AF9}.Debug|ARM.Build.0 = Debug|Any CPU
{0770D7BB-F7FA-4E44-900C-F72F33110AF9}.Debug|ARM64.ActiveCfg = Debug|Any CPU
{0770D7BB-F7FA-4E44-900C-F72F33110AF9}.Debug|ARM64.Build.0 = Debug|Any CPU
{0770D7BB-F7FA-4E44-900C-F72F33110AF9}.Debug|x64.ActiveCfg = Debug|Any CPU
{0770D7BB-F7FA-4E44-900C-F72F33110AF9}.Debug|x64.Build.0 = Debug|Any CPU
{0770D7BB-F7FA-4E44-900C-F72F33110AF9}.Debug|x86.ActiveCfg = Debug|Any CPU
{0770D7BB-F7FA-4E44-900C-F72F33110AF9}.Debug|x86.Build.0 = Debug|Any CPU
{0770D7BB-F7FA-4E44-900C-F72F33110AF9}.Release|Any CPU.ActiveCfg = Release|Any CPU
{0770D7BB-F7FA-4E44-900C-F72F33110AF9}.Release|Any CPU.Build.0 = Release|Any CPU
{0770D7BB-F7FA-4E44-900C-F72F33110AF9}.Release|ARM.ActiveCfg = Release|Any CPU
{0770D7BB-F7FA-4E44-900C-F72F33110AF9}.Release|ARM.Build.0 = Release|Any CPU
{0770D7BB-F7FA-4E44-900C-F72F33110AF9}.Release|ARM64.ActiveCfg = Release|Any CPU
{0770D7BB-F7FA-4E44-900C-F72F33110AF9}.Release|ARM64.Build.0 = Release|Any CPU
{0770D7BB-F7FA-4E44-900C-F72F33110AF9}.Release|x64.ActiveCfg = Release|Any CPU
{0770D7BB-F7FA-4E44-900C-F72F33110AF9}.Release|x64.Build.0 = Release|Any CPU
{0770D7BB-F7FA-4E44-900C-F72F33110AF9}.Release|x86.ActiveCfg = Release|Any CPU
{0770D7BB-F7FA-4E44-900C-F72F33110AF9}.Release|x86.Build.0 = Release|Any CPU
{4D079A62-B303-4900-BEC1-994BC26BCCA2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{4D079A62-B303-4900-BEC1-994BC26BCCA2}.Debug|Any CPU.Build.0 = Debug|Any CPU
{4D079A62-B303-4900-BEC1-994BC26BCCA2}.Debug|ARM.ActiveCfg = Debug|Any CPU
@ -4993,7 +4971,6 @@ Global
{CF1AE501-3518-45CC-94AA-D6DE047EA022} = {13CDB6B1-9B1D-43A9-9E9E-08989D55ACA5}
{26400B99-2FB6-4B5F-BE2B-D15124F2B51D} = {13CDB6B1-9B1D-43A9-9E9E-08989D55ACA5}
{B506ECD8-853D-4E69-8F0A-AFCDB7FEABF3} = {3BD95300-E2F6-4CA6-B4CC-5D19DF5C6AC0}
{0770D7BB-F7FA-4E44-900C-F72F33110AF9} = {593200F9-6D14-43BC-9289-8BB75FAC6552}
{4D079A62-B303-4900-BEC1-994BC26BCCA2} = {593200F9-6D14-43BC-9289-8BB75FAC6552}
{91FD20B6-250C-437B-9FBE-DA4D2B87DE03} = {593200F9-6D14-43BC-9289-8BB75FAC6552}
{2C53F069-6E87-4A36-8915-E0219EBA8BA7} = {593200F9-6D14-43BC-9289-8BB75FAC6552}

@ -1,48 +0,0 @@
// ========================================================================
//
// Copyright (C) 2016-2017 MARC CHEVALDONNE
// marc.chevaldonne.free.fr
//
// Module : CarnetDeSante.cs
// Author : Marc Chevaldonné
// Creation date : 2016-10-18
//
// ========================================================================
using System;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
namespace ex_042_006_EF_CF_One_to_One
{
/// <summary>
/// CarnetDeSante est une classe POCO, i.e. Plain Old CLR Object
/// Elle a une relation 1-1 avec la classe Nounours via la propriété Owner.
/// Notez l'annotation ForeignKey("Nounours") sur la propriété UniqueId qui indique qu'il ne faut pas générer
/// une clé primaire pour ces instances, mais utiliser celle du Owner associé
/// </summary>
[Table("Carnets")]
public class CarnetDeSante
{
[Key, ForeignKey("Owner")]
public Guid UniqueId
{
get; set;
}
public DateTime LastModified
{
get; set;
}
public virtual Nounours Owner
{
get; set;
}
public override string ToString()
{
return $"{UniqueId} : carnet de {Owner.Nom}, modifié la dernière fois le {LastModified.ToString("d")}";
}
}
}

@ -1,79 +0,0 @@
// ========================================================================
//
// Copyright (C) 2016-2017 MARC CHEVALDONNE
// marc.chevaldonne.free.fr
//
// Module : DbContextInitializer.cs
// Author : Marc Chevaldonné
// Creation date : 2016-10-18
//
// ========================================================================
using Microsoft.EntityFrameworkCore;
using System;
namespace ex_042_006_EF_CF_One_to_One
{
/// <summary>
/// initialiseur de stratégies...
/// </summary>
public static class DbContextInitializer
{
/// <summary>
/// remplissage de la base avec des données stubbées.
/// </summary>
/// <param name="context">base à remplir</param>
public static void Seed(NounoursDBEntities context)
{
SetInitializer(context, InitializationStrategies.DropCreateDatabaseAlways);
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 };
CarnetDeSante carnet1 = new CarnetDeSante { Owner = chewie, LastModified = DateTime.Today };
CarnetDeSante carnet2 = new CarnetDeSante { Owner = yoda, LastModified = new DateTime(1980, 5, 21) };
CarnetDeSante carnet3 = new CarnetDeSante { Owner = ewok, LastModified = new DateTime(1983, 5, 25) };
chewie.Carnet = carnet1;
yoda.Carnet = carnet2;
ewok.Carnet = carnet3;
context.NounoursSet.AddRange(new Nounours[] { chewie, yoda, ewok });
context.Carnets.AddRange(new CarnetDeSante[] { carnet1, carnet2, carnet3 });
context.SaveChanges();
}
/// <summary>
/// les différentes stratégies de création de la base
/// </summary>
public enum InitializationStrategies
{
CreateDatabaseIfNotExists,
DropCreateDatabaseIfModelChanges,
DropCreateDatabaseAlways
}
public static void SetInitializer(DbContext context, InitializationStrategies strategy)
{
switch(strategy)
{
//par défaut : crée la base seulement si elle n'existe pas
default:
case InitializationStrategies.CreateDatabaseIfNotExists:
context.Database.EnsureCreated();
break;
//recrée la base même si elle existe déjà
case InitializationStrategies.DropCreateDatabaseAlways:
context.Database.EnsureDeleted();
context.Database.EnsureCreated();
break;
//recrée la base seulement si le modèle change : impossible aujourd'hui en Entity Framework Core...
case InitializationStrategies.DropCreateDatabaseIfModelChanges:
throw new NotImplementedException("Le mode DropCreateDatabaseIfModelChanges ne peut pas encore exister sous Entity Framework Core");
}
}
}
}

@ -1,108 +0,0 @@
// ========================================================================
//
// Copyright (C) 2016-2017 MARC CHEVALDONNE
// marc.chevaldonne.free.fr
//
// Module : Nounours.cs
// Author : Marc Chevaldonné
// Creation date : 2016-10-18
//
// ========================================================================
using System;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
namespace ex_042_006_EF_CF_One_to_One
{
/// <summary>
/// Nounours est une classe POCO, i.e. Plain Old CLR Object.
/// Elle a une relation 1-1 avec la classe CarnetDeSante via la propriété Carnet.
/// La clé primaire est générée lors de l'insertion en table (et est utilisée dans la classe CarnetDeSante
/// pour faire l'association. cf. CarnetDeSante).
[Table("TableNounours")]
public class Nounours
{
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public Guid UniqueId
{
get; set;
}
public virtual CarnetDeSante Carnet { get; set; }
[Required]
[MaxLength(256)]
public string Nom
{
get;
set;
}
[Column("Naissance")]
public DateTime DateDeNaissance
{
get;
set;
}
[NotMapped]
public int NbPoils
{
get;
set;
}
/// <summary>
/// returns a hash code in order to use this class in hash table
/// </summary>
/// <returns>hash code</returns>
public override int GetHashCode()
{
return Nom.GetHashCode();
}
/// <summary>
/// checks if the "right" object is equal to this Nounours or not
/// </summary>
/// <param name="right">the other object to be compared with this Nounours</param>
/// <returns>true if equals, false if not</returns>
public override bool Equals(object right)
{
//check null
if (object.ReferenceEquals(right, null))
{
return false;
}
if (object.ReferenceEquals(this, right))
{
return true;
}
if (this.GetType() != right.GetType())
{
return false;
}
return this.Equals(right as Nounours);
}
/// <summary>
/// checks if this Nounours is equal to the other Nounours
/// </summary>
/// <param name="other">the other Nounours to be compared with</param>
/// <returns>true if equals</returns>
public bool Equals(Nounours other)
{
return (this.Nom.Equals(other.Nom) && this.DateDeNaissance == other.DateDeNaissance);
}
public override string ToString()
{
return $"{UniqueId}: {Nom} ({DateDeNaissance:dd/MM/yyyy}, {NbPoils} poils)";
}
}
}

@ -1,30 +0,0 @@
// ========================================================================
//
// Copyright (C) 2016-2017 MARC CHEVALDONNE
// marc.chevaldonne.free.fr
//
// Module : NounoursDBEntities.cs
// Author : Marc Chevaldonné
// Creation date : 2016-10-18
//
// ========================================================================
using Microsoft.EntityFrameworkCore;
namespace ex_042_006_EF_CF_One_to_One
{
/// <summary>
/// La classe qui dérive de DbContext est celle qui permettra de faire les opérations CRUD sur le modèle.
/// Cette classe contient deux DbSet<T> pour permettre de réaliser des opérations CRUD sur les types T, ici Nounours et CarnetDeSante.
/// </summary>
public class NounoursDBEntities : DbContext
{
public virtual DbSet<Nounours> NounoursSet { get; set; }
public virtual DbSet<CarnetDeSante> Carnets { get; set; }
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
optionsBuilder.UseSqlServer(@"Server=(localdb)\mssqllocaldb;Database=ex_042_006_EF_CF_One_to_One.Nounours.mdf;Trusted_Connection=True;");
}
}
}

@ -1,76 +0,0 @@
// ========================================================================
//
// Copyright (C) 2016-2017 MARC CHEVALDONNE
// marc.chevaldonne.free.fr
//
// Module : Program.cs
// Author : Marc Chevaldonné
// Creation date : 2016-10-18
//
// ========================================================================
using Microsoft.EntityFrameworkCore;
using System;
using static System.Console;
namespace ex_042_006_EF_CF_One_to_One
{
public class Program
{
/// <summary>
/// Cet exemple montre comment construire une relation 1-1 dans la base de données en utilisant les conventions de nommage Entity Framework.
///
/// on utilise les données stubbées de MyStubDataInitializationStrategy
/// On affiche les Nounours et les Carnets de santé.
/// Constatez que les identifiants sont bien les mêmes à cause de la relation 1-1.
/// </summary>
static void Main(string[] args)
{
OutputEncoding = System.Text.Encoding.UTF8;
try
{
using (NounoursDBEntities db = new NounoursDBEntities())
{
//choix de la stratégie et remplissage avec des données stubbées
DbContextInitializer.Seed(db);
}
using (NounoursDBEntities db = new NounoursDBEntities())
{
WriteLine("nounours : ");
foreach (var n in db.NounoursSet.Include(n => n.Carnet))
{
WriteLine($"\t{n}, LastModified: {n.Carnet.LastModified.ToString("d")}");
}
WriteLine("carnets de santé : ");
foreach (var c in db.Carnets)
{
WriteLine($"\t{c}");
}
}
}
catch (NotImplementedException exception)
{
WriteLine(exception.Message);
}
catch (Exception)
{
WriteLine("Votre base de données n'existe pas. C'est peut-être la première fois que vous exécutez cet exemple.");
WriteLine("Pour créer la base de données, suivez les instructions suivantes (que vous retrouvez en commentaires dans la classe Program) :");
WriteLine("Pour créer la base, ouvrez un invite de commandes et placez-vous dans le dossier de ce projet, ou bien,");
WriteLine("- dans Visual Studio ouvrez la Console du Gestionnaire de package (Outils -> Gestionnaire de package NuGet -> Console du Gestionnaire de package),");
WriteLine("- dans cette Console, vous devriez être dans le dossier de la solution, déplacez-vous dans celui du projet (ici : cd ex_042_006_EF_CF_One_to_One)");
WriteLine("- tapez : dotnet restore (pour restaurer les packages .NET Core)");
WriteLine("- tapez : dotnet ef migrations add MyFirstMigration");
WriteLine(" note : vous pourrez détruire le dossier Migrations une fois la base créée");
WriteLine("- tapez : dotnet ef database update");
WriteLine(" Ceci génère la base de données en utilisant la migration, et en particulier votre classe DBContext et vos classes POCO.");
WriteLine("\nDans cet exemple, une base de données SQLServer est créée et en particulier la table Nounours.mdf");
}
ReadLine();
}
}
}

@ -1,21 +0,0 @@
<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>
</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>

@ -34,6 +34,7 @@ Ce chapitre s'attardera sur le lien entre le mod
* [**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*.
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* :

@ -20,10 +20,6 @@ namespace ex_042_012_OneToOne_conventions
{
/// <summary>
/// Cet exemple montre comment construire une relation 1-1 dans la base de données en utilisant les conventions de nommage Entity Framework.
///
/// on utilise les données stubbées de MyStubDataInitializationStrategy
/// On affiche les Nounours et les Carnets de santé.
/// Constatez que les identifiants sont bien les mêmes à cause de la relation 1-1.
/// </summary>
static void Main(string[] args)
{
@ -31,25 +27,43 @@ namespace ex_042_012_OneToOne_conventions
try
{
//using (NounoursDBEntities db = new NounoursDBEntities())
//{
// //choix de la stratégie et remplissage avec des données stubbées
// //DbContextInitializer.Seed(db);
//}
using (NounoursDBEntities db = new NounoursDBEntities())
{
WriteLine("nounours : ");
WriteLine("Contenu de la base (nounours) : ");
foreach (var n in db.NounoursSet.Include(n => n.Carnet))
{
WriteLine($"\t{n}, LastModified: {n.Carnet.LastModified.ToString("d")}");
}
WriteLine("carnets de santé : ");
WriteLine("Contenu de la base (carnets de santé) : ");
foreach (var c in db.Carnets)
{
WriteLine($"\t{c}");
}
WriteLine("\nAjout d'un nounours et de son carnet de santé\n");
Nounours porg = new Nounours { Nom = "Porg", DateDeNaissance = new DateTime(2017, 7, 19), NbPoils = 123 };
CarnetDeSante carnetPorg = new CarnetDeSante { LastModified = DateTime.Now, Owner = porg };
porg.Carnet = carnetPorg;
db.AddRange(porg, carnetPorg);
db.SaveChanges();
}
using (NounoursDBEntities db = new NounoursDBEntities())
{
WriteLine("Contenu de la base (nounours) : ");
foreach (var n in db.NounoursSet.Include(n => n.Carnet))
{
WriteLine($"\t{n}, LastModified: {n.Carnet.LastModified.ToString("d")}");
}
WriteLine("Contenu de la base (carnets de santé) : ");
foreach (var c in db.Carnets)
{
WriteLine($"\t{c}");
}
}
}
catch (NotImplementedException exception)

@ -0,0 +1,216 @@
# ex_042_012_OneToOne_conventions
*20/01/2020 &sdot; Marc Chevaldonné*
---
Cet exemple montre comment réaliser une relation *One To One* entre deux entités avec *Entity Framework Core* et l'*annotation de données*.
Une version équivalente réalisée avec la *Fluent API* est disponible dans l'exemple
[**ex_042_013 : One To One with Fluent API**](../ex_042_013_OneToOne_FluentAPI)
---
## Comment est construit cet exemple ?
* Le projet est de type .NET Core
* Il contient quatre classes :
* ```Nounours```
* ```CarnetDeSante```
* ```NounoursDBEntities```
* ```StubbedContext```
### Les classes entités : ```Nounours``` et ```CarnetDeSante```
Un ```Nounours``` contient différentes propriétés (```Nom```, ```DateDeNaissance```, ```NbPoils```) dont ```Carnet``` de type ```CarnetDeSante```.
Un ```CarnetDeSante``` possède une propriété de type ```DateTime``` (```LastModified```), et une propriété ```Owner``` de type ```Nounours```.
On a donc bien une relation *One To One* puisqu'un ```Nounours``` possède un ```CarnetDeSante``` et qu'un ```CarnetDeSante``` possède un ```Nounours```.
<img src="./readme_files/ex_042_012_classDiagram.svg"/>
Ce qu'il faut noter :
* ```Nounours``` possède une association vers ```CarnetDeSante```
```csharp
public CarnetDeSante Carnet { get; set; }
```
* ```CarnetDeSante``` possède une association vers ```Nounours```
```csharp
public Nounours Owner { get; set; }
```
* ```Nounours``` possède un identifiant unique (qui peut être utilisé par convention d'écriture ou _annotation de données_) : ```[Key]```.
* Cet identifiant est généré par la base : ```[DatabaseGenerated(DatabaseGeneratedOption.Identity)]```.
* ```CarnetDeSante``` possède un identifiant unique (qui peut être utilisé par convention d'écriture ou _annotation de données_) : ```[Key]```.
* Cet identifiant fait référence à une clé étrangère qui est la clé primaire associée à l'entité pointée par sa propriété ```Owner``` : ```ForeignKey("Owner")```.
```csharp
[Key, ForeignKey("Owner")]
public int UniqueId
{
get; set;
}
```
* En conséquence, l'identifiant unique ```UniqueId``` de ```CarnetDeSante``` aura la même valeur que l'```UniqueId``` d'une entité ```Nounours```.
### La classe ```NounoursDBEntities```
* Comme dans les exemples précédents, ```NounoursDBEntities``` dérive de ```DbContext```.
* ```NounoursDBEntities``` déclare deux ```DbSet``` : un de ```Nounours``` et l'autre de ```CarnetDeSante```.
```csharp
public DbSet<Nounours> NounoursSet { get; set; }
public DbSet<CarnetDeSante> Carnets { get; set; }
```
##### Quelques explications supplémentaires :
Pour pouvoir relier une entité ```Nounours``` à une entité ```CarnetDeSante``` de manière bidirectionnelle, les propriétés ```Nounours.Carnet``` et ```CarnetDeSante.Owner``` sont suffisantes, à condition qu'une clé étrangère soit déclarée et utilisée.
C'est le cas grâce à l'annotation de données ```ForeignKey("Owner")``` qui s'occupe de définir de quelle manière les deux entités sont reliées.
### La classe ```StubbedContext```
* ```StubbedContext``` est une classe fille de ```NounoursDBEntities```.
Son rôle est de proposer un Stub en plus de ce que sait déjà faire sa classe mère. Elle ne sera donc utilisée que pour des tests unitaires ou fonctionnels.
En conséquence, elle reprend tout ce que fait sa classe mère et ne change que la méthode ```OnModelCreating``` qui appelle la méthode de la classe mère puis ajoute des instances d'entités, grâce à la méthode d'extension ```HasData```.
```csharp
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
modelBuilder.Entity<CarnetDeSante>().HasData(
new CarnetDeSante { UniqueId=1, LastModified = DateTime.Today },
new CarnetDeSante { UniqueId=2, LastModified = new DateTime(1980, 5, 21) },
new CarnetDeSante { UniqueId=3, LastModified = new DateTime(1983, 5, 25) }
);
modelBuilder.Entity<Nounours>().HasData(
new Nounours { UniqueId=1, Nom = "Chewbacca", DateDeNaissance = new DateTime(1977, 5, 27), NbPoils = 1234567 },
new Nounours { UniqueId=2, Nom = "Yoda", DateDeNaissance = new DateTime(1980, 5, 21), NbPoils = 3 },
new Nounours { UniqueId=3, Nom = "Ewok", DateDeNaissance = new DateTime(1983, 5, 25), NbPoils = 3456789 }
);
}
```
* Remarquez que __À AUCUN MOMENT__ nous ne précisons les valeurs des propriétés ```Owner``` de ```CarnetDeSante``` et ```Carnet``` de ```Nounours```.
Le simple fait d'utiliser la même clé (propriétés ```UniqueId```) est suffisant puisque celle de ```CarnetDeSante``` est une clé étrangère.
* Notez que ce ne sera pas le cas lors d'une utilisation *classique* de nos classes (ajout, modification...). Nous ne donnerons plus les identifiants directement mais les références des propriétés ```Carnet``` et ```Owner```.
### La classe ```Program```
* La classe ```StubbedContext``` est ensuite indirectement utilisée dans ```Program``` pour remplir la base de manière tout à fait classique et ne nécessite aucun appel supplémentaire : ceci est fait lors de la migration et la création de la base.
* On affiche tout d'abord le contenu de la base (c'est-à-dire rien ou d'anciennes données si la migration est faite à partir de ```NounoursDBEntites```) ou le stub (si la migration est faite à partir de ```StubbedContext```).
*Notez l'utilisation d'```Include``` dans ```db.NounoursSet.Include(n => n.Carnet)``` sinon, les ```CarnetDeSante``` ne sont pas chargés. ```Include``` n'est pas utilisé ensuite dans ```db.Carnets``` car les ```Nounours``` ont déjà été chargés depuis la connexion. Mais on aurait pu faire les accès dans l'autre sens et dans ce cas d'abord ```db.Carnets.Include(c => c.Owner)``` puis simplement ```db.NounoursSet```.*
```csharp
using (NounoursDBEntities db = new NounoursDBEntities())
{
WriteLine("Contenu de la base (nounours) : ");
foreach (var n in db.NounoursSet.Include(n => n.Carnet))
{
WriteLine($"\t{n}, LastModified: {n.Carnet.LastModified.ToString("d")}");
}
WriteLine("Contenu de la base (carnets de santé) : ");
foreach (var c in db.Carnets)
{
WriteLine($"\t{c}");
}
//...
}
```
* La suite de l'exemple ajoute un nouveau ```Nounours``` et son ```CarnetDeSante``` puis affiche le contenu de la base de données.
```csharp
using (NounoursDBEntities db = new NounoursDBEntities())
{
//...
WriteLine("\nAjout d'un nounours et de son carnet de santé\n");
Nounours porg = new Nounours { Nom = "Porg", DateDeNaissance = new DateTime(2017, 7, 19), NbPoils = 123 };
CarnetDeSante carnetPorg = new CarnetDeSante { LastModified = DateTime.Now, Owner = porg };
porg.Carnet = carnetPorg;
db.AddRange(porg, carnetPorg);
db.SaveChanges();
}
```
```csharp
using (NounoursDBEntities db = new NounoursDBEntities())
{
WriteLine("Contenu de la base (nounours) : ");
foreach (var n in db.NounoursSet.Include(n => n.Carnet))
{
WriteLine($"\t{n}, LastModified: {n.Carnet.LastModified.ToString("d")}");
}
WriteLine("Contenu de la base (carnets de santé) : ");
foreach (var c in db.Carnets)
{
WriteLine($"\t{c}");
}
}
```
## 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* sous Windows ou le *Terminal* sous MacOSX, 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_012_OneToOne_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 ex_042_012 --context StubbedContext
```
* Création de la table :
```
dotnet ef database update --context StubbedContext
```
* Génération et exécution
Vous pouvez maintenant générer et exécuter l'exemple **ex_042_012_OneToOne_conventions**.
* Le résultat de l'exécution va ressembler à :
```
Contenu de la base (nounours) :
1: Chewbacca (27/05/1977, 1234567 poils), LastModified: 20/01/2020
2: Yoda (21/05/1980, 3 poils), LastModified: 21/05/1980
3: Ewok (25/05/1983, 3456789 poils), LastModified: 25/05/1983
Contenu de la base (carnets de santé) :
1 : carnet de Chewbacca, modifié la dernière fois le 20/01/2020
2 : carnet de Yoda, modifié la dernière fois le 21/05/1980
3 : carnet de Ewok, modifié la dernière fois le 25/05/1983
Ajout d'un nounours et de son carnet de santé
Contenu de la base (nounours) :
1: Chewbacca (27/05/1977, 1234567 poils), LastModified: 20/01/2020
2: Yoda (21/05/1980, 3 poils), LastModified: 21/05/1980
3: Ewok (25/05/1983, 3456789 poils), LastModified: 25/05/1983
4: Porg (19/07/2017, 123 poils), LastModified: 20/01/2020
Contenu de la base (carnets de santé) :
1 : carnet de Chewbacca, modifié la dernière fois le 20/01/2020
2 : carnet de Yoda, modifié la dernière fois le 21/05/1980
3 : carnet de Ewok, modifié la dernière fois le 25/05/1983
4 : carnet de Porg, modifié la dernière fois le 20/01/2020
```
*Note : l'identifiant du dernier ```Nounours``` sera vraisemblablement différent puisqu'il est créé par la base lors de l'insertion.*
## Comment exécuter cet exemple sans le stub ?
Il suffit de faire exactement comme dans le paragraphe précédent, mais en choisissant le contexte ```NounoursDBEntities``` à la place de ```StubbedContext``` :
```
dotnet ef migrations add ex_042_012 --context NounoursDBEntities
dotnet ef database update --context NounoursDBEntities
```
Lors de l'exécution, le résultat sera évidemment différent puisqu'il n'y aura pas les 3 nounours du stub.
## Comment vérifier quelles base et tables ont été créées et leur contenu ?
Pour vérifier le contenu de votre base SQLite, vous pouvez utiliser le programme *DB Browser* :
* Rendez-vous sur la page : https://sqlitebrowser.org/dl/ et téléchargez le programme *DB Browser*.
* Lancez *DB Browser for SQLite*
* Glissez-déposez au milieu de la fenêtre de *DB Browser for SQLite* le fichier *ex_042_012_OneToOne_conventions.Nounours.db* qui a été généré par l'exécution du programme et qui se trouve près de *ex_042_012_OneToOne_conventions.csproj*.
![DB Browser for SQLite](./readme_files/dbbrowser01.png)
* Choisissez ensuite l'onglet *Parcourir les données*
* Observez les résultats obtenus des deux tables
![DB Browser for SQLite](./readme_files/dbbrowser02.png)
![DB Browser for SQLite](./readme_files/dbbrowser03.png)

@ -5,6 +5,16 @@
<TargetFramework>netcoreapp3.0</TargetFramework>
<StartWorkingDirectory>$(MSBuildProjectDirectory)</StartWorkingDirectory>
</PropertyGroup>
<ItemGroup>
<Compile Remove="Migrations\**" />
<EmbeddedResource Remove="Migrations\**" />
<None Remove="Migrations\**" />
</ItemGroup>
<ItemGroup>
<None Remove="ex_042_012_OneToOne_conventions.Nounours.db" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="3.1.0" />
<PackageReference Include="Microsoft.EntityFrameworkCore.SQLite" Version="3.1.0" />

Binary file not shown.

After

Width:  |  Height:  |  Size: 124 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 96 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 102 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 7.4 KiB

@ -17,9 +17,9 @@
* V 008, 009 seeding data
* ###### relationships
* V 010, 011 shadow properties
* 010, 011, single navigation property
* V 010, 011, single navigation property
* cascade delete
* one to one
* 012 one to one
* one to many
* many to many
* dictionaries

Loading…
Cancel
Save