Подтвердить что ты не робот

DbContext был удален и автозаполнен

У меня есть контроллер:

private readonly ILogger _logger;    
private readonly IRepository _repository;

public HomeController(ILogger logger, IRepository repository)
{
   _logger = logger;
   _repository = repository;
}

Это репозиторий:

public class EfRepository : IRepository
{
    // ...methods for add, delete, update entities
    // ....

    public void Dispose()
    {
         if (this._context != null)
         {
             this._context.SaveChanges();
             (this._context as IDisposable).Dispose();
             this._context = null;
         }
    }
}

Наконец, типы регистрации в IoC:

_builder.RegisterType<Logger>().As<ILogger>();
_builder.RegisterType<EfRepository>().As<IRepository>().WithParameter("context", new PcpContext());

Когда я запускаю приложение, я получаю эту ошибку:

Операция не может быть выполнена, поскольку DbContext был расположены.

Я попытался изменить регистрацию EfRepository следующим образом:

_builder.RegisterType<EfRepository>()
   .As<IRepository>()
   .WithParameter("context", new PcpContext()).InstancePerLifetimeScope();

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

4b9b3361

Ответ 1

При использовании метода WithParameter экземпляр параметра будет одинаковым для каждого разрешенного объекта. Таким образом, при .WithParameter("context", new PcpContext()) вы эффективно используете один и тот же экземпляр класса PcpContext для любого разрешенного экземпляра IRepository.

С вашим текущим кодом, когда экземпляр IRepository размещен, он также удалит экземпляр PcpContext. Тогда любая последующая попытка разрешить IRepository получит экземпляр PcpContext, который был удален. Вам нужен способ получить новый новый экземпляр EF DbContext для каждого запроса Http, который удаляется в конце запроса.

Один из вариантов может состоять в том, чтобы зарегистрировать блок кода для IRepository, чтобы блок кода выполнялся каждый раз, когда IRepository необходимо разрешить:

_builder.Register<IRepository>(c => new EfRepository(new PcpContext()))

Лучшим вариантом было бы создать новую абстракцию IDatabaseContext, обновив EfRepository, поэтому она зависит от новой абстракции IDatabaseContext вместо класса PcpContext (что уже может быть в случае:)).

Класс реализации для IDatabaseContext будет вашим классом PcpContext, который должен наследоваться от EF DbContext и, вероятно, получит строку подключения в качестве параметра.

public class EfRepository : IRepository
{
    private readonly IDatabaseContext _context;

    public EfRepository(IDatabaseContext context)
    {
        _context = context;
    }

    ...methods for add, delete, update entities

    //There is no longer need for this to be disposable.
    //The disaposable object is the database context, and Autofac will take care of it
    //public void Dispose()
}

public interface IDatabaseContext : IDisposable 
{
    ... declare methods for add, delete, update entities
}

public class PcpContext: DbContext, IDatabaseContext 
{
    public EntityFrameworkContext(string connectionString)
        : base(connectionString)
    {
    }

    ...methods exposing EF for add, delete, update entities

    //No need to implement IDisposable as we inherit from DbContext 
    //that already implements it and we don´t introduce new resources that should be disposed of
}

Это становится лучше с идеей использования контейнера IoC и оставления бремени управления жизненным циклом. Теперь ваш класс репозитория не должен быть одноразовым и не должен управлять и распоряжаться его зависимостью IDatabaseContext. Это Autofac, который будет отслеживать экземпляр контекста и распоряжаться им, когда это необходимо.

По той же причине вы, вероятно, захотите использовать InstancePerLifetimeScope с зависимостью контекста базы данных. Это означало бы, что один и тот же контекст EF используется для каждого экземпляра репозитория в одном и том же запросе Http и размещается в конце запроса.

_builder.RegisterType<EfRepository>()
   .As<IRepository>();

_builder.RegisterType<PcpContext>()
   .As<IDatabaseContext>()
   .WithParameter("connectionString", "NameOfConnStringInWebConfig")
   .InstancePerLifetimeScope();

Ответ 2

Я пошел с простым решением "кодового блока", как предположил @Daniel J.G(лямбда).

Ниже приведен пример кода в Autofac. Пример Дэниелса - для Единства, поскольку он также упоминает о себе. Поскольку OP добавил Autofac в качестве тега, это показалось мне важным:

_builder.Register(c => new AppDbContext()).As(typeof(AppDbContext));

Этот код решил проблему DbContext has been disposed, которую я имел с Entity Framework. Обратите внимание, что по сравнению с большинством других контейнеров DI, в том числе Unity, Autofac переключается вокруг зарегистрированной вещи и того, что она разрешает.

Для примера кода, данного OP, исправление будет примерно таким:

_builder.Register(c => new EfRepository(new PcpContext())).As(IRepository);

Обратите внимание, что этот последний бит является непроверенным кодом. Но в любом случае вы должны обратиться к Дэниелсу за дополнительной информацией, потому что я думаю, что он прав с "лучшим вариантом". Но вы можете использовать мой вариант решения, если у вас нет времени прямо сейчас (например, я). Просто добавьте TODO, чтобы вы могли справиться с технической задолженностью, которую вы наносите:).

Когда я это сделаю, я посмотрю, смогу ли я обновить этот ответ рабочим кодом для Autofac, который следует его "лучшей опции". Сначала я хочу внимательно прочитать в этой статье. При быстром чтении мне кажется, что люди Autofac продвигают использование "Локатора сервисов" для обработки жизненного пространства. Но, по словам Марка Сееманна, анти-шаблон, так что у меня есть кое-что, чтобы понять... Любой эксперт DI с мнением?