web-dev-qa-db-fra.com

Entity Framework, DBContext et using () + async?

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 DbContexten 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 dbconnectiondoit ê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 asyncand awaitname__. EF 6.0 utilise toutes ces méthodes asynchrones (par exemple SaveChangesAsyncname__, ToListAsyncname__, etc.).

public Task<tblLanguage> Post(tblLanguage language)
{
    using (var langRepo = new TblLanguageRepository(new Entities()))
    {
        return langRepo.Add(RequestOrganizationTypeEnum, language);
    }
}

Dans la classe TblLanguageReponame__:

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 usingest 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?

23
Objective Coder

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 .

24
Stephen Cleary

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:

  • certaines entités peuvent déjà être matérialisées dans le DbContext à partir de requêtes précédentes, en enregistrant des requêtes supplémentaires.
  • vous n'avez pas toutes ces déclarations using supplémentaires encombrant votre code.
4
Dirk Boer

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;
    }
}
1
danludwig

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. 

1
Scottie

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.

0
Ross Brigoli

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

0
ken2k