Partager via


Pages Razor avec Entity Framework Core dans ASP.NET Core - Tutoriel 1 sur 8

Remarque

Ceci n’est pas la dernière version de cet article. Pour la version actuelle, consultez la version .NET 8 de cet article.

Avertissement

Cette version d’ASP.NET Core n’est plus prise en charge. Pour plus d’informations, consultez la Stratégie de prise en charge de .NET et .NET Core. Pour la version actuelle, consultez la version .NET 8 de cet article.

Important

Ces informations portent sur la préversion du produit, qui est susceptible d’être en grande partie modifié avant sa commercialisation. Microsoft n’offre aucune garantie, expresse ou implicite, concernant les informations fournies ici.

Pour la version actuelle, consultez la version .NET 8 de cet article.

Par Tom Dykstra, Jeremy Likness et Jon P Smith

Il s’agit du premier tutoriel d’une série qui montre comment utiliser Entity Framework (EF) Core dans une application Pages Razor ASP.NET Core. Dans ces tutoriels, un site web est créé pour une université fictive nommée Contoso. Le site comprend des fonctionnalités comme l’admission des étudiants, la création de cours et les affectations des formateurs. Le tutoriel utilise l’approche code first. Pour plus d’informations sur la façon de suivre ce tutoriel à l’aide de l’approche database first, consultez ce problème Github.

Télécharger ou afficher l’application complète. Télécharger les instructions.

Prérequis

Moteurs de base de données

Les instructions Visual Studio utilisent la Base de données locale SQL Server, version de SQL Server Express qui s’exécute uniquement sur Windows.

Résolution des problèmes

Si vous rencontrez un problème que vous ne pouvez pas résoudre, comparez votre code au projet terminé. Un bon moyen d’obtenir de l’aide est de poster une question sur StackOverflow.com en utilisant le mot-clé ASP.NET Core ou le mot-clé EF Core.

Exemple d’application

L’application générée dans ces didacticiels est le site web de base d’une université. Les utilisateurs peuvent afficher et mettre à jour les informations relatives aux étudiants, aux cours et aux formateurs. Voici quelques-uns des écrans créés dans le didacticiel.

Page d’index des étudiants

Page de modification des étudiants

Le style de l’interface utilisateur de ce site repose sur les modèles de projet intégrés. Le tutoriel traite essentiellement de l’utilisation d’EF Core avec ASP.NET Core, et non de la façon de personnaliser l’interface utilisateur.

Facultatif : Générer l’exemple de téléchargement

Cette étape est facultative. Il est recommandé de générer l’application terminée lorsque vous rencontrez des problèmes que vous ne pouvez pas résoudre. Si vous rencontrez un problème que vous ne pouvez pas résoudre, comparez votre code au projet terminé. Télécharger les instructions.

Sélectionnez ContosoUniversity.csproj pour ouvrir le projet.

  • Créez le projet.

  • Dans la console du Gestionnaire de package (PMC), exécutez la commande suivante :

    Update-Database
    

Exécutez le projet pour amorcer la base de données.

Créer le projet d’application web

  1. Démarrez Visual Studio 2022 et sélectionnez Créer un projet.

    Créer un nouveau projet à partir de la fenêtre de démarrage

  2. Dans la boîte de dialogue Créer un nouveau projet, sélectionnez ASP.NET Core Web App, puis Suivant.

    Créer une application web ASP.NET Core

  3. Dans la boîte de dialogue Configurer votre nouveau projet, entrez ContosoUniversity pour Nom du projet. Il est important de nommer le projet ContosoUniversity, en respectant la casse, pour que les espaces de noms correspondent quand vous copiez et collez l’exemple de code.

  4. Cliquez sur Suivant.

  5. Dans la boîte de dialogue Informations supplémentaires, sélectionnez .NET 6.0 (prise en charge à long terme), puis sélectionnez Créer.

    Informations supplémentaires

Configurer le style du site

Copiez et collez le code suivant dans le fichier Pages/Shared/_Layout.cshtml :

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>@ViewData["Title"] - Contoso University</title>
    <link rel="stylesheet" href="~/lib/bootstrap/dist/css/bootstrap.css" />
    <link rel="stylesheet" href="~/css/site.css" asp-append-version="true" />
    <link rel="stylesheet" href="~/ContosoUniversity.styles.css" asp-append-version="true" />
</head>
<body>
    <header>
        <nav class="navbar navbar-expand-sm navbar-toggleable-sm navbar-light bg-white border-bottom box-shadow mb-3">
            <div class="container">
                <a class="navbar-brand" asp-area="" asp-page="/Index">Contoso University</a>
                <button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target=".navbar-collapse" aria-controls="navbarSupportedContent"
                        aria-expanded="false" aria-label="Toggle navigation">
                    <span class="navbar-toggler-icon"></span>
                </button>
                <div class="navbar-collapse collapse d-sm-inline-flex justify-content-between">
                    <ul class="navbar-nav flex-grow-1">                        
                        <li class="nav-item">
                            <a class="nav-link text-dark" asp-area="" asp-page="/About">About</a>
                        </li>
                        <li class="nav-item">
                            <a class="nav-link text-dark" asp-area="" asp-page="/Students/Index">Students</a>
                        </li>
                        <li class="nav-item">
                            <a class="nav-link text-dark" asp-area="" asp-page="/Courses/Index">Courses</a>
                        </li>
                        <li class="nav-item">
                            <a class="nav-link text-dark" asp-area="" asp-page="/Instructors/Index">Instructors</a>
                        </li>
                        <li class="nav-item">
                            <a class="nav-link text-dark" asp-area="" asp-page="/Departments/Index">Departments</a>
                        </li>
                    </ul>
                </div>
            </div>
        </nav>
    </header>
    <div class="container">
        <main role="main" class="pb-3">
            @RenderBody()
        </main>
    </div>

    <footer class="border-top footer text-muted">
        <div class="container">
            &copy; 2021 - Contoso University - <a asp-area="" asp-page="/Privacy">Privacy</a>
        </div>
    </footer>

    <script src="~/lib/jquery/dist/jquery.js"></script>
    <script src="~/lib/bootstrap/dist/js/bootstrap.bundle.js"></script>
    <script src="~/js/site.js" asp-append-version="true"></script>

    @await RenderSectionAsync("Scripts", required: false)
</body>
</html>

Le fichier de layout définit l’en-tête, le pied de page et le menu du site. Le code précédent apporte les modifications suivantes :

  • Remplacez chaque occurrence de « ContosoUniversity » par « Contoso University ». Il y a trois occurrences.
  • Les entrées de menu Home et Privacy sont supprimées.
  • Les entrées sont ajoutées à À propos de, Étudiants, Cours, Instructeurset Départements.

Dans Pages/Index.cshtml, remplacez le contenu du fichier par le code suivant :

@page
@model IndexModel
@{
    ViewData["Title"] = "Home page";
}

<div class="row mb-auto">
    <div class="col-md-4">
        <div class="row no-gutters border mb-4">
            <div class="col p-4 mb-4 ">
                <p class="card-text">
                    Contoso University is a sample application that
                    demonstrates how to use Entity Framework Core in an
                    ASP.NET Core Razor Pages web app.
                </p>
            </div>
        </div>
    </div>
    <div class="col-md-4">
        <div class="row no-gutters border mb-4">
            <div class="col p-4 d-flex flex-column position-static">
                <p class="card-text mb-auto">
                    You can build the application by following the steps in a series of tutorials.
                </p>
                <p>
@*                    <a href="https://docs.microsoft.com/aspnet/core/data/ef-rp/intro" class="stretched-link">See the tutorial</a>
*@                </p>
            </div>
        </div>
    </div>
    <div class="col-md-4">
        <div class="row no-gutters border mb-4">
            <div class="col p-4 d-flex flex-column">
                <p class="card-text mb-auto">
                    You can download the completed project from GitHub.
                </p>
                <p>
@*                    <a href="https://github.com/dotnet/AspNetCore.Docs/tree/main/aspnetcore/data/ef-rp/intro/samples" class="stretched-link">See project source code</a>
*@                </p>
            </div>
        </div>
    </div>
</div>

Le code précédent remplace le texte à propose de ASP.NET Core par le texte à propos de cette application.

Exécutez l’application pour vérifier que la page home s’affiche.

Le modèle de données

Les sections suivantes créent un modèle de données :

Diagramme du modèle de données Course-Enrollment-Student

Un étudiant peut s’inscrire à un nombre quelconque de cours et un cours peut avoir un nombre quelconque d’élèves inscrits.

L’entité Student

Diagramme de l’entité Student

  • Créez un dossier Models dans le dossier de projet.
  • Créez Models/Student.cs avec le code suivant :
    namespace ContosoUniversity.Models
    {
        public class Student
        {
            public int ID { get; set; }
            public string LastName { get; set; }
            public string FirstMidName { get; set; }
            public DateTime EnrollmentDate { get; set; }
    
            public ICollection<Enrollment> Enrollments { get; set; }
        }
    }
    

La propriété ID devient la colonne de clé primaire de la table de base de données qui correspond à cette classe. Par défaut, EF Core interprète une propriété nommée ID ou classnameID comme clé primaire. L’autre nom reconnu automatiquement de la clé primaire de classe Student est StudentID. Pour plus d’informations, consultez EF Core - Clés.

La propriété Enrollments est une propriété de navigation. Les propriétés de navigation contiennent d’autres entités qui sont associées à cette entité. Dans ce cas, la propriété Enrollments d’une entité Student contient toutes les entités Enrollment associées à cet étudiant. Par exemple, si une ligne Student dans la base de données est associée à deux lignes Enrollment, la propriété de navigation Enrollments contient ces deux entités Enrollment.

Dans la base de données, une ligne Inscription est associée à une ligne Étudiant si sa colonne StudentID contient la valeur d’ID de l’étudiant. Par exemple, supposez qu’une ligne Student présente un ID égal à 1. Les lignes Inscription associées auront un StudentID égal à 1. StudentID est une clé étrangère dans la table Inscription.

La propriété Enrollments est définie en tant que ICollection<Enrollment>, car plusieurs entités Enrollment associées peuvent exister. D’autres types de collection peuvent être utilisés, tels que List<Enrollment> ou HashSet<Enrollment>. Quand vous utilisez ICollection<Enrollment>, EF Core crée une collection HashSet<Enrollment> par défaut.

L’entité Enrollment

Diagramme de l’entité Enrollment

Créez Models/Enrollment.cs avec le code suivant :

using System.ComponentModel.DataAnnotations;

namespace ContosoUniversity.Models
{
    public enum Grade
    {
        A, B, C, D, F
    }

    public class Enrollment
    {
        public int EnrollmentID { get; set; }
        public int CourseID { get; set; }
        public int StudentID { get; set; }
        [DisplayFormat(NullDisplayText = "No grade")]
        public Grade? Grade { get; set; }

        public Course Course { get; set; }
        public Student Student { get; set; }
    }
}

La propriété EnrollmentID est la clé primaire ; cette entité utilise le modèle classnameID à la place de ID par lui-même. Pour un modèle de données de production, beaucoup de développeurs choisissent un modèle et l’utilisent systématiquement. Ce tutoriel utilise les deux pour montrer qu’ils fonctionnent tous les deux. L’utilisation de ID sans classname facilite l’implémentation de certaines modifications du modèle de données.

La propriété Grade est un enum. La présence du point d’interrogation après la déclaration de type Grade indique que la propriété Gradeaccepte les valeurs Null. Une note (Grade) qui a la valeur Null est différente d’une note égale à zéro : la valeur Null signifie qu’une note n’est pas connue ou n’a pas encore été affectée.

La propriété StudentID est une clé étrangère, et la propriété de navigation correspondante est Student. Une entité Enrollment est associée à une entité Student. Par conséquent, la propriété contient une seule entité Student.

La propriété CourseID est une clé étrangère, et la propriété de navigation correspondante est Course. Une entité Enrollment est associée à une entité Course.

EF Core interprète une propriété en tant que clé étrangère si elle se nomme <navigation property name><primary key property name>. Par exemple, StudentID est la clé étrangère pour la propriété de navigation Student, car la clé primaire de l’entité Student est ID. Les propriétés de clé étrangère peuvent également se nommer <primary key property name>. Par exemple, CourseID puisque la clé primaire de l’entité Course est CourseID.

L’entité Course

Diagramme de l’entité Course

Créez Models/Course.cs avec le code suivant :

using System.ComponentModel.DataAnnotations.Schema;

namespace ContosoUniversity.Models
{
    public class Course
    {
        [DatabaseGenerated(DatabaseGeneratedOption.None)]
        public int CourseID { get; set; }
        public string Title { get; set; }
        public int Credits { get; set; }

        public ICollection<Enrollment> Enrollments { get; set; }
    }
}

La propriété Enrollments est une propriété de navigation. Une entité Course peut être associée à un nombre quelconque d’entités Enrollment.

L’attribut DatabaseGenerated permet à l’application de spécifier la clé primaire, plutôt que de la faire générer par la base de données.

Générez l'application. Le compilateur génère plusieurs avertissements sur la façon dont les valeurs null sont gérées. Pour plus d’informations, consultez ce problème GitHub, Types de référence pouvant accepter la valeur Null et Tutoriel : Exprimer votre intention de conception plus clairement avec des types de référence pouvant accepter la valeur Null et non-nullables.

Pour éliminer les avertissements des types de référence pouvant accepter la valeur Null, supprimez la ligne suivante du fichier ContosoUniversity.csproj :

<Nullable>enable</Nullable>

Le moteur de génération de modèles automatique ne prend actuellement pas en charge les types de référence pouvant accepter la valeur Null. Par conséquent, les modèles utilisés dans la génération automatique de modèles ne le peuvent pas non plus.

Supprimez l’annotation ? de type de référence pouvant accepter la valeur Null de public string? RequestId { get; set; } dans Pages/Error.cshtml.cs afin que le projet génère sans avertissements du compilateur.

Générer automatiquement des modèles de pages Student

Dans cette section, l’outil de génération de modèles automatique ASP.NET Core est utilisé pour générer :

  • Une classe EF CoreDbContext. Le contexte est la classe principale qui coordonne les fonctionnalités d’Entity Framework pour un modèle de données déterminé. Il dérive de la classe Microsoft.EntityFrameworkCore.DbContext.
  • Les pages Razor qui gèrent les opérations Créer, Lecture, Mettre à jour et Supprimer (CRUD) pour l’entité Student.
  • Créez un dossier Pages/Students.
  • Dans l’Explorateur de solutions, cliquez avec le bouton droit sur le dossier Pages/Students, puis sélectionnez Ajouter>Nouvel élément généré automatiquement.
  • Dans la boîte de dialogue Ajouter un nouvel élément de génération automatique de modèles :
    • Dans l’onglet de gauche, sélectionnez Pages >Razor courantes > installées
    • Sélectionnez Pages Razor utilisant Entity Framework (CRUD)>ADD.
  • Dans la boîte de dialogue Ajouter des Pages Razor avec Entity Framework (CRUD) :
    • Dans la liste déroulante Classe de modèle, sélectionnez Student (ContosoUniversity.Models).
    • Dans la ligne Classe du contexte de données, sélectionnez le signe + (plus).
      • Modifiez le nom du contexte de données pour qu’il se termine par SchoolContext plutôt que par ContosoUniversityContext. Le nom du contexte mis à jour : ContosoUniversity.Data.SchoolContext
      • Sélectionnez Ajouter pour terminer l’ajout de la classe de contexte de données.
      • Sélectionnez Ajouter pour terminer la boîte de dialogue Ajouter des Pages Razor.

Les packages suivants sont automatiquement installés :

  • Microsoft.EntityFrameworkCore.SqlServer
  • Microsoft.EntityFrameworkCore.Tools
  • Microsoft.VisualStudio.Web.CodeGeneration.Design

Si l’étape précédente échoue, générez le projet et recommencez l’étape de génération de modèles automatique.

Le processus de génération de modèles automatique :

  • Crée les pages Razor dans le dossier Pages/Étudiants :
    • Create.cshtml et Create.cshtml.cs
    • Delete.cshtml et Delete.cshtml.cs
    • Details.cshtml et Details.cshtml.cs
    • Edit.cshtml et Edit.cshtml.cs
    • Index.cshtml et Index.cshtml.cs
  • Crée Data/SchoolContext.cs.
  • Ajoute le contexte à l’injection de dépendances dans Program.cs.
  • Ajoute une chaîne de connexion de base de données à appsettings.json.

Chaîne de connexion de base de données

L’outil de génération de modèles automatique génère une chaîne de connexion dans le fichier appsettings.json.

La chaîne de connexion spécifie SQL Server LocalDB :

{
  "Logging": {
    "LogLevel": {
      "Default": "Information",
      "Microsoft.AspNetCore": "Warning"
    }
  },
  "AllowedHosts": "*",
  "ConnectionStrings": {
    "SchoolContext": "Server=(localdb)\\mssqllocaldb;Database=SchoolContext-0e9;Trusted_Connection=True;MultipleActiveResultSets=true"
  }
}

LocalDB est une version allégée du moteur de base de données SQL Server Express. Elle est destinée au développement d’applications, et non à une utilisation en production. Par défaut, la Base de données locale crée des fichiers .mdf dans le répertoire C:/Users/<user>.

Mettre à jour la classe du contexte de base de données

La classe principale qui coordonne les fonctionnalités EF Core pour un modèle de données déterminé est la classe du contexte de base de données. Le contexte de données est dérivé de Microsoft.EntityFrameworkCore.DbContext. Il spécifie les entités qui sont incluses dans le modèle de données. Dans ce projet, la classe est nommée SchoolContext.

Mettez à jour Data/SchoolContext.cs à l’aide du code suivant :

using Microsoft.EntityFrameworkCore;
using ContosoUniversity.Models;

namespace ContosoUniversity.Data
{
    public class SchoolContext : DbContext
    {
        public SchoolContext (DbContextOptions<SchoolContext> options)
            : base(options)
        {
        }

        public DbSet<Student> Students { get; set; }
        public DbSet<Enrollment> Enrollments { get; set; }
        public DbSet<Course> Courses { get; set; }

        protected override void OnModelCreating(ModelBuilder modelBuilder)
        {
            modelBuilder.Entity<Course>().ToTable("Course");
            modelBuilder.Entity<Enrollment>().ToTable("Enrollment");
            modelBuilder.Entity<Student>().ToTable("Student");
        }
    }
}

Le code précédent passe du singulier DbSet<Student> Student au pluriel DbSet<Student> Students. Pour que le code Pages Razor corresponde au nouveau nom de DBSet, apportez une modification globale à partir de : _context.Student. vers : _context.Students.

Il y a 8 occurrences.

Étant donné qu’un jeu d’entités contient plusieurs entités, de nombreux développeurs préfèrent que les noms de propriétés DBSet soient au pluriel.

Le code mis en mis en évidence :

  • Crée une propriété DbSet<TEntity> pour chaque jeu d’entités. Dans la terminologie EF Core :
    • Un jeu d’entités correspond généralement à une table de base de données.
    • Une entité correspond à une ligne dans la table.
  • Appelle OnModelCreating. OnModelCreating :
    • Est appelée lorsque SchoolContext a été initialisé, mais avant que le modèle ne soit sécurisé et utilisé pour initialiser le contexte.
    • Est obligatoire, car plus loin dans le tutoriel, l’entité Student aura des références aux autres entités.

Nous espérons résoudre ce problème dans une version ultérieure.

Program.cs

ASP.NET Core comprend l’injection de dépendances. Des services, tels que SchoolContext, sont inscrits avec l’injection de dépendances au démarrage de l’application. Ces services sont affectés aux composants qui les nécessitent, par exemple les Pages Razor, par le biais de paramètres de constructeur. Le code de constructeur qui obtient une instance de contexte de base de données est indiqué plus loin dans le tutoriel.

L’outil de génération de modèles automatique a inscrit automatiquement la classe du contexte dans le conteneur d’injection de dépendances.

Les lignes en surbrillance suivantes ont été ajoutées par l’outil de génération de modèles automatique :

using ContosoUniversity.Data;
using Microsoft.EntityFrameworkCore;
var builder = WebApplication.CreateBuilder(args);

builder.Services.AddRazorPages();

builder.Services.AddDbContext<SchoolContext>(options =>
  options.UseSqlServer(builder.Configuration.GetConnectionString("SchoolContext")));

Le nom de la chaîne de connexion est transmis au contexte en appelant une méthode sur un objet DbContextOptions. Pour le développement local, le système de configuration ASP.NET Core lit la chaîne de connexion à partir du fichier appsettings.json ou appsettings.Development.json.

Ajouter le filtre d’exception de la base de données

Ajoutez AddDatabaseDeveloperPageExceptionFilter et UseMigrationsEndPoint comme indiqué dans le code suivant :

using ContosoUniversity.Data;
using Microsoft.EntityFrameworkCore;
var builder = WebApplication.CreateBuilder(args);

builder.Services.AddRazorPages();

builder.Services.AddDbContext<SchoolContext>(options =>
  options.UseSqlServer(builder.Configuration.GetConnectionString("SchoolContext")));

builder.Services.AddDatabaseDeveloperPageExceptionFilter();

var app = builder.Build();

if (!app.Environment.IsDevelopment())
{
    app.UseExceptionHandler("/Error");
    app.UseHsts();
}
else
{
    app.UseDeveloperExceptionPage();
    app.UseMigrationsEndPoint();
}

Ajoutez le package NuGet Microsoft.AspNetCore.Diagnostics.EntityFrameworkCore.

Dans la Console du gestionnaire de package, entrez la commande suivante pour ajouter le package NuGet :

Install-Package Microsoft.AspNetCore.Diagnostics.EntityFrameworkCore

Le package NuGet Microsoft.AspNetCore.Diagnostics.EntityFrameworkCore fournit un intergiciel ASP.NET Core pour les pages d’erreur Entity Framework Core. Cet intergiciel permet de détecter et de diagnostiquer les erreurs de migration Entity Framework Core.

Le AddDatabaseDeveloperPageExceptionFilter fournit des informations utiles sur les erreurs dans l’environnement de développement pour les erreurs de migration EF.

Création de la base de données

Mettez à jour Program.cs pour créer la base de données si elle n’existe pas :

using ContosoUniversity.Data;
using Microsoft.EntityFrameworkCore;
var builder = WebApplication.CreateBuilder(args);

builder.Services.AddRazorPages();

builder.Services.AddDbContext<SchoolContext>(options =>
  options.UseSqlServer(builder.Configuration.GetConnectionString("SchoolContext")));

builder.Services.AddDatabaseDeveloperPageExceptionFilter();

var app = builder.Build();

if (!app.Environment.IsDevelopment())
{
    app.UseExceptionHandler("/Error");
    app.UseHsts();
}
else
{
    app.UseDeveloperExceptionPage();
    app.UseMigrationsEndPoint();
}

using (var scope = app.Services.CreateScope())
{
    var services = scope.ServiceProvider;

    var context = services.GetRequiredService<SchoolContext>();
    context.Database.EnsureCreated();
    // DbInitializer.Initialize(context);
}

app.UseHttpsRedirection();
app.UseStaticFiles();

app.UseRouting();

app.UseAuthorization();

app.MapRazorPages();

app.Run();

La méthode EnsureCreated n’effectue aucune action s’il existe une base de données pour le contexte. S’il n’existe pas de base de données, elle crée la base de données et le schéma. EnsureCreated active le workflow suivant pour gérer les modifications du modèle de données :

  • Supprimez la base de données. Toutes les données existantes sont perdues.
  • Modifiez le modèle de données. Par exemple, ajoutez un champ EmailAddress.
  • Exécutez l’application.
  • EnsureCreated crée une base de données avec le nouveau schéma.

Ce workflow fonctionne à un stade précoce du développement, quand le schéma évolue rapidement, aussi longtemps que vous n’avez pas besoin de conserver les données. La situation est différente quand les données qui ont été entrées dans la base de données doivent être conservées. Dans ce cas, procédez à des migrations.

Plus tard dans cette série de tutoriels, vous supprimerez la base de données créée par EnsureCreated et procéderez à des migrations. Une base de données créée par EnsureCreated ne peut pas être mise à jour via des migrations.

Test de l'application

  • Exécutez l’application.
  • Sélectionnez le lien Students, puis Créer nouveau.
  • Testez les liens Modifier, Détails et Supprimer.

Amorcer la base de données

La méthode EnsureCreated crée une base de données vide. Cette section ajoute du code qui remplit la base de données avec des données de test.

Créez Data/DbInitializer.cs avec le code suivant :

using ContosoUniversity.Models;

namespace ContosoUniversity.Data
{
    public static class DbInitializer
    {
        public static void Initialize(SchoolContext context)
        {
            // Look for any students.
            if (context.Students.Any())
            {
                return;   // DB has been seeded
            }

            var students = new Student[]
            {
                new Student{FirstMidName="Carson",LastName="Alexander",EnrollmentDate=DateTime.Parse("2019-09-01")},
                new Student{FirstMidName="Meredith",LastName="Alonso",EnrollmentDate=DateTime.Parse("2017-09-01")},
                new Student{FirstMidName="Arturo",LastName="Anand",EnrollmentDate=DateTime.Parse("2018-09-01")},
                new Student{FirstMidName="Gytis",LastName="Barzdukas",EnrollmentDate=DateTime.Parse("2017-09-01")},
                new Student{FirstMidName="Yan",LastName="Li",EnrollmentDate=DateTime.Parse("2017-09-01")},
                new Student{FirstMidName="Peggy",LastName="Justice",EnrollmentDate=DateTime.Parse("2016-09-01")},
                new Student{FirstMidName="Laura",LastName="Norman",EnrollmentDate=DateTime.Parse("2018-09-01")},
                new Student{FirstMidName="Nino",LastName="Olivetto",EnrollmentDate=DateTime.Parse("2019-09-01")}
            };

            context.Students.AddRange(students);
            context.SaveChanges();

            var courses = new Course[]
            {
                new Course{CourseID=1050,Title="Chemistry",Credits=3},
                new Course{CourseID=4022,Title="Microeconomics",Credits=3},
                new Course{CourseID=4041,Title="Macroeconomics",Credits=3},
                new Course{CourseID=1045,Title="Calculus",Credits=4},
                new Course{CourseID=3141,Title="Trigonometry",Credits=4},
                new Course{CourseID=2021,Title="Composition",Credits=3},
                new Course{CourseID=2042,Title="Literature",Credits=4}
            };

            context.Courses.AddRange(courses);
            context.SaveChanges();

            var enrollments = new Enrollment[]
            {
                new Enrollment{StudentID=1,CourseID=1050,Grade=Grade.A},
                new Enrollment{StudentID=1,CourseID=4022,Grade=Grade.C},
                new Enrollment{StudentID=1,CourseID=4041,Grade=Grade.B},
                new Enrollment{StudentID=2,CourseID=1045,Grade=Grade.B},
                new Enrollment{StudentID=2,CourseID=3141,Grade=Grade.F},
                new Enrollment{StudentID=2,CourseID=2021,Grade=Grade.F},
                new Enrollment{StudentID=3,CourseID=1050},
                new Enrollment{StudentID=4,CourseID=1050},
                new Enrollment{StudentID=4,CourseID=4022,Grade=Grade.F},
                new Enrollment{StudentID=5,CourseID=4041,Grade=Grade.C},
                new Enrollment{StudentID=6,CourseID=1045},
                new Enrollment{StudentID=7,CourseID=3141,Grade=Grade.A},
            };

            context.Enrollments.AddRange(enrollments);
            context.SaveChanges();
        }
    }
}

Le code vérifie si des étudiants figurent dans la base de données. S’il n’y a pas d’étudiants, il ajoute des données de test à la base de données. Il crée les données de test dans des tableaux et non dans des collections List<T> afin d’optimiser les performances.

  • Dans Program.cs, supprimez // de la ligne DbInitializer.Initialize :
using (var scope = app.Services.CreateScope())
{
    var services = scope.ServiceProvider;

    var context = services.GetRequiredService<SchoolContext>();
    context.Database.EnsureCreated();
    DbInitializer.Initialize(context);
}
  • Arrêtez l’application si elle est en cours d’exécution et exécutez la commande suivante dans la Console du gestionnaire de package :

    Drop-Database -Confirm
    
    
  • Répondez avec Y pour supprimer la base de données.

  • Redémarrez l’application.
  • Sélectionnez la page Students pour examiner les données amorcées.

Afficher la base de données

  • Ouvrez l’Explorateur d’objets SQL Server (SSOX) à partir du menu Affichage de Visual Studio.
  • Dans SSOX, sélectionnez (localdb)\MSSQLLocalDB > Databases > SchoolContext-{GUID}. Le nom de la base de données est généré à partir du nom de contexte indiqué précédemment, ainsi que d’un tiret et d’un GUID.
  • Développez le nœud Tables.
  • Cliquez avec le bouton droit sur la table Student et cliquez sur Afficher les données pour voir les colonnes créées et les lignes insérées dans la table.
  • Cliquez avec le bouton droit sur la table Student et cliquez sur Afficher le code pour voir comment le modèle Student est mappé au schéma de la table Student.

Méthodes EF asynchrones dans les applications web ASP.NET Core

La programmation asynchrone est le mode par défaut pour ASP.NET Core et EF Core.

Un serveur web a un nombre limité de threads disponibles et, dans les situations de forte charge, tous les threads disponibles peuvent être utilisés. Quand cela se produit, le serveur ne peut pas traiter de nouvelle requête tant que les threads ne sont pas libérés. Avec le code synchrone, plusieurs threads peuvent être bloqués alors qu’ils n’effectuent aucun travail, car ils attendent que des E/S se terminent. Avec le code asynchrone, quand un processus attend que des E/S se terminent, son thread est libéré afin d’être utilisé par le serveur pour traiter d’autres demandes. Il permet ainsi d’utiliser les ressources serveur plus efficacement, et le serveur peut gérer plus de trafic sans retard.

Le code asynchrone introduit néanmoins une petite surcharge au moment de l’exécution. Dans les situations de faible trafic, le gain de performances est négligeable, tandis qu’en cas de trafic élevé l’amélioration potentielle des performances est importante.

Dans le code suivant, le mot clé async, la valeur renvoyée Task, le mot clé await et la méthode ToListAsync déclenchent l’exécution asynchrone du code.

public async Task OnGetAsync()
{
    Students = await _context.Students.ToListAsync();
}
  • Le mot clé async fait en sorte que le compilateur :
    • Génère des rappels pour les parties du corps de méthode.
    • Crée l’objet Task qui est retourné.
  • Le type de retour Task représente le travail en cours.
  • Le mot clé await indique au compilateur de fractionner la méthode en deux parties. La première partie se termine par l’opération qui est démarrée de façon asynchrone. La seconde partie est placée dans une méthode de rappel qui est appelée quand l’opération se termine.
  • ToListAsync est la version asynchrone de la méthode d’extension ToList.

Voici quelques éléments à connaître lors de l’écriture de code asynchrone qui utilise EF Core :

  • Seules les instructions qui provoquent l’envoi de requêtes ou de commandes vers la base de données sont exécutées de façon asynchrone. Cela inclut ToListAsync, SingleOrDefaultAsync, FirstOrDefaultAsync et SaveChangesAsync, mais pas les instructions qui ne font que changer un IQueryable, telles que var students = context.Students.Where(s => s.LastName == "Davolio").
  • Un contexte EF Core n’est pas thread-safe : n’essayez pas d’effectuer plusieurs opérations en parallèle.
  • Pour tirer parti des avantages de performances du code asynchrone, vérifiez que les packages de bibliothèque (par exemple pour la pagination) utilisent le mode asynchrone s’ils appellent des méthodes EF Core qui envoient des requêtes à la base de données.

Pour plus d’informations sur la programmation asynchrone dans .NET, consultez Vue d’ensemble d’Async et Programmation asynchrone avec async et await.

Avertissement

L’implémentation asynchrone de Microsoft.Data.SqlClient présente certains problèmes connus (#593, #601, etc.). Si vous rencontrez des problèmes de performances inattendus, essayez plutôt d’utiliser l’exécution des commandes de synchronisation, en particulier lorsque vous traitez de grandes valeurs de texte ou binaires.

Considérations relatives aux performances

En général, une page web ne doit pas charger un nombre arbitraire de lignes. Une requête doit utiliser la pagination ou une approche limitative. Par exemple, la requête précédente peut utiliser Take pour limiter les lignes retournées :

public async Task OnGetAsync()
{
    Student = await _context.Students.Take(10).ToListAsync();
}

L’énumération d’une table volumineuse dans une vue peut renvoyer une réponse HTTP 200 partiellement construite si une exception de base de données se produit pendant l’énumération.

La pagination est abordée plus loin dans le tutoriel.

Pour plus d’informations, consultez Considérations sur les performances (EF).

Étapes suivantes

Utiliser SQLite pour le développement, SQL Server pour la production

Il s’agit du premier tutoriel d’une série qui montre comment utiliser Entity Framework (EF) Core dans une application Pages Razor ASP.NET Core. Dans ces tutoriels, un site web est créé pour une université fictive nommée Contoso. Le site comprend des fonctionnalités comme l’admission des étudiants, la création de cours et les affectations des formateurs. Le tutoriel utilise l’approche code first. Pour plus d’informations sur la façon de suivre ce tutoriel à l’aide de l’approche database first, consultez ce problème Github.

Télécharger ou afficher l’application complète. Télécharger les instructions.

Prérequis

Moteurs de base de données

Les instructions Visual Studio utilisent la Base de données locale SQL Server, version de SQL Server Express qui s’exécute uniquement sur Windows.

Résolution des problèmes

Si vous rencontrez un problème que vous ne pouvez pas résoudre, comparez votre code au projet terminé. Un bon moyen d’obtenir de l’aide est de poster une question sur StackOverflow.com en utilisant le mot-clé ASP.NET Core ou le mot-clé EF Core.

Exemple d’application

L’application générée dans ces didacticiels est le site web de base d’une université. Les utilisateurs peuvent afficher et mettre à jour les informations relatives aux étudiants, aux cours et aux formateurs. Voici quelques-uns des écrans créés dans le didacticiel.

Page d’index des étudiants

Page de modification des étudiants

Le style de l’interface utilisateur de ce site repose sur les modèles de projet intégrés. Le tutoriel traite essentiellement de l’utilisation d’EF Core avec ASP.NET Core, et non de la façon de personnaliser l’interface utilisateur.

Facultatif : Générer l’exemple de téléchargement

Cette étape est facultative. Il est recommandé de générer l’application terminée lorsque vous rencontrez des problèmes que vous ne pouvez pas résoudre. Si vous rencontrez un problème que vous ne pouvez pas résoudre, comparez votre code au projet terminé. Télécharger les instructions.

Sélectionnez ContosoUniversity.csproj pour ouvrir le projet.

  • Créez le projet.
  • Dans la console du Gestionnaire de package (PMC), exécutez la commande suivante :
Update-Database

Exécutez le projet pour amorcer la base de données.

Créer le projet d’application web

  1. Démarrez Visual Studio et sélectionnez Créer un projet.
  2. Dans la boîte de dialogue Créer un nouveau projet, sélectionnez Application web ASP.NET Core>Next.
  3. Dans la boîte de dialogue Configurer votre nouveau projet, entrez ContosoUniversity pour Nom du projet. Il est important d’utiliser ce nom exact, en respectant l’utilisation des majuscules, de sorte que chaque namespace corresponde au moment où le code est copié.
  4. Cliquez sur Créer.
  5. Dans la boîte de dialogue Créer une nouvelle application web ASP.NET Core, sélectionnez :
    1. .NET Core et ASP.NET Core 5.0 dans les listes déroulantes.
    2. Application web ASP.NET Core.
    3. CréerBoîte de dialogue Nouveau projet ASP.NET Core

Configurer le style du site

Copiez et collez le code suivant dans le fichier Pages/Shared/_Layout.cshtml :

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>@ViewData["Title"] - Contoso University</title>
    <link rel="stylesheet" href="~/lib/bootstrap/dist/css/bootstrap.css" />
    <link rel="stylesheet" href="~/css/site.css" />
</head>
<body>
    <header>
        <nav class="navbar navbar-expand-sm navbar-toggleable-sm navbar-light bg-white border-bottom box-shadow mb-3">
            <div class="container">
                <a class="navbar-brand" asp-area="" asp-page="/Index">Contoso University</a>
                <button class="navbar-toggler" type="button" data-toggle="collapse" data-target=".navbar-collapse" aria-controls="navbarSupportedContent"
                        aria-expanded="false" aria-label="Toggle navigation">
                    <span class="navbar-toggler-icon"></span>
                </button>
                <div class="navbar-collapse collapse d-sm-inline-flex flex-sm-row-reverse">
                    <ul class="navbar-nav flex-grow-1">
                        <li class="nav-item">
                            <a class="nav-link text-dark" asp-area="" asp-page="/About">About</a>
                        </li>
                        <li class="nav-item">
                            <a class="nav-link text-dark" asp-area="" asp-page="/Students/Index">Students</a>
                        </li>
                        <li class="nav-item">
                            <a class="nav-link text-dark" asp-area="" asp-page="/Courses/Index">Courses</a>
                        </li>
                        <li class="nav-item">
                            <a class="nav-link text-dark" asp-area="" asp-page="/Instructors/Index">Instructors</a>
                        </li>
                        <li class="nav-item">
                            <a class="nav-link text-dark" asp-area="" asp-page="/Departments/Index">Departments</a>
                        </li>
                    </ul>
                </div>
            </div>
        </nav>
    </header>
    <div class="container">
        <main role="main" class="pb-3">
            @RenderBody()
        </main>
    </div>

    <footer class="border-top footer text-muted">
        <div class="container">
            &copy; 2021 - Contoso University - <a asp-area="" asp-page="/Privacy">Privacy</a>
        </div>
    </footer>

    <script src="~/lib/jquery/dist/jquery.js"></script>
    <script src="~/lib/bootstrap/dist/js/bootstrap.bundle.js"></script>
    <script src="~/js/site.js" asp-append-version="true"></script>

    @RenderSection("Scripts", required: false)
</body>
</html>

Le fichier de layout définit l’en-tête, le pied de page et le menu du site. Le code précédent apporte les modifications suivantes :

  • Remplacez chaque occurrence de « ContosoUniversity » par « Contoso University ». Il y a trois occurrences.
  • Les entrées de menu Home et Privacy sont supprimées.
  • Les entrées sont ajoutées à À propos de, Étudiants, Cours, Instructeurset Départements.

Dans Pages/Index.cshtml, remplacez le contenu du fichier par le code suivant :

@page
@model IndexModel
@{
    ViewData["Title"] = "Home page";
}

<div class="row mb-auto">
    <div class="col-md-4">
        <div class="row no-gutters border mb-4">
            <div class="col p-4 mb-4 ">
                <p class="card-text">
                    Contoso University is a sample application that
                    demonstrates how to use Entity Framework Core in an
                    ASP.NET Core Razor Pages web app.
                </p>
            </div>
        </div>
    </div>
    <div class="col-md-4">
        <div class="row no-gutters border mb-4">
            <div class="col p-4 d-flex flex-column position-static">
                <p class="card-text mb-auto">
                    You can build the application by following the steps in a series of tutorials.
                </p>
                <p>
                    <a href="https://docs.microsoft.com/aspnet/core/data/ef-rp/intro" class="stretched-link">See the tutorial</a>
                </p>
            </div>
        </div>
    </div>
    <div class="col-md-4">
        <div class="row no-gutters border mb-4">
            <div class="col p-4 d-flex flex-column">
                <p class="card-text mb-auto">
                    You can download the completed project from GitHub.
                </p>
                <p>
                    <a href="https://github.com/dotnet/AspNetCore.Docs/tree/main/aspnetcore/data/ef-rp/intro/samples" class="stretched-link">See project source code</a>
                </p>
            </div>
        </div>
    </div>
</div>

Le code précédent remplace le texte à propose de ASP.NET Core par le texte à propos de cette application.

Exécutez l’application pour vérifier que la page home s’affiche.

Le modèle de données

Les sections suivantes créent un modèle de données :

Diagramme du modèle de données Course-Enrollment-Student

Un étudiant peut s’inscrire à un nombre quelconque de cours et un cours peut avoir un nombre quelconque d’élèves inscrits.

L’entité Student

Diagramme de l’entité Student

  • Créez un dossier Models dans le dossier de projet.

  • Créez Models/Student.cs avec le code suivant :

    using System;
    using System.Collections.Generic;
    
    namespace ContosoUniversity.Models
    {
        public class Student
        {
            public int ID { get; set; }
            public string LastName { get; set; }
            public string FirstMidName { get; set; }
            public DateTime EnrollmentDate { get; set; }
    
            public ICollection<Enrollment> Enrollments { get; set; }
        }
    }
    

La propriété ID devient la colonne de clé primaire de la table de base de données qui correspond à cette classe. Par défaut, EF Core interprète une propriété nommée ID ou classnameID comme clé primaire. L’autre nom reconnu automatiquement de la clé primaire de classe Student est StudentID. Pour plus d’informations, consultez EF Core - Clés.

La propriété Enrollments est une propriété de navigation. Les propriétés de navigation contiennent d’autres entités qui sont associées à cette entité. Dans ce cas, la propriété Enrollments d’une entité Student contient toutes les entités Enrollment associées à cet étudiant. Par exemple, si une ligne Student dans la base de données est associée à deux lignes Enrollment, la propriété de navigation Enrollments contient ces deux entités Enrollment.

Dans la base de données, une ligne Inscription est associée à une ligne Étudiant si sa colonne StudentID contient la valeur d’ID de l’étudiant. Par exemple, supposez qu’une ligne Student présente un ID égal à 1. Les lignes Inscription associées auront un StudentID égal à 1. StudentID est une clé étrangère dans la table Inscription.

La propriété Enrollments est définie en tant que ICollection<Enrollment>, car plusieurs entités Enrollment associées peuvent exister. D’autres types de collection peuvent être utilisés, tels que List<Enrollment> ou HashSet<Enrollment>. Quand vous utilisez ICollection<Enrollment>, EF Core crée une collection HashSet<Enrollment> par défaut.

L’entité Enrollment

Diagramme de l’entité Enrollment

Créez Models/Enrollment.cs avec le code suivant :

using System.ComponentModel.DataAnnotations;

namespace ContosoUniversity.Models
{
    public enum Grade
    {
        A, B, C, D, F
    }

    public class Enrollment
    {
        public int EnrollmentID { get; set; }
        public int CourseID { get; set; }
        public int StudentID { get; set; }
        [DisplayFormat(NullDisplayText = "No grade")]
        public Grade? Grade { get; set; }

        public Course Course { get; set; }
        public Student Student { get; set; }
    }
}

La propriété EnrollmentID est la clé primaire ; cette entité utilise le modèle classnameID à la place de ID par lui-même. Pour un modèle de données de production, beaucoup de développeurs choisissent un modèle et l’utilisent systématiquement. Ce tutoriel utilise les deux pour montrer qu’ils fonctionnent tous les deux. L’utilisation de ID sans classname facilite l’implémentation de certaines modifications du modèle de données.

La propriété Grade est un enum. La présence du point d’interrogation après la déclaration de type Grade indique que la propriété Gradeaccepte les valeurs Null. Une note (Grade) qui a la valeur Null est différente d’une note égale à zéro : la valeur Null signifie qu’une note n’est pas connue ou n’a pas encore été affectée.

La propriété StudentID est une clé étrangère, et la propriété de navigation correspondante est Student. Une entité Enrollment est associée à une entité Student. Par conséquent, la propriété contient une seule entité Student.

La propriété CourseID est une clé étrangère, et la propriété de navigation correspondante est Course. Une entité Enrollment est associée à une entité Course.

EF Core interprète une propriété en tant que clé étrangère si elle se nomme <navigation property name><primary key property name>. Par exemple, StudentID est la clé étrangère pour la propriété de navigation Student, car la clé primaire de l’entité Student est ID. Les propriétés de clé étrangère peuvent également se nommer <primary key property name>. Par exemple, CourseID puisque la clé primaire de l’entité Course est CourseID.

L’entité Course

Diagramme de l’entité Course

Créez Models/Course.cs avec le code suivant :

using System.Collections.Generic;
using System.ComponentModel.DataAnnotations.Schema;

namespace ContosoUniversity.Models
{
    public class Course
    {
        [DatabaseGenerated(DatabaseGeneratedOption.None)]
        public int CourseID { get; set; }
        public string Title { get; set; }
        public int Credits { get; set; }

        public ICollection<Enrollment> Enrollments { get; set; }
    }
}

La propriété Enrollments est une propriété de navigation. Une entité Course peut être associée à un nombre quelconque d’entités Enrollment.

L’attribut DatabaseGenerated permet à l’application de spécifier la clé primaire, plutôt que de la faire générer par la base de données.

Générez le projet pour vérifier qu’il n’y a pas d’erreurs du compilateur.

Générer automatiquement des modèles de pages Student

Dans cette section, l’outil de génération de modèles automatique ASP.NET Core est utilisé pour générer :

  • Une classe EF CoreDbContext. Le contexte est la classe principale qui coordonne les fonctionnalités d’Entity Framework pour un modèle de données déterminé. Il dérive de la classe Microsoft.EntityFrameworkCore.DbContext.
  • Les pages Razor qui gèrent les opérations Créer, Lecture, Mettre à jour et Supprimer (CRUD) pour l’entité Student.
  • Créez un dossier Pages/Students.
  • Dans l’Explorateur de solutions, cliquez avec le bouton droit sur le dossier Pages/Students, puis sélectionnez Ajouter>Nouvel élément généré automatiquement.
  • Dans la boîte de dialogue Ajouter un nouvel élément de génération automatique de modèles :
    • Dans l’onglet de gauche, sélectionnez Pages >Razor courantes > installées
    • Sélectionnez Pages Razor utilisant Entity Framework (CRUD)>ADD.
  • Dans la boîte de dialogue Ajouter des Pages Razor avec Entity Framework (CRUD) :
    • Dans la liste déroulante Classe de modèle, sélectionnez Student (ContosoUniversity.Models).
    • Dans la ligne Classe du contexte de données, sélectionnez le signe + (plus).
      • Modifiez le nom du contexte de données pour qu’il se termine par SchoolContext plutôt que par ContosoUniversityContext. Le nom du contexte mis à jour : ContosoUniversity.Data.SchoolContext
      • Sélectionnez Ajouter pour terminer l’ajout de la classe de contexte de données.
      • Sélectionnez Ajouter pour terminer la boîte de dialogue Ajouter des Pages Razor.

Si la génération de modèles automatique échoue avec l’erreur 'Install the package Microsoft.VisualStudio.Web.CodeGeneration.Design and try again.', réexécutez l’outil de génération de modèles automatique ou consultez ce problème GitHub.

Les packages suivants sont automatiquement installés :

  • Microsoft.EntityFrameworkCore.SqlServer
  • Microsoft.EntityFrameworkCore.Tools
  • Microsoft.VisualStudio.Web.CodeGeneration.Design

Si l’étape précédente échoue, générez le projet et recommencez l’étape de génération de modèles automatique.

Le processus de génération de modèles automatique :

  • Crée les pages Razor dans le dossier Pages/Étudiants :
    • Create.cshtml et Create.cshtml.cs
    • Delete.cshtml et Delete.cshtml.cs
    • Details.cshtml et Details.cshtml.cs
    • Edit.cshtml et Edit.cshtml.cs
    • Index.cshtml et Index.cshtml.cs
  • Crée Data/SchoolContext.cs.
  • Ajoute le contexte à l’injection de dépendances dans Startup.cs.
  • Ajoute une chaîne de connexion de base de données à appsettings.json.

Chaîne de connexion de base de données

L’outil de génération de modèles automatique génère une chaîne de connexion dans le fichier appsettings.json.

La chaîne de connexion spécifie SQL Server LocalDB :

{
  "Logging": {
    "LogLevel": {
      "Default": "Information",
      "Microsoft": "Warning",
      "Microsoft.Hosting.Lifetime": "Information"
    }
  },
  "AllowedHosts": "*",
  "ConnectionStrings": {
    "SchoolContext": "Server=(localdb)\\mssqllocaldb;Database=CU-1;Trusted_Connection=True;MultipleActiveResultSets=true"
  }
}

LocalDB est une version allégée du moteur de base de données SQL Server Express. Elle est destinée au développement d’applications, et non à une utilisation en production. Par défaut, la Base de données locale crée des fichiers .mdf dans le répertoire C:/Users/<user>.

Mettre à jour la classe du contexte de base de données

La classe principale qui coordonne les fonctionnalités EF Core pour un modèle de données déterminé est la classe du contexte de base de données. Le contexte de données est dérivé de Microsoft.EntityFrameworkCore.DbContext. Il spécifie les entités qui sont incluses dans le modèle de données. Dans ce projet, la classe est nommée SchoolContext.

Mettez à jour Data/SchoolContext.cs à l’aide du code suivant :

using Microsoft.EntityFrameworkCore;
using ContosoUniversity.Models;

namespace ContosoUniversity.Data
{
    public class SchoolContext : DbContext
    {
        public SchoolContext (DbContextOptions<SchoolContext> options)
            : base(options)
        {
        }

        public DbSet<Student> Students { get; set; }
        public DbSet<Enrollment> Enrollments { get; set; }
        public DbSet<Course> Courses { get; set; }

        protected override void OnModelCreating(ModelBuilder modelBuilder)
        {
            modelBuilder.Entity<Course>().ToTable("Course");
            modelBuilder.Entity<Enrollment>().ToTable("Enrollment");
            modelBuilder.Entity<Student>().ToTable("Student");
        }
    }
}

Le code précédent passe du singulier DbSet<Student> Student au pluriel DbSet<Student> Students. Pour que le code Pages Razor corresponde au nouveau nom de DBSet, apportez une modification globale à partir de : _context.Student. vers : _context.Students.

Il y a 8 occurrences.

Étant donné qu’un jeu d’entités contient plusieurs entités, de nombreux développeurs préfèrent que les noms de propriétés DBSet soient au pluriel.

Le code mis en mis en évidence :

  • Crée une propriété DbSet<TEntity> pour chaque jeu d’entités. Dans la terminologie EF Core :
    • Un jeu d’entités correspond généralement à une table de base de données.
    • Une entité correspond à une ligne dans la table.
  • Appelle OnModelCreating. OnModelCreating :
    • Est appelée lorsque SchoolContext a été initialisé, mais avant que le modèle ne soit sécurisé et utilisé pour initialiser le contexte.
    • Est obligatoire, car plus loin dans le tutoriel, l’entité Student aura des références aux autres entités.

Générez le projet pour vérifier qu’il n’y a pas d’erreurs du compilateur.

Startup.cs

ASP.NET Core comprend l’injection de dépendances. Des services, tels que SchoolContext, sont inscrits avec l’injection de dépendances au démarrage de l’application. Ces services sont affectés aux composants qui les nécessitent, par exemple les Pages Razor, par le biais de paramètres de constructeur. Le code de constructeur qui obtient une instance de contexte de base de données est indiqué plus loin dans le tutoriel.

L’outil de génération de modèles automatique a inscrit automatiquement la classe du contexte dans le conteneur d’injection de dépendances.

Les lignes en surbrillance suivantes ont été ajoutées par l’outil de génération de modèles automatique :

public void ConfigureServices(IServiceCollection services)
{
    services.AddRazorPages();

    services.AddDbContext<SchoolContext>(options =>
            options.UseSqlServer(Configuration.GetConnectionString("SchoolContext")));
}

Le nom de la chaîne de connexion est transmis au contexte en appelant une méthode sur un objet DbContextOptions. Pour le développement local, le système de configuration ASP.NET Core lit la chaîne de connexion à partir du fichier appsettings.json.

Ajouter le filtre d’exception de la base de données

Ajoutez AddDatabaseDeveloperPageExceptionFilter et UseMigrationsEndPoint comme indiqué dans le code suivant :

public void ConfigureServices(IServiceCollection services)
{
    services.AddRazorPages();

    services.AddDbContext<SchoolContext>(options =>
       options.UseSqlServer(Configuration.GetConnectionString("SchoolContext")));

    services.AddDatabaseDeveloperPageExceptionFilter();
}

public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
    if (env.IsDevelopment())
    {
        app.UseDeveloperExceptionPage();
        app.UseMigrationsEndPoint();
    }
    else
    {
        app.UseExceptionHandler("/Error");
        app.UseHsts();
    }

    app.UseHttpsRedirection();
    app.UseStaticFiles();

    app.UseRouting();

    app.UseAuthorization();

    app.UseEndpoints(endpoints =>
    {
        endpoints.MapRazorPages();
    });
}

Ajoutez le package NuGet Microsoft.AspNetCore.Diagnostics.EntityFrameworkCore.

Dans la Console du gestionnaire de package, entrez la commande suivante pour ajouter le package NuGet :

Install-Package Microsoft.AspNetCore.Diagnostics.EntityFrameworkCore

Le package NuGet Microsoft.AspNetCore.Diagnostics.EntityFrameworkCore fournit un intergiciel ASP.NET Core pour les pages d’erreur Entity Framework Core. Cet intergiciel permet de détecter et de diagnostiquer les erreurs de migration Entity Framework Core.

Le AddDatabaseDeveloperPageExceptionFilter fournit des informations utiles sur les erreurs dans l’environnement de développement pour les erreurs de migration EF.

Création de la base de données

Mettez à jour Program.cs pour créer la base de données si elle n’existe pas :

using ContosoUniversity.Data;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using System;

namespace ContosoUniversity
{
    public class Program
    {
        public static void Main(string[] args)
        {
            var host = CreateHostBuilder(args).Build();

            CreateDbIfNotExists(host);

            host.Run();
        }

        private static void CreateDbIfNotExists(IHost host)
        {
            using (var scope = host.Services.CreateScope())
            {
                var services = scope.ServiceProvider;
                try
                {
                    var context = services.GetRequiredService<SchoolContext>();
                    context.Database.EnsureCreated();
                    // DbInitializer.Initialize(context);
                }
                catch (Exception ex)
                {
                    var logger = services.GetRequiredService<ILogger<Program>>();
                    logger.LogError(ex, "An error occurred creating the DB.");
                }
            }
        }

        public static IHostBuilder CreateHostBuilder(string[] args) =>
            Host.CreateDefaultBuilder(args)
                .ConfigureWebHostDefaults(webBuilder =>
                {
                    webBuilder.UseStartup<Startup>();
                });
    }
}

La méthode EnsureCreated n’effectue aucune action s’il existe une base de données pour le contexte. S’il n’existe pas de base de données, elle crée la base de données et le schéma. EnsureCreated active le workflow suivant pour gérer les modifications du modèle de données :

  • Supprimez la base de données. Toutes les données existantes sont perdues.
  • Modifiez le modèle de données. Par exemple, ajoutez un champ EmailAddress.
  • Exécutez l’application.
  • EnsureCreated crée une base de données avec le nouveau schéma.

Ce workflow fonctionne à un stade précoce du développement, quand le schéma évolue rapidement, aussi longtemps que vous n’avez pas besoin de conserver les données. La situation est différente quand les données qui ont été entrées dans la base de données doivent être conservées. Dans ce cas, procédez à des migrations.

Plus tard dans cette série de tutoriels, vous supprimerez la base de données créée par EnsureCreated et procéderez à des migrations. Une base de données créée par EnsureCreated ne peut pas être mise à jour via des migrations.

Test de l'application

  • Exécutez l’application.
  • Sélectionnez le lien Students, puis Créer nouveau.
  • Testez les liens Modifier, Détails et Supprimer.

Amorcer la base de données

La méthode EnsureCreated crée une base de données vide. Cette section ajoute du code qui remplit la base de données avec des données de test.

Créez Data/DbInitializer.cs avec le code suivant :

using ContosoUniversity.Models;
using System;
using System.Linq;

namespace ContosoUniversity.Data
{
    public static class DbInitializer
    {
        public static void Initialize(SchoolContext context)
        {
            // Look for any students.
            if (context.Students.Any())
            {
                return;   // DB has been seeded
            }

            var students = new Student[]
            {
                new Student{FirstMidName="Carson",LastName="Alexander",EnrollmentDate=DateTime.Parse("2019-09-01")},
                new Student{FirstMidName="Meredith",LastName="Alonso",EnrollmentDate=DateTime.Parse("2017-09-01")},
                new Student{FirstMidName="Arturo",LastName="Anand",EnrollmentDate=DateTime.Parse("2018-09-01")},
                new Student{FirstMidName="Gytis",LastName="Barzdukas",EnrollmentDate=DateTime.Parse("2017-09-01")},
                new Student{FirstMidName="Yan",LastName="Li",EnrollmentDate=DateTime.Parse("2017-09-01")},
                new Student{FirstMidName="Peggy",LastName="Justice",EnrollmentDate=DateTime.Parse("2016-09-01")},
                new Student{FirstMidName="Laura",LastName="Norman",EnrollmentDate=DateTime.Parse("2018-09-01")},
                new Student{FirstMidName="Nino",LastName="Olivetto",EnrollmentDate=DateTime.Parse("2019-09-01")}
            };

            context.Students.AddRange(students);
            context.SaveChanges();

            var courses = new Course[]
            {
                new Course{CourseID=1050,Title="Chemistry",Credits=3},
                new Course{CourseID=4022,Title="Microeconomics",Credits=3},
                new Course{CourseID=4041,Title="Macroeconomics",Credits=3},
                new Course{CourseID=1045,Title="Calculus",Credits=4},
                new Course{CourseID=3141,Title="Trigonometry",Credits=4},
                new Course{CourseID=2021,Title="Composition",Credits=3},
                new Course{CourseID=2042,Title="Literature",Credits=4}
            };

            context.Courses.AddRange(courses);
            context.SaveChanges();

            var enrollments = new Enrollment[]
            {
                new Enrollment{StudentID=1,CourseID=1050,Grade=Grade.A},
                new Enrollment{StudentID=1,CourseID=4022,Grade=Grade.C},
                new Enrollment{StudentID=1,CourseID=4041,Grade=Grade.B},
                new Enrollment{StudentID=2,CourseID=1045,Grade=Grade.B},
                new Enrollment{StudentID=2,CourseID=3141,Grade=Grade.F},
                new Enrollment{StudentID=2,CourseID=2021,Grade=Grade.F},
                new Enrollment{StudentID=3,CourseID=1050},
                new Enrollment{StudentID=4,CourseID=1050},
                new Enrollment{StudentID=4,CourseID=4022,Grade=Grade.F},
                new Enrollment{StudentID=5,CourseID=4041,Grade=Grade.C},
                new Enrollment{StudentID=6,CourseID=1045},
                new Enrollment{StudentID=7,CourseID=3141,Grade=Grade.A},
            };

            context.Enrollments.AddRange(enrollments);
            context.SaveChanges();
        }
    }
}

Le code vérifie si des étudiants figurent dans la base de données. S’il n’y a pas d’étudiants, il ajoute des données de test à la base de données. Il crée les données de test dans des tableaux et non dans des collections List<T> afin d’optimiser les performances.

  • Dans Program.cs, supprimez // de la ligne DbInitializer.Initialize :

      context.Database.EnsureCreated();
      DbInitializer.Initialize(context);
    
  • Arrêtez l’application si elle est en cours d’exécution et exécutez la commande suivante dans la Console du gestionnaire de package :

    Drop-Database -Confirm
    
    
  • Répondez avec Y pour supprimer la base de données.

  • Redémarrez l’application.
  • Sélectionnez la page Students pour examiner les données amorcées.

Afficher la base de données

  • Ouvrez l’Explorateur d’objets SQL Server (SSOX) à partir du menu Affichage de Visual Studio.
  • Dans SSOX, sélectionnez (localdb)\MSSQLLocalDB > Databases > SchoolContext-{GUID}. Le nom de la base de données est généré à partir du nom de contexte indiqué précédemment, ainsi que d’un tiret et d’un GUID.
  • Développez le nœud Tables.
  • Cliquez avec le bouton droit sur la table Student et cliquez sur Afficher les données pour voir les colonnes créées et les lignes insérées dans la table.
  • Cliquez avec le bouton droit sur la table Student et cliquez sur Afficher le code pour voir comment le modèle Student est mappé au schéma de la table Student.

Code asynchrone

La programmation asynchrone est le mode par défaut pour ASP.NET Core et EF Core.

Un serveur web a un nombre limité de threads disponibles et, dans les situations de forte charge, tous les threads disponibles peuvent être utilisés. Quand cela se produit, le serveur ne peut pas traiter de nouvelle requête tant que les threads ne sont pas libérés. Avec le code synchrone, plusieurs threads peuvent être bloqués alors qu’ils n’effectuent aucun travail, car ils attendent que des E/S se terminent. Avec le code asynchrone, quand un processus attend que des E/S se terminent, son thread est libéré afin d’être utilisé par le serveur pour traiter d’autres demandes. Il permet ainsi d’utiliser les ressources serveur plus efficacement, et le serveur peut gérer plus de trafic sans retard.

Le code asynchrone introduit néanmoins une petite surcharge au moment de l’exécution. Dans les situations de faible trafic, le gain de performances est négligeable, tandis qu’en cas de trafic élevé l’amélioration potentielle des performances est importante.

Dans le code suivant, le mot clé async, la valeur renvoyée Task, le mot clé await et la méthode ToListAsync déclenchent l’exécution asynchrone du code.

public async Task OnGetAsync()
{
    Students = await _context.Students.ToListAsync();
}
  • Le mot clé async fait en sorte que le compilateur :
    • Génère des rappels pour les parties du corps de méthode.
    • Crée l’objet Task qui est retourné.
  • Le type de retour Task représente le travail en cours.
  • Le mot clé await indique au compilateur de fractionner la méthode en deux parties. La première partie se termine par l’opération qui est démarrée de façon asynchrone. La seconde partie est placée dans une méthode de rappel qui est appelée quand l’opération se termine.
  • ToListAsync est la version asynchrone de la méthode d’extension ToList.

Voici quelques éléments à connaître lors de l’écriture de code asynchrone qui utilise EF Core :

  • Seules les instructions qui provoquent l’envoi de requêtes ou de commandes vers la base de données sont exécutées de façon asynchrone. Cela inclut ToListAsync, SingleOrDefaultAsync, FirstOrDefaultAsync et SaveChangesAsync, mais pas les instructions qui ne font que changer un IQueryable, telles que var students = context.Students.Where(s => s.LastName == "Davolio").
  • Un contexte EF Core n’est pas thread-safe : n’essayez pas d’effectuer plusieurs opérations en parallèle.
  • Pour tirer parti des avantages de performances du code asynchrone, vérifiez que les packages de bibliothèque (par exemple pour la pagination) utilisent le mode asynchrone s’ils appellent des méthodes EF Core qui envoient des requêtes à la base de données.

Pour plus d’informations sur la programmation asynchrone dans .NET, consultez Vue d’ensemble d’Async et Programmation asynchrone avec async et await.

Considérations relatives aux performances

En général, une page web ne doit pas charger un nombre arbitraire de lignes. Une requête doit utiliser la pagination ou une approche limitative. Par exemple, la requête précédente peut utiliser Take pour limiter les lignes retournées :

public async Task OnGetAsync()
{
    Student = await _context.Students.Take(10).ToListAsync();
}

L’énumération d’une table volumineuse dans une vue peut renvoyer une réponse HTTP 200 partiellement construite si une exception de base de données se produit pendant l’énumération.

MaxModelBindingCollectionSize par défaut est 1024. Le code suivant définit MaxModelBindingCollectionSize :

public void ConfigureServices(IServiceCollection services)
{
    var myMaxModelBindingCollectionSize = Convert.ToInt32(
                Configuration["MyMaxModelBindingCollectionSize"] ?? "100");

    services.Configure<MvcOptions>(options =>
           options.MaxModelBindingCollectionSize = myMaxModelBindingCollectionSize);

    services.AddRazorPages();

    services.AddDbContext<SchoolContext>(options =>
          options.UseSqlServer(Configuration.GetConnectionString("SchoolContext")));

    services.AddDatabaseDeveloperPageExceptionFilter();
}

Consultez Configuration pour plus d’informations sur les paramètres de configuration tels que MyMaxModelBindingCollectionSize.

La pagination est abordée plus loin dans le tutoriel.

Pour plus d’informations, consultez Considérations sur les performances (EF).

Journalisation SQL d’Entity Framework Core

La configuration de la journalisation est généralement fournie par la section Logging des fichiers appsettings.{Environment}.json. Pour journaliser les instructions SQL, ajoutez "Microsoft.EntityFrameworkCore.Database.Command": "Information" au fichier appsettings.Development.json :

{
  "ConnectionStrings": {
    "DefaultConnection": "Server=(localdb)\\mssqllocaldb;Database=MyDB-2;Trusted_Connection=True;MultipleActiveResultSets=true"
  },
  "Logging": {
    "LogLevel": {
      "Default": "Information",
      "Microsoft": "Warning",
      "Microsoft.Hosting.Lifetime": "Information"
     ,"Microsoft.EntityFrameworkCore.Database.Command": "Information"
    }
  },
  "AllowedHosts": "*"
}

Avec la valeur JSON précédente, les instructions SQL s’affichent sur la ligne de commande et dans la fenêtre de sortie de Visual Studio.

Pour plus d’informations, consultez Journalisation dans .NET Core et ASP.NET Core et ce problème GitHub.

Étapes suivantes

Utiliser SQLite pour le développement, SQL Server pour la production

Il s’agit du premier tutoriel d’une série qui montre comment utiliser Entity Framework (EF) Core dans une application Pages Razor ASP.NET Core. Dans ces tutoriels, un site web est créé pour une université fictive nommée Contoso. Le site comprend des fonctionnalités comme l’admission des étudiants, la création de cours et les affectations des formateurs. Le tutoriel utilise l’approche code first. Pour plus d’informations sur la façon de suivre ce tutoriel à l’aide de l’approche database first, consultez ce problème Github.

Télécharger ou afficher l’application complète. Télécharger les instructions.

Prérequis

Moteurs de base de données

Les instructions Visual Studio utilisent la Base de données locale SQL Server, version de SQL Server Express qui s’exécute uniquement sur Windows.

Les instructions Visual Studio Code utilisent SQLite, moteur de base de données multiplateforme.

Si vous choisissez d’utiliser SQLite, téléchargez et installez un outil tiers pour la gestion et l’affichage d’une base de données SQLite, comme Browser for SQLite.

Résolution des problèmes

Si vous rencontrez un problème que vous ne pouvez pas résoudre, comparez votre code au projet terminé. Un bon moyen d’obtenir de l’aide est de poster une question sur StackOverflow.com en utilisant le mot-clé ASP.NET Core ou le mot-clé EF Core.

Exemple d’application

L’application générée dans ces didacticiels est le site web de base d’une université. Les utilisateurs peuvent afficher et mettre à jour les informations relatives aux étudiants, aux cours et aux formateurs. Voici quelques-uns des écrans créés dans le didacticiel.

Page d’index des étudiants

Page de modification des étudiants

Le style de l’interface utilisateur de ce site repose sur les modèles de projet intégrés. Le tutoriel traite essentiellement de l’utilisation d’EF Core, et non de la façon de personnaliser l’interface utilisateur.

Suivez le lien en haut de la page pour obtenir le code source du projet terminé. Le dossier cu30 contient le code de la version 3.0 d’ASP.NET Core. Les fichiers qui reflètent l’état du code pour les tutoriels 1-7 se trouvent dans le dossier cu30snapshots.

Pour exécuter l’application après avoir téléchargé le projet terminé :

  • Créez le projet.

  • Dans la console du Gestionnaire de package (PMC), exécutez la commande suivante :

    Update-Database
    
  • Exécutez le projet pour amorcer la base de données.

Créer le projet d’application web

  • Dans Visual Studio, dans le menu Fichier, sélectionnez Nouveau>Projet.
  • Sélectionnez Application web ASP.NET Core.
  • Nommez le projet ContosoUniversity. Il est important d’utiliser ce nom exact, en respectant l’utilisation des majuscules, de sorte que les espaces de noms correspondent au moment où le code est copié et collé.
  • Sélectionnez .NET Core et ASP.NET Core 3.0 dans les listes déroulantes, puis Application web.

Configurer le style du site

Configurez l’en-tête, le pied de page et le menu du site en mettant à jour Pages/Shared/_Layout.cshtml :

  • Remplacez chaque occurrence de « ContosoUniversity » par « Contoso University ». Il y a trois occurrences.

  • Supprimez les entrées de menu Home et Privacy, puis ajoutez les entrées À propos, Étudiants, Cours, Instructeurs et Services.

Les modifications sont mises en surbrillance.

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>@ViewData["Title"] - Contoso University</title>
    <link rel="stylesheet" href="~/lib/bootstrap/dist/css/bootstrap.css" />
    <link rel="stylesheet" href="~/css/site.css" />
</head>
<body>
    <header>
        <nav class="navbar navbar-expand-sm navbar-toggleable-sm navbar-light bg-white border-bottom box-shadow mb-3">
            <div class="container">
                <a class="navbar-brand" asp-area="" asp-page="/Index">Contoso University</a>
                <button class="navbar-toggler" type="button" data-toggle="collapse" data-target=".navbar-collapse" aria-controls="navbarSupportedContent"
                        aria-expanded="false" aria-label="Toggle navigation">
                    <span class="navbar-toggler-icon"></span>
                </button>
                <div class="navbar-collapse collapse d-sm-inline-flex flex-sm-row-reverse">
                    <ul class="navbar-nav flex-grow-1">
                        <li class="nav-item">
                            <a class="nav-link text-dark" asp-area="" asp-page="/About">About</a>
                        </li>
                        <li class="nav-item">
                            <a class="nav-link text-dark" asp-area="" asp-page="/Students/Index">Students</a>
                        </li>
                        <li class="nav-item">
                            <a class="nav-link text-dark" asp-area="" asp-page="/Courses/Index">Courses</a>
                        </li>
                        <li class="nav-item">
                            <a class="nav-link text-dark" asp-area="" asp-page="/Instructors/Index">Instructors</a>
                        </li>
                        <li class="nav-item">
                            <a class="nav-link text-dark" asp-area="" asp-page="/Departments/Index">Departments</a>
                        </li>
                    </ul>
                </div>
            </div>
        </nav>
    </header>
    <div class="container">
        <main role="main" class="pb-3">
            @RenderBody()
        </main>
    </div>

    <footer class="border-top footer text-muted">
        <div class="container">
            &copy; 2019 - Contoso University - <a asp-area="" asp-page="/Privacy">Privacy</a>
        </div>
    </footer>

    <script src="~/lib/jquery/dist/jquery.js"></script>
    <script src="~/lib/bootstrap/dist/js/bootstrap.bundle.js"></script>
    <script src="~/js/site.js" asp-append-version="true"></script>

    @RenderSection("Scripts", required: false)
</body>
</html>

Dans Pages/Index.cshtml, remplacez le contenu du fichier par le code suivant de façon à remplacer le texte relatif à ASP.NET Core par le texte se rapportant à cette application :

@page
@model IndexModel
@{
    ViewData["Title"] = "Home page";
}

<div class="row mb-auto">
    <div class="col-md-4">
        <div class="row no-gutters border mb-4">
            <div class="col p-4 mb-4 ">
                <p class="card-text">
                    Contoso University is a sample application that
                    demonstrates how to use Entity Framework Core in an
                    ASP.NET Core Razor Pages web app.
                </p>
            </div>
        </div>
    </div>
    <div class="col-md-4">
        <div class="row no-gutters border mb-4">
            <div class="col p-4 d-flex flex-column position-static">
                <p class="card-text mb-auto">
                    You can build the application by following the steps in a series of tutorials.
                </p>
                <p>
                    <a href="https://docs.microsoft.com/aspnet/core/data/ef-rp/intro" class="stretched-link">See the tutorial</a>
                </p>
            </div>
        </div>
    </div>
    <div class="col-md-4">
        <div class="row no-gutters border mb-4">
            <div class="col p-4 d-flex flex-column">
                <p class="card-text mb-auto">
                    You can download the completed project from GitHub.
                </p>
                <p>
                    <a href="https://github.com/dotnet/AspNetCore.Docs/tree/main/aspnetcore/data/ef-rp/intro/samples" class="stretched-link">See project source code</a>
                </p>
            </div>
        </div>
    </div>
</div>

Exécutez l’application pour vérifier que la page home s’affiche.

Le modèle de données

Les sections suivantes créent un modèle de données :

Diagramme du modèle de données Course-Enrollment-Student

Un étudiant peut s’inscrire à un nombre quelconque de cours et un cours peut avoir un nombre quelconque d’élèves inscrits.

L’entité Student

Diagramme de l’entité Student

  • Créez un dossier Models dans le dossier de projet.

  • Créez Models/Student.cs avec le code suivant :

    using System;
    using System.Collections.Generic;
    
    namespace ContosoUniversity.Models
    {
        public class Student
        {
            public int ID { get; set; }
            public string LastName { get; set; }
            public string FirstMidName { get; set; }
            public DateTime EnrollmentDate { get; set; }
    
            public ICollection<Enrollment> Enrollments { get; set; }
        }
    }
    

La propriété ID devient la colonne de clé primaire de la table de base de données qui correspond à cette classe. Par défaut, EF Core interprète une propriété nommée ID ou classnameID comme clé primaire. L’autre nom reconnu automatiquement de la clé primaire de classe Student est StudentID. Pour plus d’informations, consultez EF Core - Clés.

La propriété Enrollments est une propriété de navigation. Les propriétés de navigation contiennent d’autres entités qui sont associées à cette entité. Dans ce cas, la propriété Enrollments d’une entité Student contient toutes les entités Enrollment associées à cet étudiant. Par exemple, si une ligne Student dans la base de données est associée à deux lignes Enrollment, la propriété de navigation Enrollments contient ces deux entités Enrollment.

Dans la base de données, une ligne Enrollment est associée à une ligne Student si sa colonne StudentID contient la valeur d’ID de l’étudiant. Par exemple, supposez qu’une ligne Student présente un ID égal à 1. Les lignes Enrollment associées auront un StudentID égal à 1. StudentID est une clé étrangère dans la table Enrollment.

La propriété Enrollments est définie en tant que ICollection<Enrollment>, car plusieurs entités Enrollment associées peuvent exister. Vous pouvez utiliser d’autres types de collection, comme List<Enrollment> ou HashSet<Enrollment>. Quand vous utilisez ICollection<Enrollment>, EF Core crée une collection HashSet<Enrollment> par défaut.

L’entité Enrollment

Diagramme de l’entité Enrollment

Créez Models/Enrollment.cs avec le code suivant :

namespace ContosoUniversity.Models
{
    public enum Grade
    {
        A, B, C, D, F
    }

    public class Enrollment
    {
        public int EnrollmentID { get; set; }
        public int CourseID { get; set; }
        public int StudentID { get; set; }
        public Grade? Grade { get; set; }

        public Course Course { get; set; }
        public Student Student { get; set; }
    }
}

La propriété EnrollmentID est la clé primaire ; cette entité utilise le modèle classnameID à la place de ID par lui-même. Pour un modèle de données de production, choisissez un modèle et utilisez-le systématiquement. Ce tutoriel utilise les deux pour montrer qu’ils fonctionnent tous les deux. L’utilisation de ID sans classname facilite l’implémentation de certaines modifications du modèle de données.

La propriété Grade est un enum. La présence du point d’interrogation après la déclaration de type Grade indique que la propriété Gradeaccepte les valeurs Null. Une note (Grade) qui a la valeur Null est différente d’une note égale à zéro : la valeur Null signifie qu’une note n’est pas connue ou n’a pas encore été affectée.

La propriété StudentID est une clé étrangère, et la propriété de navigation correspondante est Student. Une entité Enrollment est associée à une entité Student. Par conséquent, la propriété contient une seule entité Student.

La propriété CourseID est une clé étrangère, et la propriété de navigation correspondante est Course. Une entité Enrollment est associée à une entité Course.

EF Core interprète une propriété en tant que clé étrangère si elle se nomme <navigation property name><primary key property name>. Par exemple, StudentID est la clé étrangère pour la propriété de navigation Student, car la clé primaire de l’entité Student est ID. Les propriétés de clé étrangère peuvent également se nommer <primary key property name>. Par exemple, CourseID puisque la clé primaire de l’entité Course est CourseID.

L’entité Course

Diagramme de l’entité Course

Créez Models/Course.cs avec le code suivant :

using System.Collections.Generic;
using System.ComponentModel.DataAnnotations.Schema;

namespace ContosoUniversity.Models
{
    public class Course
    {
        [DatabaseGenerated(DatabaseGeneratedOption.None)]
        public int CourseID { get; set; }
        public string Title { get; set; }
        public int Credits { get; set; }

        public ICollection<Enrollment> Enrollments { get; set; }
    }
}

La propriété Enrollments est une propriété de navigation. Une entité Course peut être associée à un nombre quelconque d’entités Enrollment.

L’attribut DatabaseGenerated permet à l’application de spécifier la clé primaire, plutôt que de la faire générer par la base de données.

Générez le projet pour vérifier qu’il n’y a pas d’erreurs du compilateur.

Générer automatiquement des modèles de pages Student

Dans cette section, vous allez utiliser l’outil de génération de modèles automatique ASP.NET Core pour générer :

  • Une classe de EF Corecontexte. Le contexte est la classe principale qui coordonne les fonctionnalités d’Entity Framework pour un modèle de données déterminé. Il dérive de la classe Microsoft.EntityFrameworkCore.DbContext.
  • Les pages Razor qui gèrent les opérations Créer, Lecture, Mettre à jour et Supprimer (CRUD) pour l’entité Student.
  • Créez un dossier Students dans le dossier Pages.
  • Dans l’Explorateur de solutions, cliquez avec le bouton droit sur le dossier Pages/Students, puis sélectionnez Ajouter>Nouvel élément généré automatiquement.
  • Dans la boîte de dialogue Ajouter un modèle automatique, sélectionnez Pages Razor utilisant Entity Framework (CRUD)>Ajouter.
  • Dans la boîte de dialogue Ajouter des Pages Razor avec Entity Framework (CRUD) :
    • Dans la liste déroulante Classe de modèle, sélectionnez Student (ContosoUniversity.Models).
    • Dans la ligne Classe du contexte de données, sélectionnez le signe + (plus).
    • Remplacez le nom du contexte de données ContosoUniversity.Models.ContosoUniversityContext par ContosoUniversity.Data.SchoolContext.
    • Sélectionnez Ajouter.

Les packages suivants sont automatiquement installés :

  • Microsoft.VisualStudio.Web.CodeGeneration.Design
  • Microsoft.EntityFrameworkCore.SqlServer
  • Microsoft.Extensions.Logging.Debug
  • Microsoft.EntityFrameworkCore.Tools

Si vous rencontrez un problème à l’étape précédente, générez le projet et recommencez l’étape de génération de modèles automatique.

Le processus de génération de modèles automatique :

  • Crée les pages Razor dans le dossier Pages/Étudiants :
    • Create.cshtml et Create.cshtml.cs
    • Delete.cshtml et Delete.cshtml.cs
    • Details.cshtml et Details.cshtml.cs
    • Edit.cshtml et Edit.cshtml.cs
    • Index.cshtml et Index.cshtml.cs
  • Crée Data/SchoolContext.cs.
  • Ajoute le contexte à l’injection de dépendances dans Startup.cs.
  • Ajoute une chaîne de connexion de base de données à appsettings.json.

Chaîne de connexion de base de données

Le fichier appsettings.json spécifie la chaîne de connexion SQL Server LocalDB.

{
  "Logging": {
    "LogLevel": {
      "Default": "Information",
      "Microsoft": "Warning",
      "Microsoft.Hosting.Lifetime": "Information"
    }
  },
  "AllowedHosts": "*",
  "ConnectionStrings": {
    "SchoolContext": "Server=(localdb)\\mssqllocaldb;Database=SchoolContext6;Trusted_Connection=True;MultipleActiveResultSets=true"
  }
}

LocalDB est une version allégée du moteur de base de données SQL Server Express. Elle est destinée au développement d’applications, et non à une utilisation en production. Par défaut, la Base de données locale crée des fichiers .mdf dans le répertoire C:/Users/<user>.

Mettre à jour la classe du contexte de base de données

La classe principale qui coordonne les fonctionnalités EF Core pour un modèle de données déterminé est la classe du contexte de base de données. Le contexte de données est dérivé de Microsoft.EntityFrameworkCore.DbContext. Il spécifie les entités qui sont incluses dans le modèle de données. Dans ce projet, la classe est nommée SchoolContext.

Mettez à jour Data/SchoolContext.cs à l’aide du code suivant :

using Microsoft.EntityFrameworkCore;
using ContosoUniversity.Models;

namespace ContosoUniversity.Data
{
    public class SchoolContext : DbContext
    {
        public SchoolContext (DbContextOptions<SchoolContext> options)
            : base(options)
        {
        }

        public DbSet<Student> Students { get; set; }
        public DbSet<Enrollment> Enrollments { get; set; }
        public DbSet<Course> Courses { get; set; }

        protected override void OnModelCreating(ModelBuilder modelBuilder)
        {
            modelBuilder.Entity<Course>().ToTable("Course");
            modelBuilder.Entity<Enrollment>().ToTable("Enrollment");
            modelBuilder.Entity<Student>().ToTable("Student");
        }
    }
}

Le code en surbrillance crée une propriété DbSet<TEntity> pour chaque jeu d’entités. Dans la terminologie EF Core :

  • Un jeu d’entités correspond généralement à une table de base de données.
  • Une entité correspond à une ligne dans la table.

Comme un jeu d’entités contient plusieurs entités, les propriétés DBSet doivent être des noms au pluriel. Comme l’outil de génération de modèles automatique a créé un DBSet Student, cette étape le remplace par le pluriel Students.

Pour que le code Pages Razor corresponde au nouveau nom DBSet, apportez une modification globale à l’ensemble du projet en remplaçant _context.Student par _context.Students. Il y a 8 occurrences.

Générez le projet pour vérifier qu’il n’y a pas d’erreurs du compilateur.

Startup.cs

ASP.NET Core comprend l’injection de dépendances. Des services (tels que le contexte de base de données EF Core) sont inscrits avec l’injection de dépendance au démarrage de l’application. Ces services sont affectés aux composants qui les nécessitent (par exemple les Pages Razor) par le biais de paramètres de constructeur. Le code de constructeur qui obtient une instance de contexte de base de données est indiqué plus loin dans le tutoriel.

L’outil de génération de modèles automatique a inscrit automatiquement la classe du contexte dans le conteneur d’injection de dépendances.

  • Dans ConfigureServices, les lignes en surbrillance ont été ajoutées par l’outil de génération de modèles automatique :

    public void ConfigureServices(IServiceCollection services)
    {
        services.AddRazorPages();
    
        services.AddDbContext<SchoolContext>(options =>
                options.UseSqlServer(Configuration.GetConnectionString("SchoolContext")));
    }
    

Le nom de la chaîne de connexion est transmis au contexte en appelant une méthode sur un objet DbContextOptions. Pour le développement local, le système de configuration ASP.NET Core lit la chaîne de connexion à partir du fichier appsettings.json.

Création de la base de données

Mettez à jour Program.cs pour créer la base de données si elle n’existe pas :

using ContosoUniversity.Data;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using System;

namespace ContosoUniversity
{
    public class Program
    {
        public static void Main(string[] args)
        {
            var host = CreateHostBuilder(args).Build();

            CreateDbIfNotExists(host);

            host.Run();
        }

        private static void CreateDbIfNotExists(IHost host)
        {
            using (var scope = host.Services.CreateScope())
            {
                var services = scope.ServiceProvider;
                try
                {
                    var context = services.GetRequiredService<SchoolContext>();
                    context.Database.EnsureCreated();
                    // DbInitializer.Initialize(context);
                }
                catch (Exception ex)
                {
                    var logger = services.GetRequiredService<ILogger<Program>>();
                    logger.LogError(ex, "An error occurred creating the DB.");
                }
            }
        }

        public static IHostBuilder CreateHostBuilder(string[] args) =>
            Host.CreateDefaultBuilder(args)
                .ConfigureWebHostDefaults(webBuilder =>
                {
                    webBuilder.UseStartup<Startup>();
                });
    }
}

La méthode EnsureCreated n’effectue aucune action s’il existe une base de données pour le contexte. S’il n’existe pas de base de données, elle crée la base de données et le schéma. EnsureCreated active le workflow suivant pour gérer les modifications du modèle de données :

  • Supprimez la base de données. Toutes les données existantes sont perdues.
  • Modifiez le modèle de données. Par exemple, ajoutez un champ EmailAddress.
  • Exécutez l’application.
  • EnsureCreated crée une base de données avec le nouveau schéma.

Ce workflow fonctionne bien à un stade précoce du développement, quand le schéma évolue rapidement, aussi longtemps que vous n’avez pas besoin de conserver les données. La situation est différente quand les données qui ont été entrées dans la base de données doivent être conservées. Dans ce cas, procédez à des migrations.

Plus tard dans cette série de tutoriels, vous supprimerez la base de données créée par EnsureCreated et procéderez à des migrations. Une base de données créée par EnsureCreated ne peut pas être mise à jour via des migrations.

Test de l'application

  • Exécutez l’application.
  • Sélectionnez le lien Students, puis Créer nouveau.
  • Testez les liens Modifier, Détails et Supprimer.

Amorcer la base de données

La méthode EnsureCreated crée une base de données vide. Cette section ajoute du code qui remplit la base de données avec des données de test.

Créez Data/DbInitializer.cs avec le code suivant :

using ContosoUniversity.Data;
using ContosoUniversity.Models;
using System;
using System.Linq;

namespace ContosoUniversity.Data
{
    public static class DbInitializer
    {
        public static void Initialize(SchoolContext context)
        {
            context.Database.EnsureCreated();

            // Look for any students.
            if (context.Students.Any())
            {
                return;   // DB has been seeded
            }

            var students = new Student[]
            {
                new Student{FirstMidName="Carson",LastName="Alexander",EnrollmentDate=DateTime.Parse("2019-09-01")},
                new Student{FirstMidName="Meredith",LastName="Alonso",EnrollmentDate=DateTime.Parse("2017-09-01")},
                new Student{FirstMidName="Arturo",LastName="Anand",EnrollmentDate=DateTime.Parse("2018-09-01")},
                new Student{FirstMidName="Gytis",LastName="Barzdukas",EnrollmentDate=DateTime.Parse("2017-09-01")},
                new Student{FirstMidName="Yan",LastName="Li",EnrollmentDate=DateTime.Parse("2017-09-01")},
                new Student{FirstMidName="Peggy",LastName="Justice",EnrollmentDate=DateTime.Parse("2016-09-01")},
                new Student{FirstMidName="Laura",LastName="Norman",EnrollmentDate=DateTime.Parse("2018-09-01")},
                new Student{FirstMidName="Nino",LastName="Olivetto",EnrollmentDate=DateTime.Parse("2019-09-01")}
            };

            context.Students.AddRange(students);
            context.SaveChanges();

            var courses = new Course[]
            {
                new Course{CourseID=1050,Title="Chemistry",Credits=3},
                new Course{CourseID=4022,Title="Microeconomics",Credits=3},
                new Course{CourseID=4041,Title="Macroeconomics",Credits=3},
                new Course{CourseID=1045,Title="Calculus",Credits=4},
                new Course{CourseID=3141,Title="Trigonometry",Credits=4},
                new Course{CourseID=2021,Title="Composition",Credits=3},
                new Course{CourseID=2042,Title="Literature",Credits=4}
            };

            context.Courses.AddRange(courses);
            context.SaveChanges();

            var enrollments = new Enrollment[]
            {
                new Enrollment{StudentID=1,CourseID=1050,Grade=Grade.A},
                new Enrollment{StudentID=1,CourseID=4022,Grade=Grade.C},
                new Enrollment{StudentID=1,CourseID=4041,Grade=Grade.B},
                new Enrollment{StudentID=2,CourseID=1045,Grade=Grade.B},
                new Enrollment{StudentID=2,CourseID=3141,Grade=Grade.F},
                new Enrollment{StudentID=2,CourseID=2021,Grade=Grade.F},
                new Enrollment{StudentID=3,CourseID=1050},
                new Enrollment{StudentID=4,CourseID=1050},
                new Enrollment{StudentID=4,CourseID=4022,Grade=Grade.F},
                new Enrollment{StudentID=5,CourseID=4041,Grade=Grade.C},
                new Enrollment{StudentID=6,CourseID=1045},
                new Enrollment{StudentID=7,CourseID=3141,Grade=Grade.A},
            };

            context.Enrollments.AddRange(enrollments);
            context.SaveChanges();
        }
    }
}

Le code vérifie si des étudiants figurent dans la base de données. S’il n’y a pas d’étudiants, il ajoute des données de test à la base de données. Il crée les données de test dans des tableaux et non dans des collections List<T> afin d’optimiser les performances.

  • Dans Program.cs, remplacez l’appel EnsureCreated par un appel DbInitializer.Initialize :

    // context.Database.EnsureCreated();
    DbInitializer.Initialize(context);
    

Arrêtez l’application si elle est en cours d’exécution et exécutez la commande suivante dans la Console du gestionnaire de package :

Drop-Database
  • Redémarrez l’application.

  • Sélectionnez la page Students pour examiner les données amorcées.

Afficher la base de données

  • Ouvrez l’Explorateur d’objets SQL Server (SSOX) à partir du menu Affichage de Visual Studio.
  • Dans SSOX, sélectionnez (localdb)\MSSQLLocalDB > Databases > SchoolContext-{GUID}. Le nom de la base de données est généré à partir du nom de contexte indiqué précédemment, ainsi que d’un tiret et d’un GUID.
  • Développez le nœud Tables.
  • Cliquez avec le bouton droit sur la table Student et cliquez sur Afficher les données pour voir les colonnes créées et les lignes insérées dans la table.
  • Cliquez avec le bouton droit sur la table Student et cliquez sur Afficher le code pour voir comment le modèle Student est mappé au schéma de la table Student.

Code asynchrone

La programmation asynchrone est le mode par défaut pour ASP.NET Core et EF Core.

Un serveur web a un nombre limité de threads disponibles et, dans les situations de forte charge, tous les threads disponibles peuvent être utilisés. Quand cela se produit, le serveur ne peut pas traiter de nouvelle requête tant que les threads ne sont pas libérés. Avec le code synchrone, plusieurs threads peuvent être bloqués alors qu’ils n’effectuent en fait aucun travail, car ils attendent que des E/S se terminent. Avec le code asynchrone, quand un processus attend que des E/S se terminent, son thread est libéré afin d’être utilisé par le serveur pour traiter d’autres demandes. Il permet ainsi d’utiliser les ressources serveur plus efficacement, et le serveur peut gérer plus de trafic sans retard.

Le code asynchrone introduit néanmoins une petite surcharge au moment de l’exécution. Dans les situations de faible trafic, le gain de performances est négligeable, tandis qu’en cas de trafic élevé l’amélioration potentielle des performances est importante.

Dans le code suivant, le mot clé async, la valeur renvoyée Task<T>, le mot clé await et la méthode ToListAsync déclenchent l’exécution asynchrone du code.

public async Task OnGetAsync()
{
    Students = await _context.Students.ToListAsync();
}
  • Le mot clé async fait en sorte que le compilateur :
    • Génère des rappels pour les parties du corps de méthode.
    • Crée l’objet Task qui est retourné.
  • Le type de retour Task<T> représente le travail en cours.
  • Le mot clé await indique au compilateur de fractionner la méthode en deux parties. La première partie se termine par l’opération qui est démarrée de façon asynchrone. La seconde partie est placée dans une méthode de rappel qui est appelée quand l’opération se termine.
  • ToListAsync est la version asynchrone de la méthode d’extension ToList.

Voici quelques éléments à connaître lors de l’écriture de code asynchrone qui utilise EF Core :

  • Seules les instructions qui provoquent l’envoi de requêtes ou de commandes vers la base de données sont exécutées de façon asynchrone. Cela inclut ToListAsync, SingleOrDefaultAsync, FirstOrDefaultAsync et SaveChangesAsync, mais pas les instructions qui ne font que changer un IQueryable, telles que var students = context.Students.Where(s => s.LastName == "Davolio").
  • Un contexte EF Core n’est pas thread-safe : n’essayez pas d’effectuer plusieurs opérations en parallèle.
  • Pour tirer parti des avantages de performances du code asynchrone, vérifiez que les packages de bibliothèque (par exemple pour la pagination) utilisent le mode asynchrone s’ils appellent des méthodes EF Core qui envoient des requêtes à la base de données.

Pour plus d’informations sur la programmation asynchrone dans .NET, consultez Vue d’ensemble d’Async et Programmation asynchrone avec async et await.

Étapes suivantes