2013-02-17 2 views
12

У меня есть контроллер:DbContext были захоронены и autofac

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

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

The operation cannot be completed because the DbContext has been disposed.

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

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

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

+3

НИКОГДА не совершайте наш DbContext при утилизации. Dispose вызывается в случае исключения, но вы не хотите сохранять какие-либо изменения, когда это произойдет. – Steven

+0

@Steven: я удалил эту строку, так или иначе это не решит проблему. – user1260827

+1

Каждый раз, когда я видел SaveChanges внутри утилиты, у кодера возникали проблемы. Я еще не понял, какой шаблон дизайна он предлагает или рекомендует «совершает» или «сохраняет» в Dispose. Я собираюсь предложить вам переоценить план, чтобы сохранить его внутри. Как насчет обработки ошибок. почему вы связываете «коммиты» с изменениями в базе данных с сборкой мусора? Стоит прочитать http://msdn.microsoft.com/en-us/library/fs2xkftw%28VS.80%29.aspx Когда вы запускаете утилиту? возможно, начинайте здесь: http://stackoverflow.com/questions/898828/c-sharp-finalize-dispose-pattern –

ответ

19

При использовании метода 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(); 
+0

Я не использую Autofac, поэтому было интересно читать ... +1 хороший анализ и объяснение –

+0

Спасибо @soadyp ! Я использую Unity вместо Autofac. Хотя принципы одинаковы, информация о том, как каждый конкретный контейнер реализует управление жизненным циклом, будет отличаться. (Например, с Unity я использовал бы время жизни иерархического элемента для «IDatabaseContext» в сочетании с новым дочерним контейнером, созданным по запросу Http) –

+0

Что делать, если у меня было несколько ограниченных DbContexts? Зарегистрировать DbContext как ключ? – Chazt3n

0

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

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

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

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

Для примера кода дается ОП исправление будет что-то вроде этого:

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

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

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

Смежные вопросы