web-dev-qa-db-fra.com

Impossible d'accéder à un objet supprimé dans ASP.NET Core lors de l'injection de DbContext

Sur un projet ASP.NET Core, j'ai les éléments suivants au démarrage:

  services.AddDbContext<Context>(x => x.UseSqlServer(connectionString));

  services.AddTransient<IValidationService, ValidationService>();

  services.AddTransient<IValidator<Model>, ModelValidator>();

Le ValidationService est comme suit:

public interface IValidationService {
    Task<List<Error>> ValidateAsync<T>(T model);
}

public class ValidationService : IValidationService {
    private readonly IServiceProvider _provider;

    public ValidationService(IServiceProvider provider) {
        _provider = provider;
    }

    public async Task<List<Error>> ValidateAsync<T>(T model) {
        IValidator<T> validator = _provider.GetRequiredService<IValidator<T>>();

        return await validator.ValidateAsync(model);
    }
}

Et le ModelValidator est comme suit:

public class ModelValidator : AbstractValidator<Model> {
  public ModelValidator(Context context) {
    // Some code using context
  }
}

Lorsque j'injecte un IValidationService dans un contrôleur et que je l'utilise comme:

List<Error> errors = await _validator.ValidateAsync(order);    

Je reçois l'erreur:

System.ObjectDisposedException: Impossible d'accéder à un objet supprimé. UNE La cause commune de cette erreur est la suppression d'un contexte qui a été résolu à partir de l'injection de dépendance et ensuite essayer d'utiliser la même chose instance de contexte ailleurs dans votre application. Cela peut arriver si vous êtes appelez Dispose () sur le contexte ou enveloppez-le dans un fichier using statement. Si vous utilisez une injection de dépendance, vous devriez laisser le conteneur d'injection de dépendance prendre en charge le contexte les instances. Nom de l'objet: 'Context'.

Toute idée de la raison pour laquelle je rencontre cette erreur lors de l'utilisation de Context dans ModelValidator.

Comment régler ceci?

METTRE &AGRAVE; JOUR

J'ai donc changé le code en:

services.AddScoped<IValidationService, ValidationService>();

services.AddScoped<IValidator<Model>, ModelValidator>();

Mais je reçois la même erreur ...

UPDATE - Seed code de données dans la méthode Configure au démarrage

Donc, configurez la méthode que j'ai:

if (hostingEnvironment.IsDevelopment())
  applicationBuilder.SeedData();

Et l'extension SeedData est:

public static class DataSeedExtensions {
    private static IServiceProvider _provider;

    public static void SeedData(this IApplicationBuilder builder) { 
        _provider = builder.ApplicationServices;
        _type = type;

        using (Context context = (Context)_provider.GetService<Context>()) {
            await context.Database.MigrateAsync();
            // Insert data code
    }
}

Qu'est-ce que je rate?

MISE À JOUR - Une solution possible

Changer ma méthode Seed par ce qui suit semble fonctionner:

using (IServiceScope scope = 
    _provider.GetRequiredService<IServiceScopeFactory>().CreateScope()) {
    Context context = _provider.GetService<Context>();
    // Insert data in database
}
18
Miguel Moura

Mise à jour pour ASP.NET Core 2.1

Dans ASP.NET Core 2.1, les méthodes ont légèrement changé. La méthode générale est similaire à la version 2.0, seuls le nom des méthodes et les types de retour ont été modifiés. 

public static void Main(string[] args)
{
    CreateWebHostBuilder(args)
        .Build()
        .Seed();
}

public static IWebHostBuilder CreateWebHostBuilder(string[] args)
{
    return new WebHostBuilder()
        ...; // Do not call .Build() here
}

S'applique à ASP.NET Core 2.0

Avec ASP.NET Core 2.0, la manière dont les outils EF Core (dotnet ef migrations, etc.) déterminent DbContext et la chaîne de connexion au moment de la conception a été modifiée. 

La réponse ci-dessous indique que les migrations et l'amorçage sont appliqués lorsque vous appelez l'une des commandes dotnet ef xxx

Le nouveau modèle permettant d'obtenir une instance de conception pour les outils EF Core consiste à utiliser une méthode statique BuildHostWeb.

Conformément à cette annonce , EF Core utilisera désormais la méthode statique BuildWebHost qui configure l’application entière, mais ne l’exécute pas. 

  public class Program
  {
      public static void Main(string[] args)
      {
          var Host = BuildWebHost(args);

          Host.Run();
      }

      // Tools will use this to get application services
      public static IWebHost BuildWebHost(string[] args) =>
          new WebHostBuilder()
              .UseKestrel()
              .UseContentRoot(Directory.GetCurrentDirectory())
              .UseIISIntegration()
              .UseStartup<Startup>()
              .Build();
  }

Remplacez-le dans votre ancienne méthode Main

public static void Main(string[] args)
{
    var Host = BuildWebHost(args)
        .Seed();

    Host.Run();
}

Où Seed est une méthode d'extension:

public static IWebHost Seed(this IWebHost webhost)
{
    using (var scope = webhost.Services.GetService<IServiceScopeFactory>().CreateScope())
    {
        // alternatively resolve UserManager instead and pass that if only think you want to seed are the users     
        using (var dbContext = scope.ServiceProvider.GetRequiredService<ApplicationDbContext>()) 
        {
            SeedData.SeedAsync(dbContext).GetAwaiter().GetResult();
        }
    }
}

public static class SeedData
{
    public static async Task SeedAsync(ApplicationDbContext dbContext)
    {
        dbContext.Users.Add(new User { Id = 1, Username = "admin", PasswordHash = ... });
    }
}

Ancienne réponse, s'applique toujours à ASP.NET Core 1.x

Vous devez appliquer le modèle semi-officiel sur la façon de créer le noyau Entity Framework Core dans une application ASP.NET Core, car lors du démarrage de l'application, il n'y a pas de requête et donc pas de variable RequestServices (qui résout les services limités). 

En gros, il s’agit de créer une nouvelle étendue, de résoudre les types dont vous avez besoin et de disposer à nouveau de l’étendue une fois que vous avez terminé. 

// serviceProvider is app.ApplicationServices from Configure(IApplicationBuilder app) method
using (var serviceScope = serviceProvider.GetRequiredService<IServiceScopeFactory>().CreateScope())
{
    var db = serviceScope.ServiceProvider.GetService<AppDbContext>();

    if (await db.Database.EnsureCreatedAsync())
    {
        await SeedDatabase(db);
    }
}

L'une des raisons qui résolvent directement un service via app.ApplicationServices.GetService<MyService>() est que ApplicationServices est le fournisseur de portée de l'application (ou de la durée de vie) et que les services résolus ici restent en vie jusqu'à la fermeture de l'application. 

Habituellement, le conteneur scoped sera résolu à partir de son conteneur parent, si l'objet y existe déjà. Donc, si vous instanciez le DbContext de cette manière dans l'application, il sera disponible dans le conteneur ApplicationServices et lorsqu'une demande surviendra, un conteneur enfant sera créé. 

Désormais, lors de la résolution du DbContext, il ne sera pas résolu comme étant périmé, car il existe déjà dans le conteneur parent. Par conséquent, l'instance du conteneur parent sera renvoyée à la place. Mais comme il a été éliminé pendant le semis, il ne sera pas accessible. 

Un conteneur d'étendue n'est rien d'autre qu'un conteneur singleton avec une durée de vie limitée.

Par conséquent, ne résolvez jamais les services ciblés au démarrage d’application sans utiliser le modèle ci-dessus, qui consiste à créer d’abord une portée et à la résoudre. 

16
Tseng

Juste une estimation de la cause de votre erreur:

Vous utilisez des appels DI et asynchrones. Si quelque part dans votre pile d'appels vous retournez un vide au lieu de Task, vous obtenez le comportement décrit. À ce stade, l'appel est terminé et le contexte éliminé. Vérifiez donc si vous avez un appel asynchrone qui renvoie un vide au lieu de Tâche. Si vous modifiez la valeur de retour, objectdisposedexception est probablement corrigé.

public static class DataSeedExtensions {
private static IServiceProvider _provider;

public static async Task SeedData(this IApplicationBuilder builder) { //This line of code

  _provider = builder.ApplicationServices;
  _type = type;

  using (Context context = (Context)_provider.GetService<Context>()) {

    await context.Database.MigrateAsync();
    // Insert data code

  }

}

Et en configuration:

if (hostingEnvironment.IsDevelopment()){
   await  applicationBuilder.SeedData();
}

Article de blog sur la façon de corriger cette erreur: impossible-d'accéder-à-un-objet-disposé-dans-asp-net-core-when-injecting-dbcontext

34
Peter

J'ai eu un problème similaire en travaillant avec asp.net core. J'ai une méthode async POST dans mon contrôleur et quand elle retournera vide, j'aurai cette exception Après avoir modifié la méthode POST, renvoyer une tâche, le problème a été résolu. 

Changer de:

public async void PostAsync([FromBody] Model yourmodel)

À

public async Task PostAsync([FromBody] Model yourmodel)
8
Yang Zhang

le problème est que DBContext est défini par requête par défaut, mais que certaines choses dépendent de ce dernier comme transitoire, elles n'ont donc pas la même portée et DBContext peut être supprimé avant que vous n'ayez fini de l'utiliser.

1
Joe Audette

Semblable à Yang Zhang, je devais changer la fonction de mon contrôleur

 public IActionResult MyFunc([FromBody]string apiKey)

À:

 public async Task<IActionResult> MyFunc([FromBody]string apiKey)
0
Marcus Talcott