Partage via


Tutoriel : En savoir plus sur les scénarios avancés - ASP.NET MVC avec EF Core

Dans le tutoriel précédent, vous avez mis en œuvre l'héritage de type « table par hiérarchie ». Ce tutoriel présente plusieurs rubriques qui sont utiles pour être informé lorsque vous allez au-delà des principes de base du développement d’applications web ASP.NET Core qui utilisent Entity Framework Core.

Dans ce tutoriel, vous allez :

  • Effectuer des requêtes SQL brutes
  • Appeler une requête pour retourner des entités
  • Appeler une requête pour retourner d’autres types
  • Appeler une requête de mise à jour
  • Examiner les requêtes SQL
  • Créer une couche d’abstraction
  • En savoir plus sur la détection automatique des modifications
  • En savoir plus sur le code source et les plans de développement EF Core
  • Découvrez comment utiliser LINQ dynamique pour simplifier le code

Conditions préalables

Effectuer des requêtes SQL brutes

L’un des avantages de l’utilisation de Entity Framework est qu’il évite de lier votre code trop étroitement à une méthode particulière de stockage de données. Pour ce faire, il génère des requêtes et des commandes SQL pour vous, ce qui vous libère également d’avoir à les écrire vous-même. Toutefois, il existe des scénarios exceptionnels lorsque vous devez exécuter des requêtes SQL spécifiques que vous avez créées manuellement. Pour ces scénarios, l’API Entity Framework Code First inclut des méthodes qui vous permettent de passer des commandes SQL directement à la base de données. Vous disposez des options suivantes dans EF Core 1.0 :

  • Utilisez la méthode DbSet.FromSql pour les requêtes qui retournent des types d’entités. Les objets renvoyés doivent être du type attendu par l’objet DbSet et ils sont automatiquement suivis par le contexte de base de données, sauf si vous désactivez le suivi.

  • Utilisez le Database.ExecuteSqlCommand pour les commandes non-requête.

Si vous devez exécuter une requête qui retourne des types qui ne sont pas des entités, vous pouvez utiliser ADO.NET avec la connexion de base de données fournie par EF. Les données retournées ne sont pas suivies par le contexte de base de données, même si vous utilisez cette méthode pour récupérer les types d’entités.

Comme c’est toujours le cas lorsque vous exécutez des commandes SQL dans une application web, vous devez prendre des précautions pour protéger votre site contre les attaques par injection SQL. Pour ce faire, utilisez des requêtes paramétrables pour vous assurer que les chaînes soumises par une page web ne peuvent pas être interprétées comme des commandes SQL. Dans ce tutoriel, vous allez utiliser des requêtes paramétrables lors de l’intégration de l’entrée utilisateur dans une requête.

Appeler une requête pour retourner des entités

La classe DbSet<TEntity> fournit une méthode que vous pouvez utiliser pour exécuter une requête qui retourne une entité de type TEntity. Pour voir comment cela fonctionne, vous allez modifier le code dans la méthode Details du contrôleur de service.

Dans DepartmentsController.cs, dans la méthode Details, remplacez le code qui récupère un département par un appel de la méthode FromSql, comme illustré dans le code surligné ci-dessous.

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

    string query = "SELECT * FROM Department WHERE DepartmentID = {0}";
    var department = await _context.Departments
        .FromSql(query, id)
        .Include(d => d.Administrator)
        .AsNoTracking()
        .FirstOrDefaultAsync();

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

    return View(department);
}

Pour vérifier que le nouveau code fonctionne correctement, sélectionnez l’onglet Services, puis Détails pour l’un des services.

Détails du service

Appeler une requête pour retourner d’autres types

Précédemment, vous avez créé une grille de statistiques d’étudiants pour la page About qui a montré le nombre d’étudiants pour chaque date d’inscription. Vous avez obtenu les données de l’ensemble d’entités Students (_context.Students) et utilisé LINQ pour projeter les résultats dans une liste d’objets de modèle d’affichage EnrollmentDateGroup. Supposons que vous souhaitez écrire le code SQL lui-même plutôt que d’utiliser LINQ. Pour ce faire, vous devez exécuter une requête SQL qui retourne quelque chose d’autre que des objets d’entité. Dans EF Core version 1.0, il est possible d’écrire du code ADO.NET et d’obtenir la connexion à la base de données à partir d’EF (Entity Framework).

Dans HomeController.cs, remplacez la méthode About par le code suivant :

public async Task<ActionResult> About()
{
    List<EnrollmentDateGroup> groups = new List<EnrollmentDateGroup>();
    var conn = _context.Database.GetDbConnection();
    try
    {
        await conn.OpenAsync();
        using (var command = conn.CreateCommand())
        {
            string query = "SELECT EnrollmentDate, COUNT(*) AS StudentCount "
                + "FROM Person "
                + "WHERE Discriminator = 'Student' "
                + "GROUP BY EnrollmentDate";
            command.CommandText = query;
            DbDataReader reader = await command.ExecuteReaderAsync();

            if (reader.HasRows)
            {
                while (await reader.ReadAsync())
                {
                    var row = new EnrollmentDateGroup { EnrollmentDate = reader.GetDateTime(0), StudentCount = reader.GetInt32(1) };
                    groups.Add(row);
                }
            }
            reader.Dispose();
        }
    }
    finally
    {
        conn.Close();
    }
    return View(groups);
}

Ajoutez une instruction using :

using System.Data.Common;

Exécutez l’application et accédez à la page À propos. Il affiche les mêmes données qu’avant.

À propos de la page

Appeler une requête de mise à jour

Supposons que les administrateurs de l’Université Contoso souhaitent effectuer des modifications globales dans la base de données, telles que la modification du nombre de crédits pour chaque cours. Si l’université a un grand nombre de cours, il serait inefficace de les récupérer en tant qu’entités et de les modifier individuellement. Dans cette section, vous allez implémenter une page web qui permet à l’utilisateur de spécifier un facteur par lequel modifier le nombre de crédits pour tous les cours, et vous allez apporter la modification en exécutant une instruction SQL UPDATE. La page web se présente comme l’illustration suivante :

mettre à jour les crédits de cours

Dans CoursesController.cs, ajoutez des méthodes UpdateCourseCredits pour HttpGet et HttpPost :

public IActionResult UpdateCourseCredits()
{
    return View();
}
[HttpPost]
public async Task<IActionResult> UpdateCourseCredits(int? multiplier)
{
    if (multiplier != null)
    {
        ViewData["RowsAffected"] = 
            await _context.Database.ExecuteSqlCommandAsync(
                "UPDATE Course SET Credits = Credits * {0}",
                parameters: multiplier);
    }
    return View();
}

Lorsque le contrôleur traite une requête HttpGet, rien n’est retourné dans ViewData["RowsAffected"], et l’affichage affiche une zone de texte vide et un bouton Envoyer, comme illustré dans l’illustration précédente.

Lorsque vous cliquez sur le bouton Update, la méthode HttpPost est appelée et le multiplicateur a la valeur entrée dans la zone de texte. Le code exécute alors l’instruction SQL qui met à jour les cours et renvoie le nombre de lignes affectées à la vue dans ViewData. Lorsque la vue obtient une valeur RowsAffected, elle affiche le nombre de lignes mises à jour.

Dans l’Explorateur de solutions, cliquez avec le bouton droit sur le dossier Vues/Cours, puis cliquez sur Ajouter > Nouvel élément.

Dans la boîte de dialogue Ajouter un nouvel élément, cliquez sur ASP.NET Core sous Installé dans le volet gauche, cliquez sur Razor Afficher, puis nommez la nouvelle vue UpdateCourseCredits.cshtml.

Dans Views/Courses/UpdateCourseCredits.cshtml, remplacez le code du modèle par le code suivant :

@{
    ViewBag.Title = "UpdateCourseCredits";
}

<h2>Update Course Credits</h2>

@if (ViewData["RowsAffected"] == null)
{
    <form asp-action="UpdateCourseCredits">
        <div class="form-actions no-color">
            <p>
                Enter a number to multiply every course's credits by: @Html.TextBox("multiplier")
            </p>
            <p>
                <input type="submit" value="Update" class="btn btn-default" />
            </p>
        </div>
    </form>
}
@if (ViewData["RowsAffected"] != null)
{
    <p>
        Number of rows updated: @ViewData["RowsAffected"]
    </p>
}
<div>
    @Html.ActionLink("Back to List", "Index")
</div>

Exécutez la méthode UpdateCourseCredits en sélectionnant l’onglet Courses, puis en ajoutant « /UpdateCourseCredits » à la fin de l’URL dans la barre d’adresse du navigateur (par exemple : http://localhost:5813/Courses/UpdateCourseCredits). Entrez un nombre dans la zone de texte :

mettre à jour les crédits de cours

Cliquez sur Update. Vous voyez le nombre de lignes affectées :

Page de mise à jour des crédits de cours – lignes affectées

Cliquez sur Retour à la liste pour afficher la liste des cours avec le nombre révisé de crédits.

Notez que le code de production garantit que les mises à jour entraînent toujours des données valides. Le code simplifié illustré ici peut multiplier le nombre de crédits suffisants pour aboutir à des nombres supérieurs à 5. (La propriété Credits a un attribut [Range(0, 5)].) La requête de mise à jour fonctionne, mais les données non valides peuvent entraîner des résultats inattendus dans d’autres parties du système qui supposent que le nombre de crédits est de 5 ou moins.

Pour plus d’informations sur les requêtes SQL brutes, consultez requêtes SQL brutes.

Examiner les requêtes SQL

Il est parfois utile de voir les requêtes SQL réelles envoyées à la base de données. La fonctionnalité de journalisation intégrée pour ASP.NET Core est automatiquement utilisée par EF Core pour écrire des journaux contenant le code SQL pour les requêtes et les mises à jour. Dans cette section, vous verrez quelques exemples de journalisation SQL.

Ouvrez StudentsController.cs et, dans la méthode Details, définissez un point d’arrêt sur l’instruction if (student == null).

Exécutez l’application en mode débogage, puis accédez à la page Détails d’un étudiant.

Accédez à la fenêtre Output qui indique la sortie de débogage. Vous voyez la requête :

Microsoft.EntityFrameworkCore.Database.Command:Information: Executed DbCommand (56ms) [Parameters=[@__id_0='?'], CommandType='Text', CommandTimeout='30']
SELECT TOP(2) [s].[ID], [s].[Discriminator], [s].[FirstName], [s].[LastName], [s].[EnrollmentDate]
FROM [Person] AS [s]
WHERE ([s].[Discriminator] = N'Student') AND ([s].[ID] = @__id_0)
ORDER BY [s].[ID]
Microsoft.EntityFrameworkCore.Database.Command:Information: Executed DbCommand (122ms) [Parameters=[@__id_0='?'], CommandType='Text', CommandTimeout='30']
SELECT [s.Enrollments].[EnrollmentID], [s.Enrollments].[CourseID], [s.Enrollments].[Grade], [s.Enrollments].[StudentID], [e.Course].[CourseID], [e.Course].[Credits], [e.Course].[DepartmentID], [e.Course].[Title]
FROM [Enrollment] AS [s.Enrollments]
INNER JOIN [Course] AS [e.Course] ON [s.Enrollments].[CourseID] = [e.Course].[CourseID]
INNER JOIN (
    SELECT TOP(1) [s0].[ID]
    FROM [Person] AS [s0]
    WHERE ([s0].[Discriminator] = N'Student') AND ([s0].[ID] = @__id_0)
    ORDER BY [s0].[ID]
) AS [t] ON [s.Enrollments].[StudentID] = [t].[ID]
ORDER BY [t].[ID]

Vous remarquerez quelque chose ici qui peut vous surprendre : le SQL sélectionne jusqu’à 2 lignes (TOP(2)) dans la table Person. La méthode SingleOrDefaultAsync ne se résout pas sur 1 ligne sur le serveur. Voici pourquoi :

  • Si la requête retourne plusieurs lignes, la méthode retourne null.
  • Pour déterminer si la requête retourne plusieurs lignes, EF doit vérifier si elle retourne au moins 2.

Notez que vous n’êtes pas tenu d’utiliser le mode débogage et de vous arrêter à un point d’arrêt pour obtenir la sortie de journalisation dans la fenêtre Output. C’est simplement un moyen pratique d’arrêter la journalisation au stade où vous voulez examiner la sortie. Si ce n’est pas le cas, la journalisation continue et vous devez revenir en arrière pour trouver les parties qui vous intéressent.

Créer une couche d’abstraction

De nombreux développeurs écrivent du code pour implémenter le référentiel et l’unité de travail en tant que wrapper autour du code qui fonctionne avec Entity Framework. Ces modèles sont destinés à créer une couche d’abstraction entre la couche d’accès aux données et la couche logique métier d’une application. L’implémentation de ces modèles peut aider à isoler votre application des modifications dans le magasin de données et à faciliter les tests unitaires automatisés ou le développement piloté par les tests (TDD). Toutefois, l’écriture de code supplémentaire pour implémenter ces modèles n’est pas toujours le meilleur choix pour les applications qui utilisent EF, pour plusieurs raisons :

  • La classe de contexte EF elle-même isole votre code du code spécifique au magasin de données.

  • La classe de contexte EF peut agir comme une classe d’unité de travail pour les mises à jour de base de données que vous effectuez à l’aide d’EF.

  • EF inclut des fonctionnalités permettant d’implémenter TDD sans écrire de code de référentiel.

Pour plus d’informations sur l’implémentation du référentiel et de l’unité de travail, consultez la version Entity Framework 5 de cette série de didacticiels.

Entity Framework Core implémente un fournisseur de base de données en mémoire qui peut être utilisé pour les tests. Pour plus d’informations, consultez Test avec InMemory.

Détection automatique des modifications

Entity Framework détermine comment une entité a changé (et par conséquent quelles mises à jour doivent être envoyées à la base de données) en comparant les valeurs actuelles d’une entité aux valeurs d’origine. Les valeurs d’origine sont stockées lorsque l’entité fait l’objet d’une requête ou d’une jointure. Voici quelques-unes des méthodes qui provoquent la détection automatique des modifications :

  • DbContext.SaveChanges

  • DbContext.Entry

  • ChangeTracker.Entries

Si vous effectuez le suivi d’un grand nombre d’entités et que vous appelez l’une de ces méthodes plusieurs fois dans une boucle, vous pouvez obtenir des améliorations significatives des performances en désactivant temporairement la détection automatique des modifications à l’aide de la propriété ChangeTracker.AutoDetectChangesEnabled. Par exemple:

_context.ChangeTracker.AutoDetectChangesEnabled = false;

EF Core code source et plans de développement

La source Entity Framework Core se trouve à https://github.com/dotnet/efcore. Le référentiel EF Core contient des builds nocturnes, un suivi des problèmes, des spécifications de fonctionnalités, des notes de réunion de conception et la feuille de route pour le développement futur. Vous pouvez signaler ou trouver des bogues et contribuer.

Bien que le code source soit ouvert, Entity Framework Core est entièrement pris en charge en tant que produit Microsoft. L’équipe Microsoft Entity Framework contrôle les contributions acceptées et teste toutes les modifications de code pour garantir la qualité de chaque version.

Ingénierie à rebours à partir de la base de données existante

Pour inverser l’ingénierie d’un modèle de données, y compris les classes d’entité d’une base de données existante, utilisez la commande caffold-dbcontext. Consultez le didacticiel de prise en main .

Utiliser LINQ dynamique pour simplifier le code

Le troisième didacticiel de cette série montre comment écrire du code LINQ en codant en dur les noms des colonnes dans une instruction switch. Avec deux colonnes à choisir, cela fonctionne correctement, mais si vous avez de nombreuses colonnes, le code peut être détaillé. Pour résoudre ce problème, vous pouvez utiliser la méthode EF.Property pour spécifier le nom de la propriété sous forme de chaîne. Pour essayer cette approche, remplacez la méthode Index dans le StudentsController par le code suivant.

 public async Task<IActionResult> Index(
     string sortOrder,
     string currentFilter,
     string searchString,
     int? pageNumber)
 {
     ViewData["CurrentSort"] = sortOrder;
     ViewData["NameSortParm"] = 
         String.IsNullOrEmpty(sortOrder) ? "LastName_desc" : "";
     ViewData["DateSortParm"] = 
         sortOrder == "EnrollmentDate" ? "EnrollmentDate_desc" : "EnrollmentDate";

     if (searchString != null)
     {
         pageNumber = 1;
     }
     else
     {
         searchString = currentFilter;
     }

     ViewData["CurrentFilter"] = searchString;

     var students = from s in _context.Students
                    select s;
     
     if (!String.IsNullOrEmpty(searchString))
     {
         students = students.Where(s => s.LastName.Contains(searchString)
                                || s.FirstMidName.Contains(searchString));
     }

     if (string.IsNullOrEmpty(sortOrder))
     {
         sortOrder = "LastName";
     }

     bool descending = false;
     if (sortOrder.EndsWith("_desc"))
     {
         sortOrder = sortOrder.Substring(0, sortOrder.Length - 5);
         descending = true;
     }

     if (descending)
     {
         students = students.OrderByDescending(e => EF.Property<object>(e, sortOrder));
     }
     else
     {
         students = students.OrderBy(e => EF.Property<object>(e, sortOrder));
     }

     int pageSize = 3;
     return View(await PaginatedList<Student>.CreateAsync(students.AsNoTracking(), 
         pageNumber ?? 1, pageSize));
 }

Remerciements

Tom Dykstra et Rick Anderson (twitter @RickAndMSFT) écrit ce tutoriel. Rowan Miller, Diego Vega et d’autres membres de l’équipe Entity Framework ont assisté à des révisions de code et ont aidé à déboguer les problèmes qui se sont produit pendant que nous écrivions du code pour les didacticiels. John Parente et Paul Goldman ont travaillé sur la mise à jour du didacticiel pour ASP.NET Core 2.2.

Résoudre les erreurs courantes

ContosoUniversity.dll utilisé par un autre processus

Message d'erreur:

Impossible d’ouvrir '... bin\Debug\netcoreapp1.0\ContosoUniversity.dll' for writing -- 'The process cannot access the file '...\bin\Debug\netcoreapp1.0\ContosoUniversity.dll' parce qu’il est utilisé par un autre processus.

Solution :

Arrêtez le site dans IIS Express. Accédez à la barre d'état système Windows, recherchez IIS Express et faites un clic droit sur son icône, sélectionnez le site de l'Université Contoso, puis cliquez sur Arrêter le site.

Migration structurée sans code dans les méthodes Up et Down

Cause possible :

Les commandes EF CLI ne ferment pas et enregistrent automatiquement les fichiers de code. Si vous n’avez pas enregistré de modifications lorsque vous exécutez la commande migrations add, EF ne trouve pas vos modifications.

Solution :

Exécutez la commande migrations remove, enregistrez vos modifications de code et réexécutez la commande migrations add.

Erreurs lors de l’exécution de la mise à jour de la base de données

Il est possible d’obtenir d’autres erreurs lors de la modification du schéma dans une base de données qui contient des données existantes. Si vous obtenez des erreurs de migration que vous ne pouvez pas résoudre, vous pouvez modifier le nom de la base de données dans la chaîne de connexion ou supprimer la base de données. Avec une nouvelle base de données, il n’y a pas de données à migrer, et la commande update-database est beaucoup plus susceptible de se terminer sans erreurs.

L’approche la plus simple consiste à renommer la base de données dans appsettings.json. La prochaine fois que vous exécutez database update, une nouvelle base de données sera créée.

Pour supprimer une base de données dans SSOX, cliquez avec le bouton droit sur la base de données, cliquez sur Supprimer, puis dans la boîte de dialogue Supprimer la base de données, sélectionnez Fermer les connexions existantes, puis cliquez sur OK.

Pour supprimer une base de données à l’aide de l’interface CLI, exécutez la commande CLI database drop :

dotnet ef database drop

Erreur lors de la localisation de l’instance SQL Server

Message d'erreur:

Une erreur propre au réseau ou à une instance s’est produite lors de l’établissement d’une connexion à SQL Server. Le serveur n’a pas été trouvé ou n’a pas été accessible. Vérifiez que le nom de l’instance est correct et que SQL Server est configuré pour autoriser les connexions distantes. (fournisseur : Interfaces réseau SQL, erreur : 26 - Erreur de localisation du serveur/de l’instance spécifié)

Solution :

Vérifiez la chaîne de connexion. Si vous avez supprimé manuellement le fichier de base de données, modifiez le nom de la base de données dans la chaîne de construction pour recommencer avec une nouvelle base de données.

Obtenir le code

Télécharger ou afficher l’application terminée.

Ressources additionnelles

Pour plus d’informations sur EF Core, consultez la documentation Entity Framework Core. Un livre est également disponible : Entity Framework Core in Action.

Pour plus d’informations sur le déploiement d’une application web, consultez Hôte et déployer ASP.NET Core.

Pour plus d’informations sur d’autres rubriques relatives à ASP.NET Core MVC, telles que l’authentification et l’autorisation, consultez Vue d’ensemble de ASP.NET Core.

Pour obtenir des conseils sur la création d’une application ASP.NET Core fiable, sécurisée, performante, testable et évolutive, consultez Modèles d’application web d’entreprise. Un exemple complet d’application web de qualité de production qui implémente les modèles est disponible.

Étapes suivantes

Dans ce tutoriel, vous allez :

  • Requêtes SQL brutes effectuées
  • Appeler une requête pour retourner des entités
  • Appeler une requête pour retourner d’autres types
  • Appeler une requête de mise à jour
  • Requêtes SQL examinées
  • Création d’une couche d’abstraction
  • En savoir plus sur la détection automatique des modifications
  • En savoir plus sur EF Core code source et les plans de développement
  • Découvrez comment utiliser LINQ dynamique pour simplifier le code

Cette série de tutoriels sur l’utilisation d’Entity Framework Core dans une application MVC ASP.NET Core est terminée. Cette série a fait appel à une nouvelle base de données ; une alternative consiste à rétroconcevoir un modèle à partir d’une base de données existante.

Tutoriel : EF Core avec MVC, base de données existante