Il y a quelque chose qui m'embête depuis longtemps dans Entity Framework.
L'année dernière, j'ai écrit une grosse application pour un client utilisant EF. Et pendant le développement, tout a bien fonctionné.
Nous avons expédié le système en août. Mais après quelques semaines, j'ai commencé à voir des fuites de mémoire étranges sur le serveur de production. Mon processus ASP.NET MVC 4 utilisait toutes les ressources de la machine après quelques jours d’exécution (8 Go). Ce n'était pas bon. J'ai cherché sur le net et vu que vous devriez entourer toutes vos requêtes et opérations EF dans un bloc using()
afin que le contexte puisse être supprimé.
En une journée, j'ai refactoré tout mon code pour utiliser using()
et cela a résolu mes problèmes. Depuis lors, le processus repose sur une utilisation régulière de la mémoire.
La raison pour laquelle je n'ai pas entouré mes requêtes au départ, cependant, c'est que j'ai démarré mes premiers contrôleurs et référentiels à partir des propres échafaudages Microsofts inclus dans Visual Studio. Ceux-ci n'entouraient pas ses requêtes, mais utilisaient plutôt DbContext
en tant que variable d'instance. du contrôleur lui-même.
Tout d’abord : s’il est vraiment important de disposer du contexte (quelque chose qui ne serait pas étrange, le dbconnection
doit être fermé, etc.), Microsoft devrait peut-être l’avoir dans tous ses exemples!
Maintenant, j'ai commencé à travailler sur un nouveau grand projet avec tout ce que je savais à l'esprit et j'ai essayé les nouvelles fonctionnalités de .NET 4.5 et EF 6 async
and await
name__. EF 6.0 utilise toutes ces méthodes asynchrones (par exemple SaveChangesAsync
name__, ToListAsync
name__, etc.).
public Task<tblLanguage> Post(tblLanguage language)
{
using (var langRepo = new TblLanguageRepository(new Entities()))
{
return langRepo.Add(RequestOrganizationTypeEnum, language);
}
}
Dans la classe TblLanguageRepo
name__:
public async Task<tblLanguage> Add(OrganizationTypeEnum requestOrganizationTypeEnum, tblLanguage language)
{
...
await Context.SaveChangesAsync();
return langaugeDb;
}
Cependant, lorsque j'entoure maintenant mes instructions dans un bloc using()
, je reçois l'exception DbContext was disposed
avant que la requête ne puisse être renvoyée. C'est le comportement attendu. La requête s'exécute de manière asynchrone et le bloc using
est terminé avant la requête. Mais comment dois-je disposer de mon contexte de manière appropriée tout en utilisant le mode asynchrone et attendre les fonctions de ef 6 ??
S'il te plait, oriente moi dans la bonne direction.
Est-ce que using()
est nécessaire dans EF 6? Pourquoi les propres exemples de Microsoft ne proposent-ils jamais cela? Comment utilisez-vous les fonctionnalités asynchrones et supprimez-vous votre contexte correctement?
Votre code:
public Task<tblLanguage> Post(tblLanguage language)
{
using (var langRepo = new TblLanguageRepository(new Entities()))
{
return langRepo.Add(RequestOrganizationTypeEnum, language);
}
}
élimine le référentiel avant de renvoyer une Task
. Si vous faites le code async
:
public async Task<tblLanguage> Post(tblLanguage language)
{
using (var langRepo = new TblLanguageRepository(new Entities()))
{
return await langRepo.Add(RequestOrganizationTypeEnum, language);
}
}
il disposera ensuite du référentiel juste avant la fin de la Task
. En réalité, lorsque vous appuyez sur await
, la méthode renvoie une Task
incomplète (notez que le bloc using
est toujours "actif" à ce stade). Ensuite, lorsque la tâche langRepo.Add
se termine, la méthode Post
reprend l'exécution et supprime la langRepo
. Lorsque la méthode Post
est terminée, la Task
renvoyée est terminée.
Pour plus d'informations, voir my async
intro .
Je choisirais la méthode "un DbContext par demande" et réutiliserais le DbContext dans la demande. Comme toutes les tâches doivent de toute façon être terminées à la fin de la demande, vous pouvez la supprimer à nouveau en toute sécurité.
Voir, à savoir: Un DbContext par requête dans ASP.NET MVC (sans le conteneur IOC)
Quelques autres avantages:
using
supplémentaires encombrant votre code.Je suis d’accord avec @Dirk Boer que le meilleur moyen de gérer la durée de vie de DbContext est d’utiliser un conteneur IoC qui élimine le contexte à la fin de la requête http. Cependant, si ce n'est pas une option, vous pouvez également faire quelque chose comme ceci:
var dbContext = new MyDbContext();
var results = await dbContext.Set<MyEntity>.ToArrayAsync();
dbContext.Dispose();
L'instruction using
est simplement un sucre syntaxique pour la suppression d'un objet à la fin d'un bloc de code. Vous pouvez obtenir le même effet sans bloquer using
en appelant simplement .Dispose
vous-même.
À bien y penser, vous ne devriez pas avoir d'exceptions de suppression d'objet si vous utilisez le mot clé await
dans le bloc using:
public async Task<tblLanguage> Post(tblLanguage language)
{
using (var langRepo = new TblLanguageRepository(new Entities()))
{
var returnValue = langRepo.Add(RequestOrganizationTypeEnum, language);
await langRepo.SaveChangesAsync();
return returnValue;
}
}
Si vous utilisez les modèles de programmation à plusieurs niveaux appropriés, votre contrôleur ne devrait jamais même savoir qu’une demande de base de données est en cours d’exécution. Cela devrait tout se passer dans votre couche de service.
Il y a plusieurs façons de faire ça. L’une consiste à créer deux constructeurs par classe, un qui crée un contexte et un qui accepte un contexte déjà existant. De cette façon, vous pouvez transmettre le contexte si vous êtes déjà dans la couche service, ou en créer un nouveau s'il s'agit du contrôleur/modèle appelant la couche service.
L'autre consiste à créer une surcharge interne de chaque méthode et à accepter le contexte.
Mais, oui, vous devriez les emballer dans une utilisation.
En théorie, la collecte des ordures DEVRAIT nettoyer celles-ci sans les emballer, mais je ne fais pas entièrement confiance au GC.
Si vous souhaitez conserver votre méthode synchrone mais que vous souhaitez enregistrer dans la base de données de manière asynchrone, n'utilisez pas l'instruction using. Comme @danludwig l'a dit, il ne s'agit que d'un sucre syntaxique. Vous pouvez appeler la méthode SaveChangesAsync (), puis supprimer le contexte une fois la tâche terminée. Une façon de le faire est la suivante:
//Save asynchronously then dispose the context after
context.SaveChangesAsync().ContinueWith(c => context.Dispose());
Notez que le lambda que vous passez à ContinueWith () sera également exécuté de manière asynchrone.
IMHO, c'est encore un problème causé par l'utilisation du chargement différé. Une fois que vous avez supprimé votre contexte, vous ne pouvez plus charger paresseux une propriété, car la suppression du contexte ferme la connexion sous-jacente au serveur de base de données.
Si le chargement paresseux est activé et que l'exception se produit après la portée de using
, veuillez consulter https://stackoverflow.com/a/21406579/870604