Не удается получить доступ к расположенному объекту в ASP.NET Core при инъекции DbContext

В проекте ASP.NET Core у меня есть следующее при запуске:

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

  services.AddTransient<IValidationService, ValidationService>();

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

Служба ValidationService выглядит следующим образом:

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);

}

}

И ModelValidator выглядит следующим образом:

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

Когда я вставляю IValidationService в контроллер и использую его как:

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

Я получаю сообщение об ошибке:

System.ObjectDisposedException: Cannot access a disposed object. A common cause of this error is disposing a context that was resolved from dependency injection and then later trying to use the same context instance elsewhere in your application. 
This may occur is you are calling Dispose() on the context, or wrapping the context in a using statement. 
If you are using dependency injection, you should let the dependency injection container take care of disposing context instances.
Object name: 'Context'.    

Любая идея, почему у меня возникает эта ошибка при использовании Context внутри ModelValidator.

Как это исправить?

UPDATE

Итак, я изменил код на:

services.AddScoped<IValidationService, ValidationService>();

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

Но я получаю ту же ошибку...

UPDATE - Код данных семян внутри Configure method при запуске

Итак, на Configure method у меня есть:

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

И расширение SeedData:

открытый статический класс 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

  }

}

Что мне не хватает?

ОБНОВЛЕНИЕ - Возможное решение

Как мне кажется, работает мой метод Seed:

using (IServiceScope scope = _provider.GetRequiredService<IServiceScopeFactory>().CreateScope()) {

  Context context = _provider.GetService<Context>();

  // Insert data in database

}

Ответ 1

Новый ответ применяется для ASP.NET Core 2.x и более поздних

В ASP.NET Core 2.0 произошли некоторые изменения в том, как инструменты EF Core (dotnet ef migrations и т.д.) определяют строку DbContext и строку соединения во время разработки.

В приведенном ниже ответе указывается, что миграция и посев применяются при вызове любой из команд dotnet ef xxx.

Новый шаблон для получения экземпляра времени проектирования для основных инструментов EF - это статический метод BuildHostWeb.

По это объявление, EF Core теперь будет использовать статический метод BuildWebHost, который настраивает все приложение, но не запускается Это.

  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();
  }

Замените это в своем старом методе Main

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

    host.Run();
}

Где Seed - метод расширения:

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 = ... });
    }
}

Старый ответ, по-прежнему применяется к ASP.NET Core 1.x

Существует полуофициальный шаблон о том, как вырезать ядро ​​Entity Framework Core в приложении ASP.NET Core, которое вы должны применять, потому что во время запуска приложения нет запроса и, следовательно, нет RequestServices (который разрешает услуги с областью действия).

В сущности, это сводится к созданию новой области, разрешению типов, которые вам нужны, и удалению области снова, как только вы закончите.

// 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);
    }
}

Одной из причин, непосредственно разрешающих услугу через app.ApplicationServices.GetService<MyService>(), является то, что ApplicationServices является поставщиком области применения (или срока службы), и разрешенные здесь службы остаются в силе до тех пор, пока приложение не будет закрыто.

Обычно контейнер с областью разрешает его родительский контейнер, если объект уже существует. Поэтому, если вы создадите экземпляр DbContext таким образом в приложении, он будет доступен в контейнере ApplicationServices, и когда произойдет запрос, будет создан дочерний контейнер.

Теперь при разрешении DbContext он не будет разрешен как область действия, поскольку он уже существует в родительском контейнере, поэтому вместо этого будет возвращен экземпляр родительского контейнера. Но поскольку он был использован во время посева, он не будет доступен.

Контейнер объема - это не что иное, как однотонный контейнер с ограниченным сроком службы.

Поэтому никогда не разрешайте облачные службы при запуске приложения без использования шаблона выше, сначала создавая область действия и разрешая ее.

Ответ 2

Просто угадайте, в чем причина вашей ошибки:

Вы используете DI и асинхронные вызовы. Если где-то в вашем стеке вызовов вы возвращаете пустоту вместо задачи, вы получаете описанное поведение. В этот момент вызов завершается и контекст удаляется. Поэтому проверьте, есть ли у вас асинхронный вызов, который возвращает пустоту вместо задачи. Если вы измените возвращаемое значение, исключение objectdisposed, вероятно, будет исправлено.

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

  }

}

И в настройке:

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

Сообщение в блоге о том, как исправить эту ошибку: cannot-access-a-disposed-object-in-asp-net-core-when-injecting-dbcontext

Ответ 3

проблема заключается в том, что по умолчанию DBContext имеет область видимости для каждого запроса, но у вас есть вещи, которые зависят от него, как временные, поэтому они не имеют одинаковой области видимости, а DBC-текст может быть удален до того, как вы закончите использовать его

Ответ 4

У меня была аналогичная проблема с ядром asp.net. У меня есть метод async POST в моем контроллере, и когда он возвращает void, у меня будет это исключение. После того, как я изменил метод POST, верните TASK, проблема была решена.

Изменить:

public async void PostAsync([FromBody] Model yourmodel)

Для

public async Task PostAsync([FromBody] Model yourmodel)