web-dev-qa-db-fra.com

c # travailler avec Entity Framework sur un serveur multi-thread

Quelle est la pratique recommandée pour travailler avec une structure d'entité sur un serveur multi-thread? ?__. J'utilise une structure d'entité ObjectContext pour gérer toutes les actions de ma base de données, maintenant je sais que ce contexte n'est pas thread-safe pour l'utiliser pour effectuer certaines actions de la base de données, je l'entoure de l'instruction lock pour être sûr. Est-ce que c'est comme ça que je devrais le faire ??

39
Eyal

Quelques conseils rapides pour Entity Framework dans un environnement multithread:

  • N'utilisez pas de contexte unique avec locks (pas de motif singleton)
  • Fournissez services sans état (vous devez instancier et supprimer un contexte par demande).
  • Raccourcissez autant que possible la durée de vie du contexte
  • Implémentez un système de contrôle de la concurrence . La concurrence optimiste peut être facilement mise en œuvre avec Entity Framework ( comment ). Cela vous évitera d'écraser les modifications apportées à la base de données lorsque vous utilisez une entité qui n'est pas à jour.

Je suis un peu confus, je pensais que l’utilisation d’un seul contexte est bonne parce que je crois que cela fait quelque chose d’attrapé, alors quand j’ai affaire à la même chose entité dans les demandes consécutives, il sera beaucoup plus rapide d’utiliser la même chose contexte puis créer un nouveau contexte à chaque fois. Alors pourquoi est-ce bon de l'utiliser comme ça s'il est plus lent et toujours pas enfilé?

Vous pouvez n'utiliser qu'un seul contexte, mais il est fortement déconseillé à moins que vous ne sachiez vraiment ce que vous faites.

Je vois deux problèmes principaux qui se produisent souvent avec une telle approche:

  1. vous utiliserez beaucoup de mémoire car votre contexte ne sera jamais supprimé et toutes les entités manipulées seront mises en cache en mémoire (chaque entité qui apparaît dans le résultat d'une requête est mise en cache).

  2. vous devrez faire face à de nombreux problèmes de concurrence si vous modifiez vos données à partir d'un autre programme/contexte. Par exemple, si vous modifiez quelque chose directement dans votre base de données et que l'entité associée est déjà mise en cache dans votre objet de contexte unique, votre contexte n'aura jamais connaissance de la modification effectuée directement dans la base de données. Vous travaillerez avec une entité mise en cache qui n'est pas à jour, et croyez-moi, cela conduira à des problèmes difficiles à trouver et à résoudre.

Ne vous inquiétez pas non plus des performances liées à l'utilisation de plusieurs contextes: la charge de création/suppression d'un nouveau contexte par requête est presque insignifiante dans 90% des cas d'utilisation. N'oubliez pas que la création d'un nouveau contexte ne crée pas nécessairement une nouvelle connexion à la base de données (car la base de données utilise généralement un pool de connexions).

67
ken2k

Est-ce que c'est comme ça que je devrais le faire ??

Au minimum, utilisez un contexte par thread, mais je vous encourage fortement à le considérer comme une unité de travail et à utiliser ainsi un contexte par unité de travail par thread.

C'est à vous de définir "unité de travail" pour votre application. Mais n'utilisez pas lock pour utiliser un contexte sur plusieurs threads. Ça n'échelle pas.

12
jason

Vous traitez ObjectContext comme s'il s'agissait d'une entité extrêmement coûteuse, vous instanciez donc une fois et ensuite vous le traitiez comme une "façade". Il n'y a pas besoin de faire cela. Si, pour aucune autre raison, les connexions sont regroupées sous le capot et coûtent très peu (microseconde? - probablement moins?) Pour configurer complètement la "chaîne d'objets" afin qu'elle utilise l'abstraction de ObjectContext.

ObjectContext, tout comme l'utilisation directe de SqlConnection, etc., est conçu pour être utilisé avec une méthodologie "instancier le plus tard possible et vider le plus rapidement possible".

EF vous offre une certaine sécurité en vous permettant de vérifier si vous disposez des derniers objets avant de vous engager (Optimistic Concurrency). Cela ne signifie pas en soi "thread safe", mais vous obtiendrez le même résultat si vous respectez les règles. 

5
Gregory A Beamer

En règle générale, ObjectContext ne doit pas être utilisé globalement dans toute l'application. Vous devez fréquemment créer de nouveaux ObjectContexts et en supprimer d'anciens. Ils ne sont certainement pas threadsafe non plus. Si vous continuez à utiliser le même ObjectContext (selon la durée de vie de votre application), il est facile d'obtenir une exception de mémoire insuffisante si vous modifiez des quantités énormes de données car les références aux entités que vous modifiez sont conservées par le contexte de l'objet.

2
Devin

J'utilise la structure d'entités dans un environnement multi-thread, où n'importe quel thread, interface utilisateur et arrière-plan (STA et MTA), peuvent simultanément mettre à jour la même base de données. J'ai résolu ce problème en recréant la connexion d'entité à partir de zéro au début de l'utilisation sur un nouveau thread en arrière-plan. Examen de l'instance de connexion d'entité ConnectionString montre un guide de lecteur qui, je suppose, est utilisé pour lier des instances de connexion courantes. En recréant la connexion d'entité à partir de zéro, les valeurs guid sont différentes pour chaque thread et aucun conflit ne semble se produire. Notez que l'assemblage doit uniquement être identique à celui du modèle.

public static EntityConnection GetEntityConnection(
// Build the connection string.

  var sqlBuilder = new SqlConnectionStringBuilder();
  sqlBuilder.DataSource = serverName;
  sqlBuilder.InitialCatalog = databaseName;
  sqlBuilder.MultipleActiveResultSets = true;
  ...
  var providerString = sqlBuilder.ToString();
  var sqlConnection = new SqlConnection(providerString);

// Build the emtity connection.

  Assembly metadataAssembly = Assembly.GetExecutingAssembly();
  Assembly[] metadataAssemblies = { metadataAssembly };
  var metadataBase = @"res://*/{0}.csdl|res://*/{0}.ssdl|res://*/{0}.msl";
  var dbModelMetadata = String.Format(metadataBase, objectContextTypeModelName);
  // eg: "res://*/Models.MyDatabaseModel.csdl|res://*/Models.MyDatabaseModel.ssdl|res://*/Models.MyDatabaseModel.msl"
  var modelMetadataPaths = modelMetadata.Split('|');
  var metadataWorkspace = new MetadataWorkspace(modelMetadataPaths, metadataAssemblies);
  var entityDbConnection = new EntityConnection(metadataWorkspace, sqlConnection);
  return entityDbConnection;
0
David Coleman