Tutoriel : Implémenter la fonctionnalité CRUD - ASP.NET MVC avec EF CoreTutorial: Implement CRUD Functionality - ASP.NET MVC with EF Core

Dans le didacticiel précédent, vous avez créé une application MVC qui stocke et affiche les données en utilisant Entity Framework et SQL Server LocalDB.In the previous tutorial, you created an MVC application that stores and displays data using the Entity Framework and SQL Server LocalDB. Dans ce didacticiel, vous allez examiner et personnaliser le code CRUD (créer, lire, mettre à jour, supprimer) que la génération de modèles automatique MVC a créé automatiquement pour vous dans des contrôleurs et des vues.In this tutorial, you'll review and customize the CRUD (create, read, update, delete) code that the MVC scaffolding automatically creates for you in controllers and views.

Notes

Il est courant d’implémenter le modèle de référentiel pour créer une couche d’abstraction entre votre contrôleur et la couche d’accès aux données.It's a common practice to implement the repository pattern in order to create an abstraction layer between your controller and the data access layer. Pour conserver ces didacticiels simples et orientés vers l’apprentissage de l’utilisation d’Entity Framework proprement dit, ils n’utilisent pas de référentiels.To keep these tutorials simple and focused on teaching how to use the Entity Framework itself, they don't use repositories. Pour plus d’informations sur les référentiels avec EF, consultez le dernier didacticiel de cette série.For information about repositories with EF, see the last tutorial in this series.

Dans ce didacticiel, vous avez effectué les actions suivantes :In this tutorial, you:

  • Personnaliser la page DetailsCustomize the Details page
  • Mettre à jour la page CreateUpdate the Create page
  • Mettre à jour la page EditUpdate the Edit page
  • Mettre à jour la page DeleteUpdate the Delete page
  • Fermer les connexions de base de donnéesClose database connections

PrérequisPrerequisites

Personnaliser la page DetailsCustomize the Details page

Le code du modèle généré automatiquement pour la page Index des étudiants exclut la propriété Enrollments, car elle contient une collection.The scaffolded code for the Students Index page left out the Enrollments property, because that property holds a collection. Dans la page Details, vous affichez le contenu de la collection dans un tableau HTML.In the Details page, you'll display the contents of the collection in an HTML table.

Dans Controllers/StudentsController.cs, la méthode d’action pour la vue Details utilise la méthode SingleOrDefaultAsync pour récupérer une seule entité Student.In Controllers/StudentsController.cs, the action method for the Details view uses the SingleOrDefaultAsync method to retrieve a single Student entity. Ajoutez du code qui appelle Include.Add code that calls Include. Les méthodes ThenInclude et AsNoTracking, comme indiqué dans le code en surbrillance suivant.ThenInclude, and AsNoTracking methods, as shown in the following highlighted code.

public async Task<IActionResult> Details(int? id)
{
    if (id == null)
    {
        return NotFound();
    }

    var student = await _context.Students
        .Include(s => s.Enrollments)
            .ThenInclude(e => e.Course)
        .AsNoTracking()
        .FirstOrDefaultAsync(m => m.ID == id);

    if (student == null)
    {
        return NotFound();
    }

    return View(student);
}

Les méthodes Include et ThenInclude font que le contexte charge la propriété de navigation Student.Enrollments et dans chaque inscription, la propriété de navigation Enrollment.Course.The Include and ThenInclude methods cause the context to load the Student.Enrollments navigation property, and within each enrollment the Enrollment.Course navigation property. Vous découvrirez plus d’informations sur ces méthodes dans le tutoriel sur la lecture des données associées.You'll learn more about these methods in the read related data tutorial.

La méthode AsNoTracking améliore les performances dans les scénarios où les entités retournées ne sont pas mises à jour pendant la durée de vie du contexte actif.The AsNoTracking method improves performance in scenarios where the entities returned won't be updated in the current context's lifetime. Vous pouvez découvrir plus d’informations sur AsNoTracking à la fin de ce didacticiel.You'll learn more about AsNoTracking at the end of this tutorial.

Données de routeRoute data

La valeur de clé qui est passée à la méthode Details provient des données de route.The key value that's passed to the Details method comes from route data. Les données de route sont des données que le classeur de modèles a trouvées dans un segment de l’URL.Route data is data that the model binder found in a segment of the URL. Par exemple, la route par défaut spécifie les segments contrôleur, action et ID :For example, the default route specifies controller, action, and id segments:

app.UseMvc(routes =>
{
    routes.MapRoute(
        name: "default",
        template: "{controller=Home}/{action=Index}/{id?}");
});

Dans l’URL suivante, la route par défaut mappe Instructor en tant que contrôleur, Index en tant qu’action et 1 en tant qu’ID ; il s’agit des valeurs des données de route.In the following URL, the default route maps Instructor as the controller, Index as the action, and 1 as the id; these are route data values.

http://localhost:1230/Instructor/Index/1?courseID=2021

La dernière partie de l’URL (« ?courseID=2021 ») est une valeur de chaîne de requête.The last part of the URL ("?courseID=2021") is a query string value. Le classeur de modèles passe aussi la valeur d’ID au paramètre id de la méthode Index si vous le passez en tant que valeur de chaîne de requête :The model binder will also pass the ID value to the Index method id parameter if you pass it as a query string value:

http://localhost:1230/Instructor/Index?id=1&CourseID=2021

Dans la page Index, les URL des liens hypertexte sont créées par des instructions Tag Helper dans la vue Razor.In the Index page, hyperlink URLs are created by tag helper statements in the Razor view. Dans le code Razor suivant, le paramètre id correspond à la route par défaut : id est donc ajouté aux données de route.In the following Razor code, the id parameter matches the default route, so id is added to the route data.

<a asp-action="Edit" asp-route-id="@item.ID">Edit</a>

Ceci génère le code HTML suivant quand item.ID vaut 6 :This generates the following HTML when item.ID is 6:

<a href="/Students/Edit/6">Edit</a>

Dans le code Razor suivant, studentID ne correspond pas à un paramètre dans la route par défaut : il est donc ajouté en tant que chaîne de requête.In the following Razor code, studentID doesn't match a parameter in the default route, so it's added as a query string.

<a asp-action="Edit" asp-route-studentID="@item.ID">Edit</a>

Ceci génère le code HTML suivant quand item.ID vaut 6 :This generates the following HTML when item.ID is 6:

<a href="/Students/Edit?studentID=6">Edit</a>

Pour plus d’informations sur les Tag Helpers, consultez Tag Helpers dans ASP.NET Core.For more information about tag helpers, see Tag Helpers dans ASP.NET Core.

Ajouter des inscriptions à la vue DetailsAdd enrollments to the Details view

Ouvrez Views/Students/Details.cshtml.Open Views/Students/Details.cshtml. Chaque champ est affiché avec les helpers DisplayNameFor et DisplayFor, comme montré dans l’exemple suivant :Each field is displayed using DisplayNameFor and DisplayFor helpers, as shown in the following example:

<dt class="col-sm-2">
    @Html.DisplayNameFor(model => model.LastName)
</dt>
<dd class="col-sm-10">
    @Html.DisplayFor(model => model.LastName)
</dd>

Après le dernier champ et immédiatement avant la balise de fermeture </dl>, ajoutez le code suivant pour afficher une liste d’inscriptions :After the last field and immediately before the closing </dl> tag, add the following code to display a list of enrollments:

<dt class="col-sm-2">
    @Html.DisplayNameFor(model => model.Enrollments)
</dt>
<dd class="col-sm-10">
    <table class="table">
        <tr>
            <th>Course Title</th>
            <th>Grade</th>
        </tr>
        @foreach (var item in Model.Enrollments)
        {
            <tr>
                <td>
                    @Html.DisplayFor(modelItem => item.Course.Title)
                </td>
                <td>
                    @Html.DisplayFor(modelItem => item.Grade)
                </td>
            </tr>
        }
    </table>
</dd>

Si l’indentation du code est incorrecte une fois le code collé, appuyez sur Ctrl-K-D pour la corriger.If code indentation is wrong after you paste the code, press CTRL-K-D to correct it.

Ce code parcourt en boucle les entités dans la propriété de navigation Enrollments.This code loops through the entities in the Enrollments navigation property. Pour chaque inscription, il affiche le titre du cours et la note.For each enrollment, it displays the course title and the grade. Le titre du cours est récupéré à partir de l’entité de cours qui est stockée dans la propriété de navigation Course de l’entité Enrollments.The course title is retrieved from the Course entity that's stored in the Course navigation property of the Enrollments entity.

Exécutez l’application, sélectionnez l’onglet Students, puis cliquez sur le lien Details pour un étudiant.Run the app, select the Students tab, and click the Details link for a student. Vous voyez la liste des cours et des notes de l’étudiant sélectionné :You see the list of courses and grades for the selected student:

Page Details pour les étudiants

Mettre à jour la page CreateUpdate the Create page

Dans StudentsController.cs, modifiez la méthode HttpPost Create en ajoutant un bloc try-catch et en supprimant l’ID de l’attribut Bind.In StudentsController.cs, modify the HttpPost Create method by adding a try-catch block and removing ID from the Bind attribute.

[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Create(
    [Bind("EnrollmentDate,FirstMidName,LastName")] Student student)
{
    try
    {
        if (ModelState.IsValid)
        {
            _context.Add(student);
            await _context.SaveChangesAsync();
            return RedirectToAction(nameof(Index));
        }
    }
    catch (DbUpdateException /* ex */)
    {
        //Log the error (uncomment ex variable name and write a log.
        ModelState.AddModelError("", "Unable to save changes. " +
            "Try again, and if the problem persists " +
            "see your system administrator.");
    }
    return View(student);
}

Ce code ajoute l’entité Student créée par le classeur de modèles ASP.NET Core MVC au jeu d’entités Students, puis enregistre les modifications dans la base de données.This code adds the Student entity created by the ASP.NET Core MVC model binder to the Students entity set and then saves the changes to the database. (Le classeur de modèles référence la fonctionnalité d’ASP.NET Core MVC qui facilite l’utilisation des données envoyées par un formulaire ; un classeur de modèles convertit les valeurs de formulaire envoyées en types CLR et les passe à la méthode d’action dans des paramètres.(Model binder refers to the ASP.NET Core MVC functionality that makes it easier for you to work with data submitted by a form; a model binder converts posted form values to CLR types and passes them to the action method in parameters. Dans ce cas, le classeur de modèles instancie une entité Student pour vous avec des valeurs de propriété provenant de la collection Form.)In this case, the model binder instantiates a Student entity for you using property values from the Form collection.)

Vous avez supprimé ID de l’attribut Bind, car ID est la valeur de clé primaire définie automatiquement par SQL Server lors de l’insertion de la ligne.You removed ID from the Bind attribute because ID is the primary key value which SQL Server will set automatically when the row is inserted. L’entrée de l’utilisateur ne définit pas la valeur de l’ID.Input from the user doesn't set the ID value.

À part l’attribut Bind, le bloc try-catch est la seule modification que vous avez apportée au code du modèle généré automatiquement.Other than the Bind attribute, the try-catch block is the only change you've made to the scaffolded code. Si une exception qui dérive de DbUpdateException est interceptée lors de l’enregistrement des modifications, un message d’erreur générique est affiché.If an exception that derives from DbUpdateException is caught while the changes are being saved, a generic error message is displayed. Les exceptions DbUpdateException sont parfois dues à quelque chose d’externe à l’application et non pas à une erreur de programmation : il est donc conseillé à l’utilisateur de réessayer.DbUpdateException exceptions are sometimes caused by something external to the application rather than a programming error, so the user is advised to try again. Bien que ceci ne soit pas implémenté dans cet exemple, une application destinée à la production doit consigner l’exception.Although not implemented in this sample, a production quality application would log the exception. Pour plus d’informations, consultez la section Journal pour obtenir un aperçu de Surveillance et télémétrie (génération d’applications Cloud du monde réel avec Azure).For more information, see the Log for insight section in Monitoring and Telemetry (Building Real-World Cloud Apps with Azure).

L’attribut ValidateAntiForgeryToken aide à éviter les attaques par falsification de requête intersites (CSRF, Cross-Site Request Forgery).The ValidateAntiForgeryToken attribute helps prevent cross-site request forgery (CSRF) attacks. Le jeton est automatiquement injecté dans la vue par le FormTagHelper et est inclus quand le formulaire est envoyé par l’utilisateur.The token is automatically injected into the view by the FormTagHelper and is included when the form is submitted by the user. Le jeton est validé par l’attribut ValidateAntiForgeryToken.The token is validated by the ValidateAntiForgeryToken attribute. Pour plus d’informations sur la falsification de requête intersites, consultez Protection contre la falsification de requête.For more information about CSRF, see Anti-Request Forgery.

Remarque sur la sécurité concernant la survalidationSecurity note about overposting

L’attribut Bind inclus dans le code du modèle généré automatiquement sur la méthode Create est un moyen de protéger contre la survalidation dans les scénarios de création.The Bind attribute that the scaffolded code includes on the Create method is one way to protect against overposting in create scenarios. Par exemple, supposons que l’entité Student comprend une propriété Secret et que vous ne voulez pas que cette page web définisse sa valeur.For example, suppose the Student entity includes a Secret property that you don't want this web page to set.

public class Student
{
    public int ID { get; set; }
    public string LastName { get; set; }
    public string FirstMidName { get; set; }
    public DateTime EnrollmentDate { get; set; }
    public string Secret { get; set; }
}

Même si vous n’avez pas de champ Secret dans la page web, un hacker pourrait utiliser un outil comme Fiddler, ou écrire du JavaScript, pour envoyer une valeur de formulaire pour Secret.Even if you don't have a Secret field on the web page, a hacker could use a tool such as Fiddler, or write some JavaScript, to post a Secret form value. Sans l’attribut Bind limitant les champs utilisés par le classeur de modèles quand il crée une instance de Student, le classeur de modèles choisit la valeur de formulaire pour Secret et l’utilise pour créer l’instance de l’entité Student.Without the Bind attribute limiting the fields that the model binder uses when it creates a Student instance, the model binder would pick up that Secret form value and use it to create the Student entity instance. Ensuite, la valeur spécifiée par le hacker pour le champ de formulaire Secret, quelle qu’elle soit, est mise à jour dans la base de données.Then whatever value the hacker specified for the Secret form field would be updated in your database. L’illustration suivante montre l’outil Fiddler en train d’ajouter le champ Secret (avec la valeur « OverPost ») aux valeurs du formulaire envoyé.The following image shows the Fiddler tool adding the Secret field (with the value "OverPost") to the posted form values.

Fiddler ajoutant le champ Secret

La valeur « OverPost » serait correctement ajoutée à la propriété Secret de la ligne insérée, même si vous n’aviez jamais prévu que la page web puisse définir cette propriété.The value "OverPost" would then be successfully added to the Secret property of the inserted row, although you never intended that the web page be able to set that property.

Vous pouvez empêcher la survalidation dans les scénarios de modification en lisant d’abord l’entité à partir de la base de données, puis en appelant TryUpdateModel, en passant une liste des propriétés explicitement autorisées.You can prevent overposting in edit scenarios by reading the entity from the database first and then calling TryUpdateModel, passing in an explicit allowed properties list. Il s’agit de la méthode utilisée dans ces didacticiels.That's the method used in these tutorials.

Une autre façon d’empêcher la survalidation qui est préférée par de nombreux développeurs consiste à utiliser les afficher des modèles de vues au lieu de classes d’entités avec la liaison de modèle.An alternative way to prevent overposting that's preferred by many developers is to use view models rather than entity classes with model binding. Incluez seulement les propriétés que vous voulez mettre à jour dans le modèle de vue.Include only the properties you want to update in the view model. Une fois le classeur de modèles MVC a terminé, copiez les propriétés du modèle de vue vers l’instance de l’entité, en utilisant si vous le souhaitez un outil comme AutoMapper.Once the MVC model binder has finished, copy the view model properties to the entity instance, optionally using a tool such as AutoMapper. Utilisez _context.Entry sur l’instance de l’entité pour définir son état sur Unchanged, puis définissez Property("PropertyName").IsModified sur true sur chaque propriété d’entité qui est incluse dans le modèle de vue.Use _context.Entry on the entity instance to set its state to Unchanged, and then set Property("PropertyName").IsModified to true on each entity property that's included in the view model. Cette méthode fonctionne à la fois dans les scénarios de modification et de création.This method works in both edit and create scenarios.

Tester la page CreateTest the Create page

Le code dans Views/Students/Create.cshtml utilise les tag helpers label, input et span (pour les messages de validation) pour chaque champ.The code in Views/Students/Create.cshtml uses label, input, and span (for validation messages) tag helpers for each field.

Exécutez l’application, sélectionnez l’onglet Students, puis cliquez sur Create New.Run the app, select the Students tab, and click Create New.

Entrez des noms et une date.Enter names and a date. Si votre navigateur vous le permet, essayez d’entrer une date non valide.Try entering an invalid date if your browser lets you do that. (Certains navigateurs vous obligent à utiliser un sélecteur de dates.) Cliquez ensuite sur Create pour voir le message d’erreur.(Some browsers force you to use a date picker.) Then click Create to see the error message.

Erreur de validation de date

Il s’agit de la validation côté serveur que vous obtenez par défaut ; dans un didacticiel suivant, vous verrez comment ajouter des attributs qui génèrent du code également pour la validation côté client.This is server-side validation that you get by default; in a later tutorial you'll see how to add attributes that will generate code for client-side validation also. Le code en surbrillance suivant montre la vérification de validation du modèle dans la méthode Create.The following highlighted code shows the model validation check in the Create method.

[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Create(
    [Bind("EnrollmentDate,FirstMidName,LastName")] Student student)
{
    try
    {
        if (ModelState.IsValid)
        {
            _context.Add(student);
            await _context.SaveChangesAsync();
            return RedirectToAction(nameof(Index));
        }
    }
    catch (DbUpdateException /* ex */)
    {
        //Log the error (uncomment ex variable name and write a log.
        ModelState.AddModelError("", "Unable to save changes. " +
            "Try again, and if the problem persists " +
            "see your system administrator.");
    }
    return View(student);
}

Changez la date en une valeur valide, puis cliquez sur Create pour voir apparaître le nouvel étudiant dans la page Index.Change the date to a valid value and click Create to see the new student appear in the Index page.

Mettre à jour la page EditUpdate the Edit page

Dans StudentController.cs, la méthode HttpGet Edit (celle sans l’attribut HttpPost) utilise la méthode SingleOrDefaultAsync pour récupérer l’entité Student sélectionnée, comme vous l’avez vu dans la méthode Details.In StudentController.cs, the HttpGet Edit method (the one without the HttpPost attribute) uses the SingleOrDefaultAsync method to retrieve the selected Student entity, as you saw in the Details method. Vous n’avez pas besoin de modifier cette méthode.You don't need to change this method.

Remplacez la méthode d’action HttpPost Edit par le code suivant.Replace the HttpPost Edit action method with the following code.

[HttpPost, ActionName("Edit")]
[ValidateAntiForgeryToken]
public async Task<IActionResult> EditPost(int? id)
{
    if (id == null)
    {
        return NotFound();
    }
    var studentToUpdate = await _context.Students.FirstOrDefaultAsync(s => s.ID == id);
    if (await TryUpdateModelAsync<Student>(
        studentToUpdate,
        "",
        s => s.FirstMidName, s => s.LastName, s => s.EnrollmentDate))
    {
        try
        {
            await _context.SaveChangesAsync();
            return RedirectToAction(nameof(Index));
        }
        catch (DbUpdateException /* ex */)
        {
            //Log the error (uncomment ex variable name and write a log.)
            ModelState.AddModelError("", "Unable to save changes. " +
                "Try again, and if the problem persists, " +
                "see your system administrator.");
        }
    }
    return View(studentToUpdate);
}

Ces modifications implémentent une bonne pratique de sécurité pour empêcher la survalidation.These changes implement a security best practice to prevent overposting. Le générateur de modèles automatique a généré un attribut Bind et a ajouté l’entité créée par le classeur de modèles au jeu d’entités avec un indicateur Modified.The scaffolder generated a Bind attribute and added the entity created by the model binder to the entity set with a Modified flag. Ce code n’est pas recommandé dans de nombreux scénarios, car l’attribut Bind efface toutes les données préexistantes dans les champs non répertoriés dans le paramètre Include.That code isn't recommended for many scenarios because the Bind attribute clears out any pre-existing data in fields not listed in the Include parameter.

Le nouveau code lit l’entité existante et appelle TryUpdateModel pour mettre à jour les champs dans l’entité récupérée en fonction de l’entrée d’utilisateur dans les données du formulaire envoyé.The new code reads the existing entity and calls TryUpdateModel to update fields in the retrieved entity based on user input in the posted form data. Le suivi automatique des modifications d’Entity Framework définit l’indicateur Modified sur les champs qui sont modifiés via une entrée dans le formulaire.The Entity Framework's automatic change tracking sets the Modified flag on the fields that are changed by form input. Quand la méthode SaveChanges est appelée, Entity Framework crée des instructions SQL pour mettre à jour la ligne de la base de données.When the SaveChanges method is called, the Entity Framework creates SQL statements to update the database row. Les conflits d’accès concurrentiel sont ignorés, et seules les colonnes de table qui ont été mises à jour par l’utilisateur sont mises à jour dans la base de données.Concurrency conflicts are ignored, and only the table columns that were updated by the user are updated in the database. (Un didacticiel suivant montre comment gérer les conflits d’accès concurrentiel.)(A later tutorial shows how to handle concurrency conflicts.)

Au titre de bonne pratique pour empêcher la survalidation, les champs dont vous voulez qu’ils puissent être mis à jour par la page Edit sont placés en liste verte dans les paramètres de TryUpdateModel.As a best practice to prevent overposting, the fields that you want to be updateable by the Edit page are whitelisted in the TryUpdateModel parameters. (La chaîne vide précédant la liste de champs dans la liste de paramètres est pour un préfixe à utiliser avec les noms des champs du formulaire.) Actuellement, vous ne protégez aucun champ supplémentaire, mais le fait de répertorier les champs que vous voulez que le classeur de modèles lie garantit que si vous ajoutez ultérieurement des champs au modèle de données, ils seront automatiquement protégés jusqu’à ce que vous les ajoutiez explicitement ici.(The empty string preceding the list of fields in the parameter list is for a prefix to use with the form fields names.) Currently there are no extra fields that you're protecting, but listing the fields that you want the model binder to bind ensures that if you add fields to the data model in the future, they're automatically protected until you explicitly add them here.

À la suite de ces modifications, la signature de méthode de la méthode HttpPost Edit est la même que celle de la méthode HttpGet Edit ; par conséquent, vous avez renommé la méthode EditPost.As a result of these changes, the method signature of the HttpPost Edit method is the same as the HttpGet Edit method; therefore you've renamed the method EditPost.

Autre possibilité pour le code HttpPost Edit : Créer et attacherAlternative HttpPost Edit code: Create and attach

Le code de HttpPost Edit recommandé garantit que seules les colonnes modifiées sont mises à jour et conserve les données dans les propriétés que vous ne voulez pas inclure pour la liaison de modèle.The recommended HttpPost edit code ensures that only changed columns get updated and preserves data in properties that you don't want included for model binding. Cependant, l’approche « lecture en premier » nécessite une lecture supplémentaire de la base de données et peut aboutir à un code plus complexe pour la gestion des conflits d’accès concurrentiel.However, the read-first approach requires an extra database read, and can result in more complex code for handling concurrency conflicts. Une alternative consiste à attacher une entité créée par le classeur de modèles au contexte EF et à la marquer comme étant modifiée.An alternative is to attach an entity created by the model binder to the EF context and mark it as modified. (Ne mettez pas à jour votre projet avec ce code, il figure ici seulement pour illustrer une approche facultative.)(Don't update your project with this code, it's only shown to illustrate an optional approach.)

public async Task<IActionResult> Edit(int id, [Bind("ID,EnrollmentDate,FirstMidName,LastName")] Student student)
{
    if (id != student.ID)
    {
        return NotFound();
    }
    if (ModelState.IsValid)
    {
        try
        {
            _context.Update(student);
            await _context.SaveChangesAsync();
            return RedirectToAction(nameof(Index));
        }
        catch (DbUpdateException /* ex */)
        {
            //Log the error (uncomment ex variable name and write a log.)
            ModelState.AddModelError("", "Unable to save changes. " +
                "Try again, and if the problem persists, " +
                "see your system administrator.");
        }
    }
    return View(student);
}

Vous pouvez utiliser cette approche quand l’interface utilisateur de la page web inclut tous les champs de l’entité et peut les mettre à jour.You can use this approach when the web page UI includes all of the fields in the entity and can update any of them.

Le code du modèle généré automatiquement utilise l’approche « créer et attacher », mais il intercepte seulement les exceptions DbUpdateConcurrencyException et retourne des codes d’erreur 404.The scaffolded code uses the create-and-attach approach but only catches DbUpdateConcurrencyException exceptions and returns 404 error codes. L’exemple suivant intercepte toutes les exceptions de mise à jour de la base de données et affiche un message d’erreur.The example shown catches any database update exception and displays an error message.

États des entitésEntity States

Le contexte de base de données effectue le suivi de la synchronisation ou non des entités en mémoire avec leurs lignes correspondantes dans la base de données, et ces informations déterminent ce qui se passe quand vous appelez la méthode SaveChanges.The database context keeps track of whether entities in memory are in sync with their corresponding rows in the database, and this information determines what happens when you call the SaveChanges method. Par exemple, quand vous passez une nouvelle entité à la méthode Add, l’état de cette entité est défini sur Added.For example, when you pass a new entity to the Add method, that entity's state is set to Added. Ensuite, quand vous appelez la méthode SaveChanges, le contexte de base de données émet une commande SQL INSERT.Then when you call the SaveChanges method, the database context issues a SQL INSERT command.

Une entité peut être dans un des états suivants :An entity may be in one of the following states:

  • Added.Added. L’entité n’existe pas encore dans la base de données.The entity doesn't yet exist in the database. La méthode SaveChanges émet une instruction INSERT.The SaveChanges method issues an INSERT statement.

  • Unchanged.Unchanged. La méthode SaveChanges ne doit rien faire avec cette entité.Nothing needs to be done with this entity by the SaveChanges method. Quand vous lisez une entité dans la base de données, l’entité a d’abord cet état.When you read an entity from the database, the entity starts out with this status.

  • Modified.Modified. Tout ou partie des valeurs de propriété de l’entité ont été modifiées.Some or all of the entity's property values have been modified. La méthode SaveChanges émet une instruction UPDATE.The SaveChanges method issues an UPDATE statement.

  • Deleted.Deleted. L’entité a été marquée pour suppression.The entity has been marked for deletion. La méthode SaveChanges émet une instruction DELETE.The SaveChanges method issues a DELETE statement.

  • Detached.Detached. L’entité n’est pas suivie par le contexte de base de données.The entity isn't being tracked by the database context.

Dans une application de poste de travail, les changements d’état sont généralement définis automatiquement.In a desktop application, state changes are typically set automatically. Vous lisez une entité et vous apportez des modifications à certaines de ses valeurs de propriété.You read an entity and make changes to some of its property values. Son état passe alors automatiquement à Modified.This causes its entity state to automatically be changed to Modified. Quand vous appelez SaveChanges, Entity Framework génère une instruction SQL UPDATE qui met à jour seulement les propriétés que vous avez modifiées.Then when you call SaveChanges, the Entity Framework generates a SQL UPDATE statement that updates only the actual properties that you changed.

Dans une application web, le DbContext qui lit initialement une entité et affiche ses données pour permettre leur modification est supprimé après le rendu d’une page.In a web app, the DbContext that initially reads an entity and displays its data to be edited is disposed after a page is rendered. Quand la méthode d’action HttpPost Edit est appelée, une nouvelle requête web est effectuée et vous disposez d’une nouvelle instance de DbContext.When the HttpPost Edit action method is called, a new web request is made and you have a new instance of the DbContext. Si vous relisez l’entité dans ce nouveau contexte, vous simulez le traitement du poste de travail.If you re-read the entity in that new context, you simulate desktop processing.

Mais si vous ne voulez pas effectuer l’opération de lecture supplémentaire, vous devez utiliser l’objet entité créé par le classeur de modèles.But if you don't want to do the extra read operation, you have to use the entity object created by the model binder. Le moyen le plus simple consiste à définir l’état de l’entité en Modified, comme cela est fait dans l’alternative pour le code HttpPost Edit illustrée précédemment.The simplest way to do this is to set the entity state to Modified as is done in the alternative HttpPost Edit code shown earlier. Ensuite, quand vous appelez SaveChanges, Entity Framework met à jour toutes les colonnes de la ligne de la base de données, car le contexte n’a aucun moyen de savoir quelles propriétés vous avez modifiées.Then when you call SaveChanges, the Entity Framework updates all columns of the database row, because the context has no way to know which properties you changed.

Si vous voulez éviter l’approche « lecture en premier », mais que vous voulez aussi que l’instruction SQL UPDATE mette à jour seulement les champs que l’utilisateur a réellement changés, le code est plus complexe.If you want to avoid the read-first approach, but you also want the SQL UPDATE statement to update only the fields that the user actually changed, the code is more complex. Vous devez enregistrer les valeurs d’origine d’une façon ou d’une autre (par exemple en utilisant des champs masqués) afin qu’ils soient disponibles quand la méthode HttpPost Edit est appelée.You have to save the original values in some way (such as by using hidden fields) so that they're available when the HttpPost Edit method is called. Vous pouvez ensuite créer une entité Student en utilisant les valeurs d’origine, appeler la méthode Attach avec cette version d’origine de l’entité, mettre à jour les valeurs de l’entité avec les nouvelles valeurs, puis appeler SaveChanges.Then you can create a Student entity using the original values, call the Attach method with that original version of the entity, update the entity's values to the new values, and then call SaveChanges.

Tester la page EditTest the Edit page

Exécutez l’application, sélectionnez l’onglet Students, puis cliquez sur un lien hypertexte Edit.Run the app, select the Students tab, then click an Edit hyperlink.

Page de modification des étudiants

Changez quelques données et cliquez sur Save.Change some of the data and click Save. La page Index s’ouvre et affiche les données modifiées.The Index page opens and you see the changed data.

Mettre à jour la page DeleteUpdate the Delete page

Dans StudentController.cs, le modèle de code pour la méthode HttpGet Delete utilise la méthode SingleOrDefaultAsync pour récupérer l’entité Student sélectionnée, comme vous l’avez vu dans les méthodes Details et Edit.In StudentController.cs, the template code for the HttpGet Delete method uses the SingleOrDefaultAsync method to retrieve the selected Student entity, as you saw in the Details and Edit methods. Cependant, pour implémenter un message d’erreur personnalisé quand l’appel à SaveChanges échoue, vous devez ajouter des fonctionnalités à cette méthode et à sa vue correspondante.However, to implement a custom error message when the call to SaveChanges fails, you'll add some functionality to this method and its corresponding view.

Comme vous l’avez vu pour les opérations de mise à jour et de création, les opérations de suppression nécessitent deux méthodes d’action.As you saw for update and create operations, delete operations require two action methods. La méthode qui est appelée en réponse à une demande GET affiche une vue qui permet à l’utilisateur d’approuver ou d’annuler l’opération de suppression.The method that's called in response to a GET request displays a view that gives the user a chance to approve or cancel the delete operation. Si l’utilisateur l’approuve, une demande POST est créée.If the user approves it, a POST request is created. Quand cela se produit, la méthode HttpPost Delete est appelée, puis cette méthode effectue ensuite l’opération de suppression.When that happens, the HttpPost Delete method is called and then that method actually performs the delete operation.

Vous allez ajouter un bloc try-catch à la méthode HttpPost Delete pour gérer les erreurs qui peuvent se produire quand la base de données est mise à jour.You'll add a try-catch block to the HttpPost Delete method to handle any errors that might occur when the database is updated. Si une erreur se produit, la méthode HttpPost Delete appelle la méthode HttpGet Delete, en lui passant un paramètre qui indique qu’une erreur s’est produite.If an error occurs, the HttpPost Delete method calls the HttpGet Delete method, passing it a parameter that indicates that an error has occurred. La méthode HttpGet Delete réaffiche ensuite la page de confirmation, ainsi que le message d’erreur, donnant à l’utilisateur la possibilité d’annuler ou de recommencer.The HttpGet Delete method then redisplays the confirmation page along with the error message, giving the user an opportunity to cancel or try again.

Remplacez la méthode d’action HttpGet Delete par le code suivant, qui gère le signalement des erreurs.Replace the HttpGet Delete action method with the following code, which manages error reporting.

public async Task<IActionResult> Delete(int? id, bool? saveChangesError = false)
{
    if (id == null)
    {
        return NotFound();
    }

    var student = await _context.Students
        .AsNoTracking()
        .FirstOrDefaultAsync(m => m.ID == id);
    if (student == null)
    {
        return NotFound();
    }

    if (saveChangesError.GetValueOrDefault())
    {
        ViewData["ErrorMessage"] =
            "Delete failed. Try again, and if the problem persists " +
            "see your system administrator.";
    }

    return View(student);
}

Ce code accepte un paramètre facultatif qui indique si la méthode a été appelée après une erreur d’enregistrement des modifications.This code accepts an optional parameter that indicates whether the method was called after a failure to save changes. Ce paramètre a la valeur false quand la méthode HttpGet Delete est appelée sans une erreur antérieure.This parameter is false when the HttpGet Delete method is called without a previous failure. Quand elle est appelée par la méthode HttpPost Delete en réponse à une erreur de mise à jour de la base de données, le paramètre a la valeur true et un message d’erreur est passé à la vue.When it's called by the HttpPost Delete method in response to a database update error, the parameter is true and an error message is passed to the view.

L’approche « lecture en premier » pour HttpPost DeleteThe read-first approach to HttpPost Delete

Remplacez la méthode d’action HttpPost Delete (nommée DeleteConfirmed) par le code suivant, qui effectue l’opération de suppression réelle et intercepte les erreurs de mise à jour de la base de données.Replace the HttpPost Delete action method (named DeleteConfirmed) with the following code, which performs the actual delete operation and catches any database update errors.

[HttpPost, ActionName("Delete")]
[ValidateAntiForgeryToken]
public async Task<IActionResult> DeleteConfirmed(int id)
{
    var student = await _context.Students.FindAsync(id);
    if (student == null)
    {
        return RedirectToAction(nameof(Index));
    }

    try
    {
        _context.Students.Remove(student);
        await _context.SaveChangesAsync();
        return RedirectToAction(nameof(Index));
    }
    catch (DbUpdateException /* ex */)
    {
        //Log the error (uncomment ex variable name and write a log.)
        return RedirectToAction(nameof(Delete), new { id = id, saveChangesError = true });
    }
}

Ce code récupère l’entité sélectionnée, puis appelle la méthode Remove pour définir l’état de l’entité sur Deleted.This code retrieves the selected entity, then calls the Remove method to set the entity's status to Deleted. Quand SaveChanges est appelée, une commande SQL DELETE est générée.When SaveChanges is called, a SQL DELETE command is generated.

L’approche « créer et attacher » pour HttpPost DeleteThe create-and-attach approach to HttpPost Delete

Si l’amélioration des performances dans une application traitant des volumes importants est une priorité, vous pouvez éviter une requête SQL inutile en instanciant une entité de Student en utilisant seulement la valeur de la clé primaire, puis en définissant l’état de l’entité sur Deleted.If improving performance in a high-volume application is a priority, you could avoid an unnecessary SQL query by instantiating a Student entity using only the primary key value and then setting the entity state to Deleted. C’est tout ce dont a besoin Entity Framework pour pouvoir supprimer l’entité.That's all that the Entity Framework needs in order to delete the entity. (Ne placez pas de ce code dans votre projet ; il figure ici seulement pour illustrer une solution alternative.)(Don't put this code in your project; it's here just to illustrate an alternative.)

[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> DeleteConfirmed(int id)
{
    try
    {
        Student studentToDelete = new Student() { ID = id };
        _context.Entry(studentToDelete).State = EntityState.Deleted;
        await _context.SaveChangesAsync();
        return RedirectToAction(nameof(Index));
    }
    catch (DbUpdateException /* ex */)
    {
        //Log the error (uncomment ex variable name and write a log.)
        return RedirectToAction(nameof(Delete), new { id = id, saveChangesError = true });
    }
}

Si l’entité a des données connexes qui doivent également être supprimées, vérifiez que la suppression en cascade est configurée dans la base de données.If the entity has related data that should also be deleted, make sure that cascade delete is configured in the database. Avec cette approche pour la suppression de l’entité, EF peut ne pas réaliser que des entités connexes doivent être supprimées.With this approach to entity deletion, EF might not realize there are related entities to be deleted.

Mettre à jour la vue DeleteUpdate the Delete view

Dans Views/Student/Delete.cshtml, ajoutez un message d’erreur entre le titre h2 et le titre h3, comme indiqué dans l’exemple suivant :In Views/Student/Delete.cshtml, add an error message between the h2 heading and the h3 heading, as shown in the following example:

<h2>Delete</h2>
<p class="text-danger">@ViewData["ErrorMessage"]</p>
<h3>Are you sure you want to delete this?</h3>

Exécutez l’application, sélectionnez l’onglet Students, puis cliquez sur un lien hypertexte Delete :Run the app, select the Students tab, and click a Delete hyperlink:

Page de confirmation de la suppression

Cliquez sur Delete.Click Delete. La page Index s’affiche sans l’étudiant supprimé.The Index page is displayed without the deleted student. (Vous verrez un exemple du code de gestion des erreurs en action dans le didacticiel sur l’accès concurrentiel.)(You'll see an example of the error handling code in action in the concurrency tutorial.)

Fermer les connexions de base de donnéesClose database connections

Pour libérer les ressources détenues par une connexion de base de données, l’instance du contexte doit être supprimée dès que possible quand vous en avez terminé avec celle-ci.To free up the resources that a database connection holds, the context instance must be disposed as soon as possible when you are done with it. L’injection de dépendances intégrée d’ASP.NET Core prend en charge cette tâche pour vous.The ASP.NET Core built-in dependency injection takes care of that task for you.

Dans Startup.cs, vous appelez la méthode d’extension AddDbContext pour provisionner la classe DbContext dans le conteneur d’injection de dépendances d’ASP.NET Core.In Startup.cs, you call the AddDbContext extension method to provision the DbContext class in the ASP.NET Core DI container. Cette méthode définit par défaut la durée de vie du service sur Scoped.That method sets the service lifetime to Scoped by default. Scoped signifie que la durée de vie de l’objet de contexte coïncide avec la durée de vie de la demande web, et que la méthode Dispose sera appelée automatiquement à la fin de la requête web.Scoped means the context object lifetime coincides with the web request life time, and the Dispose method will be called automatically at the end of the web request.

Gérer les transactionsHandle transactions

Par défaut, Entity Framework implémente implicitement les transactions.By default the Entity Framework implicitly implements transactions. Dans les scénarios où vous apportez des modifications à plusieurs lignes ou plusieurs tables, puis que appelez SaveChanges, Entity Framework garantit automatiquement que soit toutes vos modifications réussissent soit elles échouent toutes.In scenarios where you make changes to multiple rows or tables and then call SaveChanges, the Entity Framework automatically makes sure that either all of your changes succeed or they all fail. Si certaines modifications sont effectuées en premier puis qu’une erreur se produit, ces modifications sont automatiquement annulées.If some changes are done first and then an error happens, those changes are automatically rolled back. Pour les scénarios où vous avez besoin de plus de contrôle, par exemple si vous voulez inclure des opérations effectuées en dehors d’Entity Framework dans une transaction, consultez Transactions.For scenarios where you need more control -- for example, if you want to include operations done outside of Entity Framework in a transaction -- see Transactions.

Pas de suivi des requêtesNo-tracking queries

Quand un contexte de base de données récupère des lignes de table et crée des objets entité qui les représentent, par défaut, il effectue le suivi du fait que les entités en mémoire sont ou non synchronisées avec ce qui se trouve dans la base de données.When a database context retrieves table rows and creates entity objects that represent them, by default it keeps track of whether the entities in memory are in sync with what's in the database. Les données en mémoire agissent comme un cache et sont utilisées quand vous mettez à jour une entité.The data in memory acts as a cache and is used when you update an entity. Cette mise en cache est souvent inutile dans une application web, car les instances de contexte ont généralement une durée de vie courte (une instance est créée puis supprimée pour chaque requête) et le contexte qui lit une entité est généralement supprimé avant que cette entité soit réutilisée.This caching is often unnecessary in a web application because context instances are typically short-lived (a new one is created and disposed for each request) and the context that reads an entity is typically disposed before that entity is used again.

Vous pouvez désactiver le suivi des objets entité en mémoire en appelant la méthode AsNoTracking.You can disable tracking of entity objects in memory by calling the AsNoTracking method. Voici des scénarios classiques où vous voulez procéder ainsi :Typical scenarios in which you might want to do that include the following:

  • Pendant la durée de vie du contexte, vous n’avez besoin de mettre à jour aucune entité et il n’est pas nécessaire qu’EF charge automatiquement les propriétés de navigation avec les entités récupérées par des requêtes distinctes.During the context lifetime you don't need to update any entities, and you don't need EF to automatically load navigation properties with entities retrieved by separate queries. Ces conditions sont souvent rencontrées dans les méthodes d’action HttpGet d’un contrôleur.Frequently these conditions are met in a controller's HttpGet action methods.

  • Vous exécutez une requête qui récupère un gros volume de données, et seule une petite partie des données retournées sont mises à jour.You are running a query that retrieves a large volume of data, and only a small portion of the returned data will be updated. Il peut être plus efficace de désactiver le suivi pour la requête retournant un gros volume de données et d’exécuter une requête plus tard pour les quelques entités qui doivent être mises à jour.It may be more efficient to turn off tracking for the large query, and run a query later for the few entities that need to be updated.

  • Vous voulez attacher une entité pour pouvoir la mettre à jour, mais vous avez auparavant récupéré la même entité à d’autre fins.You want to attach an entity in order to update it, but earlier you retrieved the same entity for a different purpose. Comme l’entité est déjà suivie par le contexte de base de données, vous ne pouvez pas attacher l’entité que vous voulez modifier.Because the entity is already being tracked by the database context, you can't attach the entity that you want to change. Une façon de gérer cette situation est d’appeler AsNoTracking sur la requête précédente.One way to handle this situation is to call AsNoTracking on the earlier query.

Pour plus d’informations, consultez Suivi ou pas de suivi.For more information, see Tracking vs. No-Tracking.

Obtenir le codeGet the code

Télécharger ou afficher l’application complète.Download or view the completed application.

Étapes suivantesNext steps

Dans ce didacticiel, vous avez effectué les actions suivantes :In this tutorial, you:

  • Personnaliser la page DetailsCustomized the Details page
  • Mettre à jour la page CreateUpdated the Create page
  • Mettre à jour la page EditUpdated the Edit page
  • Mettre à jour la page DeleteUpdated the Delete page
  • Fermer les connexions de base de donnéesClosed database connections

Passez au tutoriel suivant pour découvrir comment développer les fonctionnalités de la page Index en ajoutant le tri, le filtrage et la pagination.Advance to the next tutorial to learn how to expand the functionality of the Index page by adding sorting, filtering, and paging.