Framework d'entité et regroupement de connexions

268

J'ai récemment commencé à utiliser Entity Framework 4.0 dans mon application .NET 4.0 et je suis curieux de savoir certaines choses concernant le pooling.

  1. Le pool de connexions que je connais est géré par le fournisseur de données ADO.NET, dans mon cas, celui du serveur MS SQL. Cela s'applique-t-il lorsque vous instanciez un nouveau contexte d'entités ( ObjectContext), c'est-à-dire sans paramètre new MyDatabaseModelEntities()?

  2. Quels sont les avantages et les inconvénients de a) créer un contexte d'entités globales pour l'application (c'est-à-dire une instance statique) ou b) de créer et d'exposer un contexte d'entités pour chaque opération / méthode donnée, avec un usingbloc.

  3. Y a-t-il d'autres recommandations, meilleures pratiques ou approches communes pour certains scénarios que je devrais connaître?

Noldorin
la source

Réponses:

369
  1. Le regroupement de connexions est géré comme dans toute autre application ADO.NET. La connexion d'entité utilise toujours une connexion de base de données traditionnelle avec une chaîne de connexion traditionnelle. Je crois que vous pouvez désactiver le regroupement de connexions dans la chaîne de connexion si vous ne souhaitez pas l'utiliser. (en savoir plus sur le regroupement de connexions SQL Server (ADO.NET) )
  2. N'utilisez jamais le contexte mondial. ObjectContext implémente en interne plusieurs modèles, y compris la carte d'identité et l'unité de travail. L'impact de l'utilisation du contexte global est différent selon le type d'application.
  3. Pour les applications Web, utilisez un seul contexte par demande. Pour les services Web, utilisez un seul contexte par appel. Dans les applications WinForms ou WPF, utilisez un seul contexte par formulaire ou par présentateur. Il peut y avoir des exigences spéciales qui ne permettront pas d'utiliser cette approche mais dans la plupart des situations, cela suffit.

Si vous voulez savoir quel impact a le contexte d'objet unique pour l'application WPF / WinForm, consultez cet article . Il s'agit de NHibernate Session mais l'idée est la même.

Éditer:

Lorsque vous utilisez EF, il charge par défaut chaque entité une seule fois par contexte. La première requête crée une instance d'entité et la stocke en interne. Toute requête ultérieure qui nécessite une entité avec la même clé renvoie cette instance stockée. Si les valeurs du magasin de données ont changé, vous recevez toujours l'entité avec les valeurs de la requête initiale. C'est ce qu'on appelle le modèle de carte d'identité . Vous pouvez forcer le contexte de l'objet à recharger l'entité mais il rechargera une seule instance partagée.

Les modifications apportées à l'entité ne sont pas conservées jusqu'à ce que vous appeliez SaveChangesle contexte. Vous pouvez effectuer des modifications dans plusieurs entités et les stocker simultanément. C'est ce qu'on appelle le modèle d'unité de travail . Vous ne pouvez pas dire de manière sélective l'entité attachée modifiée que vous souhaitez enregistrer.

Combinez ces deux modèles et vous verrez des effets intéressants. Vous n'avez qu'une seule instance d'entité pour l'ensemble de l'application. Toutes les modifications apportées à l'entité affectent l'ensemble de l'application même si les modifications ne sont pas encore persistantes (validées). Dans la plupart des cas, ce n'est pas ce que vous voulez. Supposons que vous ayez un formulaire d'édition dans l'application WPF. Vous travaillez avec l'entité et vous décidez d'annuler une édition complexe (modification de valeurs, ajout d'entités liées, suppression d'autres entités liées, etc.). Mais l'entité est déjà modifiée dans un contexte partagé. Que vas-tu faire? Astuce: je ne connais aucun CancelChanges ou UndoChanges surObjectContext .

Je pense que nous n'avons pas à discuter du scénario du serveur. Le simple partage d'une seule entité entre plusieurs requêtes HTTP ou appels de service Web rend votre application inutile. Toute demande peut simplement déclencherSaveChanges et enregistrer des données partielles d'une autre demande car vous partagez une seule unité de travail entre toutes. Cela aura également un autre problème - le contexte et toute manipulation avec des entités dans le contexte ou une connexion à une base de données utilisée par le contexte n'est pas thread-safe.

Même pour une application en lecture seule, un contexte global n'est pas un bon choix car vous voulez probablement de nouvelles données à chaque fois que vous interrogez l'application.

Ladislav Mrnka
la source
Merci pour votre réponse. Peut-être pourriez-vous expliquer pourquoi il est mauvais d'utiliser un seul contexte mondial? Cela rend l'accès parallèle plus difficile, bien sûr, mais quoi d'autre ...?
Noldorin
Ok, c'est beaucoup plus clair maintenant, merci. Juste pour confirmer, bien qu'un contexte global ne soit jamais vraiment approprié, un seul contexte pour une "boîte de dialogue d'édition" ou autre peut être la bonne façon? Dans d'autres situations, comme les services Web et ASP.NET, les contextes dans les méthodes n'ont de sens que. À peu près correct?
Noldorin
J'ai suivi votre conseil et retiré le singelton. Maintenant, je reçois une autre erreur: stackoverflow.com/questions/14795899/…
Elad Benda
Je comprends que la mise en œuvre du modèle d'unité de travail et l'encapsulation de DbContext devraient séparer la logique métier et les opérations de base de données. Je ne suis pas en mesure de comprendre comment implémenter le modèle d'unité de travail et utiliser TransactionScope uniquement pour certaines opérations.
Rudolf Dvoracek
4
@RudolfDvoracek: facilement. TransactionScopen'appartient pas à l'unité d'oeuvre, il appartient à votre logique métier car la logique elle-même définit la transaction. L'unité d'oeuvre définit uniquement ce qui doit être conservé ensemble alors que la portée de la transaction vous permet d'utiliser plusieurs fois la persistance de l'unité d'oeuvre dans la même transaction.
Ladislav Mrnka
70

Selon Daniel Simmons:

Créez une nouvelle instance ObjectContext dans une instruction Using pour chaque méthode de service afin qu'elle soit supprimée avant le retour de la méthode. Cette étape est essentielle pour l'évolutivité de votre service. Il s'assure que les connexions à la base de données ne sont pas maintenues ouvertes entre les appels de service et que l'état temporaire utilisé par une opération particulière est récupéré lorsque cette opération est terminée. Entity Framework met automatiquement en cache les métadonnées et autres informations dont il a besoin dans le domaine d'application et les connexions de base de données des pools ADO.NET, donc la recréation du contexte à chaque fois est une opération rapide.

C'est de son article complet ici:

http://msdn.microsoft.com/en-us/magazine/ee335715.aspx

Je crois que ce conseil s'étend aux requêtes HTTP, donc serait valable pour ASP.NET. Une application cliente avec état, telle qu'une application WPF, pourrait être le seul cas pour un contexte "partagé".

Dave Swersky
la source
Merci, c'est une citation très informative là-bas. Cependant, je me demande toujours si un contexte partagé (global) serait approprié même pour une application WPF cliente ou similaire. Y a-t-il un avantage même dans ce cas?
Noldorin
Il n'y aurait aucun avantage à un contexte global dans une application WPF, mais il n'y aurait probablement pas non plus de préjudice significatif. Si vous implémentez un contexte global, vous devrez peut-être effectuer une gestion manuelle des connexions à la base de données (fermeture explicite de la connexion) en cas de taux de demande élevés.
Dave Swersky
1
Droite; Donc, essentiellement, je ne peux jamais vraiment me tromper en utilisant plusieurs contextes temporaires (étant donné que je sais que le regroupement de connexions se produit)? ... Si vous utilisiez un seul contexte global, la connexion en théorie ne pourrait-elle pas tomber à un moment aléatoire?
Noldorin
1
@Nolodrin: Je ne pense pas que la connexion chuterait "au hasard" ... le risque est que les connexions restent ouvertes trop longtemps et saturent le pool de connexions.
Dave Swersky
1
L'implémentation d'ObjectContext / DbContext IDisposabledoit donc être ouverte pendant le temps raisonnable le plus court, selon moi.
nicodemus13
12

Conformément à la documentation EF6 (4,5 également): https://msdn.microsoft.com/en-us/data/hh949853#9

9.3 Contexte par demande

Les contextes d'Entity Framework sont destinés à être utilisés comme des instances de courte durée afin de fournir l'expérience de performance la plus optimale . Les contextes devraient être de courte durée et rejetés, et en tant que tels ont été mis en œuvre pour être très légers et réutiliser les métadonnées chaque fois que possible. Dans les scénarios Web, il est important de garder cela à l'esprit et de ne pas avoir un contexte plus long que la durée d'une seule demande. De même, dans les scénarios non Web, le contexte doit être ignoré en fonction de votre compréhension des différents niveaux de mise en cache dans Entity Framework. D'une manière générale, il convient d'éviter d'avoir une instance de contexte tout au long de la vie de l'application, ainsi que des contextes par thread et des contextes statiques.

Raj Rao
la source
2
Je sais que cette réponse est là depuis un certain temps, mais je dois dire que cela m'a évité une tonne de maux de tête. Erreur d'obtention de la "Connexion groupée" lors de l'utilisation d'EF avec Oracle et impossible de comprendre pourquoi. J'avais défini le dbContext comme une variable de classe, l'instanciant à la création. Le changer pour créer le contexte selon les besoins a corrigé tous les maux de mon monde. Je vous remercie!
Fletchius
1

Le code ci-dessous a aidé mon objet à être actualisé avec de nouvelles valeurs de base de données. La commande Entry (object) .Reload () force l'objet à rappeler les valeurs de la base de données

GM_MEMBERS member = DatabaseObjectContext.GM_MEMBERS.FirstOrDefault(p => p.Username == username && p.ApplicationName == this.ApplicationName);
DatabaseObjectContext.Entry(member).Reload();
HGMamaci
la source
ainsi que pour les collections (code VB):CType(myContext, IObjectContextAdapter).ObjectContext.Refresh(RefreshMode.StoreWins,myCustomers)
Ivan Ferrer Villa