Enregistrer des données

Alors qu’interroger des données vous permet de lire des données à partir de la base de données, enregistrer des données signifie ajouter de nouvelles entités à la base de données, supprimer des entités ou modifier les propriétés d’entités existantes d’une manière ou d’une autre. Entity Framework Core (EF Core) prend en charge deux approches fondamentales pour enregistrer des données dans la base de données.

Approche 1 : suivi des modifications et SaveChanges

Dans de nombreux scénarios, votre programme doit interroger des données à partir de la base de données, effectuer une modification sur celles-ci et enregistrer ces modifications, ce qu’on appelle parfois une « unité de travail ». Par exemple, supposons que vous disposez d’un ensemble de blogs et que vous souhaitez modifier la propriété Url de l’un d’entre eux. Dans EF, la procédure est généralement comme suit :

using (var context = new BloggingContext())
{
    var blog = context.Blogs.Single(b => b.Url == "http://example.com");
    blog.Url = "http://example.com/blog";
    context.SaveChanges();
}

Le code ci-dessus suit les étapes suivantes :

  1. Il utilise une requête LINQ standard pour charger une entité à partir de la base de données (voir Interroger des données). Les requêtes d’EF effectuent le suivi par défaut, ce qui signifie qu’EF suit les entités chargées dans son dispositif de suivi des modifications interne.
  2. L’instance de l’entité chargée est manipulée comme d’habitude, en affectant une propriété .NET. EF n’est pas impliqué dans cette étape.
  3. Enfin, DbContext.SaveChanges() est appelé. À ce stade, EF détecte automatiquement toute modification en comparant les entités à un instantané du moment où elles ont été chargées. Toute modification détectée est conservée dans la base de données. Lors de l’utilisation d’une base de données relationnelle, cela implique généralement l’envoi d’un SQL UPDATE, par exemple, pour mettre à jour les lignes pertinentes.

Notez que ce qui précède décrit une opération de mise à jour classique pour les données existantes, mais des principes similaires s’appliquent pour ajouter et supprimer des entités. Vous interagissez avec le dispositif de suivi des modifications d’EF en appelant DbSet<TEntity>.Add et Remove, ce qui entraîne le suivi des modifications. EF applique ensuite toutes les modifications suivies à la base de données quand SaveChanges() est appelé (p. ex., via SQL INSERT et DELETE lors de l’utilisation d’une base de données relationnelle).

SaveChanges() offre les avantages suivants :

  • Vous n’avez pas besoin d’écrire du code pour suivre les entités et les propriétés modifiées. EF effectue cette opération automatiquement pour vous et met uniquement à jour ces propriétés dans la base de données, ce qui améliore les performances. Imaginez que vos entités chargées sont liées à un composant d’interface utilisateur permettant aux utilisateurs de modifier les propriétés qu’ils souhaitent. Dans ce cas, EF élimine le fardeau de déterminer quelles entités et propriétés ont été réellement modifiées.
  • L’enregistrement des modifications dans la base de données peut parfois être compliqué. Par exemple, si vous souhaitez ajouter un blog et certaines publications pour ce blog, vous devrez peut-être récupérer la clé générée par la base de données pour le blog inséré avant de pouvoir insérer les publications (car elles doivent faire référence au blog). EF s’en occupe pour vous, ce qui simplifie considérablement les choses.
  • EF peut détecter les problèmes d’accès concurrentiel, par exemple lorsqu’une ligne de base de données a été modifiée par quelqu’un d’autre entre votre requête et SaveChanges(). Pour plus d’informations, consultez Conflits d’accès concurrentiel.
  • Sur les bases de données qui le prennent en charge, SaveChanges() inclut automatiquement plusieurs modifications d’une transaction dans un wrapper, ce qui garantit que vos données restent cohérentes si une défaillance se produit. Pour plus d’informations, consultez Transactions.
  • Dans de nombreux cas, SaveChanges() regroupe également plusieurs modifications, ce qui réduit considérablement le nombre d’allers-retours avec la base de données et améliore nettement les performances. Pour plus d’informations, consultez Mise à jour efficace.

Pour plus d’informations et des exemples de code sur l’utilisation basique de SaveChanges(), consultez SaveChanges de base. Pour plus d’informations sur le suivi des modifications d’EF, consultez la Vue d’ensemble du suivi des modifications.

Approche 2 : ExecuteUpdate et ExecuteDelete (« mise à jour en bloc »)

Remarque

Cette fonctionnalité a été introduite dans EF Core 7.0.

Bien que le suivi des modifications et SaveChanges() constituent une méthode efficace pour enregistrer des modifications, ils présentent certains inconvénients.

Tout d’abord, SaveChanges() exige que vous interrogiez et suiviez toutes les entités que vous allez modifier ou supprimer. Si vous devez, par exemple, supprimer tous les blogs avec une évaluation inférieure à un certain seuil, vous devez interroger, matérialiser et suivre un nombre potentiellement considérable de lignes et faire en sorte que SaveChanges() génère une instruction DELETE pour chacune d’entre elles. Les bases de données relationnelles offrent une alternative beaucoup plus efficace : une seule commande DELETE peut être envoyée, en spécifiant les lignes à supprimer via une clause WHERE, ce que ne permet pas de faire le modèle SaveChanges().

Pour prendre en charge ce scénario de « mise à jour en bloc », vous pouvez utiliser ExecuteDelete comme suit :

context.Blogs.Where(b => b.Rating < 3).ExecuteDelete();

Cela vous permet d’exprimer une instruction SQL DELETE via des opérateurs LINQ standard, comme vous le feriez avec une requête LINQ standard, ce qui entraîne l’exécution du code SQL suivant sur la base de données :

DELETE FROM [b]
FROM [Blogs] AS [b]
WHERE [b].[Rating] < 3

Cela s’exécute de manière très efficace dans la base de données, sans charger de données à partir de la base de données ni impliquer le dispositif de suivi des modifications d’EF. De même, ExecuteUpdate vous permet d’exprimer une instruction SQL UPDATE.

Même si vous ne modifiez pas d’entités en bloc, vous savez peut-être exactement quelles propriétés de quelle entité vous souhaitez modifier. L’utilisation de l’API de suivi des modifications pour effectuer la modification peut être trop complexe, en nécessitant de créer une instance d’entité, de la suivre via Attach, d’apporter vos modifications, puis enfin d’appeler SaveChanges(). Pour de tels scénarios, ExecuteUpdate et ExecuteDelete peuvent être un moyen considérablement plus simple d’effectuer la même opération.

Enfin, le suivi des modifications et le modèle SaveChanges() lui-même impliquent une certaine surcharge du runtime. Si vous écrivez une application haute performance, ExecuteUpdate et ExecuteDelete vous permettent d’éviter ces composants et de générer efficacement l’instruction souhaitée.

Notez toutefois que ExecuteUpdate et ExecuteDelete ont également certaines limitations :

  • Ces méthodes s’exécutent immédiatement et ne peuvent pas être traitées par lots avec d’autres opérations. En revanche, SaveChanges() peut regrouper plusieurs opérations.
  • Étant donné que le suivi des modifications n’est pas impliqué, vous devez savoir exactement quelles entités et propriétés doivent être modifiées. Cela peut signifier un suivi de code manuel et de bas niveau de ce qui doit être modifié ou ne doit pas l’être.
  • De plus, étant donné que le suivi des modifications n’est pas impliqué, ces méthodes n’appliquent pas automatiquement le contrôle d’accès concurrentiel lorsque les modifications sont conservées. Toutefois, vous pouvez toujours ajouter explicitement une clause Where pour implémenter vous-même le contrôle d’accès concurrentiel.
  • Seules la mise à jour et la suppression sont actuellement prises en charge. Pour l’insertion, vous devez utiliser DbSet<TEntity>.Add et SaveChanges().

Pour plus d’informations et d’exemples de code, consultez ExecuteUpdate et ExecuteDelete.

Résumé

Voici quelques instructions pour savoir quand utiliser cette approche. Notez que ces règles ne sont pas absolues, mais peuvent s’avérer utiles :

  • Si vous ne savez pas à l’avance quelles modifications auront lieu, utilisez SaveChanges. Cela détectera automatiquement les modifications à appliquer. Exemples de scénarios :
    • « Je souhaite charger un blog à partir de la base de données et afficher un formulaire permettant à l’utilisateur de le modifier »
  • Si vous devez manipuler un graphique d’objets (c’est-à-dire plusieurs objets interconnectés), utilisez SaveChanges. Cela déterminera l’ordre approprié des modifications et la manière de tout lier ensemble.
    • « Je veux mettre à jour un blog, en modifiant certaines des publications et en supprimant d’autres publications »
  • Si vous souhaitez modifier un nombre potentiellement important d’entités en fonction de certains critères, utilisez ExecuteUpdate et ExecuteDelete. Exemples de scénarios :
    • « Je veux donner à tous les employés une augmentation »
    • « Je veux supprimer tous les blogs dont le nom commence par X »
  • Si vous savez déjà exactement quelles entités vous souhaitez modifier et comment les modifier, utilisez ExecuteUpdate et ExecuteDelete. Exemples de scénarios :
    • « Je veux supprimer le blog qui s’appelle ‘Foo’ »
    • « Je veux changer le nom du blog avec l’ID 5 en ‘Bar’ »