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

С# entity framework: правильное использование класса DBContext внутри вашего класса репозитория

Я использовал для реализации моих классов репозитория, как вы можете видеть ниже

public Class MyRepository
{
      private MyDbContext _context; 

      public MyRepository(MyDbContext context)
      {
          _context = context;
      }

      public Entity GetEntity(Guid id)
      {
          return _context.Entities.Find(id);
      }
}

Однако я недавно прочитал эту статью, в которой говорится, что плохая практика заключается в том, что контекст данных является частным членом вашего репозитория: http://devproconnections.com/development/solving-net-scalability-problem

Теперь, теоретически статья правильная: поскольку DbContext реализует IDisposable, наиболее правильная реализация будет следующей.

public Class MyRepository
{
      public Entity  GetEntity(Guid id)
      {
          using (MyDbContext context = new MyDBContext())
          {
              return context.Entities.Find(id);
          }
      }
}

Однако, согласно этой другой статье, утилита DbContext не была бы существенной: http://blog.jongallant.com/2012/10/do-i-have-to-call-dispose-on-dbcontext.html

Какая из двух статей правильная? Я совершенно смущен. Наличие DbContext как частного члена в вашем классе репозитория может действительно вызвать "проблемы масштабируемости", как предлагает первая статья?

4b9b3361

Ответ 1

Я думаю, что вы не должны следовать первой статье, и я расскажу вам, почему.

Следуя первому подходу, вы теряете почти все функции, которые Entity Framework предоставляет через DbContext, включая кеш 1-го уровня, свою идентификационную карту, свою единицу работы и отслеживание изменений, способности загрузки. Это связано с тем, что в приведенном выше сценарии новый экземпляр DbContext создается для каждого запроса базы данных и размещается сразу же после этого, поэтому предотвращает возможность экземпляра DbContext отслеживать состояние ваших объектов данных во всей бизнес-транзакции.

Наличие DbContext как частного свойства в вашем классе репозитория также имеет свои проблемы. Я считаю, что лучшим подходом является CustomDbContextScope. Этот подход очень хорошо объясняется этим парнем: Мехди Эль Гуддари

Эта статья http://mehdi.me/ambient-dbcontext-in-ef6/ одна из лучших статей о EntityFramework, которую я видел. Вы должны прочитать его полностью, и я верю, что он ответит на все ваши вопросы.

Ответ 2

Предположим, у вас есть несколько репозиториев, и вам нужно обновить 2 записи из разных репозиториев. И вам нужно сделать это транзакционным (если не удастся - оба отката обновлений):

var repositoryA = GetRepository<ClassA>();
var repositoryB = GetRepository<ClassB>();

repository.Update(entityA);
repository.Update(entityB);

Итак, если у вас есть собственный DbContext для каждого репозитория (случай 2), для этого вам нужно использовать TransactionScope.

Лучший способ - иметь один общий DbContext для одной операции (для одного вызова, для одного единицы работы). Таким образом, DbContext может управлять транзакцией. EF вполне подходит для этого. Вы можете создать только один DbContext, внести все изменения во многие репозитории, вызвать SaveChanges один раз, удалить его после всех операций и работы.

Здесь приведен пример реализации шаблона UnitOfWork.

Второй способ может быть полезен для операций только для чтения.

Ответ 3

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

Здесь "транзакция" может ссылаться на запрос только для чтения или на запрос записи. И, как вы уже знаете, транзакция должна быть как можно короче.

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

Единственный случай, который я могу использовать для использования частного члена, - это шаблон CQRS (CQRS: кросс-экспертиза того, как это работает).

Кстати, ответ Диего Веги в сообщении также дает несколько мудрых советов:

Есть две основные причины, по которым наш примерный код всегда использует "использование", или распоряжаться контекстом каким-либо другим способом:

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

  • DbContext реализует IDiposable в соответствии с рекомендуемым шаблоном, который включает в себя предоставление виртуального защищенного метода Dispose, который производные типы могут переопределяться, если, например, необходимо агрегировать другие неуправляемые ресурсы в течение жизни контекста.

НТН

Ответ 4

Какой подход к использованию зависит от ответственности репозитория.

Ответственность за выполнение полных транзакций лежит на хранилище? т.е. внести изменения и затем сохранить изменения в базе данных, вызвав "SaveChanges"? Или это только часть более крупной транзакции, и поэтому она будет вносить изменения без их сохранения?

Случай №1) Репозиторий будет выполнять полные транзакции (он внесет изменения и сохранит их):

В этом случае второй подход лучше (подход вашего второго образца кода).

Я немного изменю этот подход, введя factory следующим образом:

public interface IFactory<T>
{
    T Create();
}

public class Repository : IRepository
{
    private IFactory<MyContext> m_Factory;

    public Repository(IFactory<MyContext> factory)
    {
        m_Factory = factory;
    }

    public void AddCustomer(Customer customer)
    {
        using (var context = m_Factory.Create())
        {
            context.Customers.Add(customer);
            context.SaveChanges();
        }            
    }
}

Я делаю это небольшое изменение, чтобы включить Injection of Dependency. Это позволяет нам позже изменить способ создания контекста.

Я не хочу, чтобы репозиторий отвечал за создание контекста. factory, который реализует IFactory<MyContext>, будет нести ответственность за создание контекста.

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

Случай №2) Репозиторий является частью более крупной транзакции (он внесет некоторые изменения, другие репозитории будут делать другие изменения, а затем кто-то еще совершит транзакцию, вызвав SaveChanges):

В этом случае лучше всего использовать первый подход (который вы описываете первым в своем вопросе).

Представьте, что это происходит, чтобы понять, как репозиторий может быть частью более крупной транзакции:

using(MyContext context = new MyContext ())
{
    repository1 = new Repository1(context);
    repository1.DoSomething(); //Modify something without saving changes
    repository2 = new Repository2(context);
    repository2.DoSomething(); //Modify something without saving changes

    context.SaveChanges();
}

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

Обратите внимание, что я обновляю репозиторий в своем коде (что является нарушением инъекции зависимостей). Я просто показываю это как пример. В реальном коде мы можем использовать фабрики для решения этой проблемы.

Теперь одно усовершенствование, которое мы можем сделать для этого подхода, - это скрыть контекст интерфейса, чтобы хранилище больше не имело доступа к SaveChanges (взгляните на Принцип разделения цепей).

У вас может быть что-то вроде этого:

public interface IDatabaseContext
{
    IDbSet<Customer> Customers { get; }
}

public class MyContext : DbContext, IDatabaseContext
{
    public IDbSet<Customer> Customers { get; set; }
}


public class Repository : IRepository
{
    private IDatabaseContext m_Context;

    public Repository(IDatabaseContext context)
    {
        m_Context = context;
    }

    public void AddCustomer(Customer customer)
    {
        m_Context.Customers.Add(customer);      
    }
}

Вы можете добавить другие методы, которые вам нужны для интерфейса, если вы хотите.

Обратите внимание, что этот интерфейс не наследуется от IDisposable. Это означает, что класс Repository не отвечает за управление временем жизни контекста. Контекст в этом случае имеет больший срок службы, чем репозиторий. Кто-то еще будет управлять временем жизни контекста.

Примечания к первой статье:

В первой статье предполагается, что вы не должны использовать первый подход, который вы описываете в своем вопросе (введите контекст в репозиторий).

В статье не ясно, как используется репозиторий. Используется ли она как часть одной транзакции? Или он охватывает несколько транзакций?

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

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

Примечания к второй статье:

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

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

Обратите внимание, что в двух подходах к дизайну мы избавляемся от контекста. Единственное различие заключается в том, кто несет ответственность за такое удаление.

В статье говорится, что DbContext, по-видимому, очищает ресурсы без необходимости явно выделять контекст.

Ответ 5

В первой статье, которую вы указали, забыли уточнить одну важную вещь: что такое время жизни так называемых экземпляров NonScalableUserRepostory (также забыл сделать NonScalableUserRepostory реализует IDisposable тоже, чтобы правильно распорядиться DbContext).

Представьте себе следующий случай:

public string SomeMethod()
{
    using (var myRepository = new NonScalableUserRepostory(someConfigInstance))
    {
        return myRepository.GetMyString();
    }
}

Ну... внутри класса NonScalableUserRepostory все равно будет какое-то частное DbContext, но контекст будет использоваться только один раз. Так что это точно так же, как то, что статья описывает как наилучшую практику.

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

Тогда ответ будет: старайтесь как можно больше сократить его. Там понятие Unit of Work, которое представляет собой бизнес-операцию. В основном вы должны иметь новый DbContext для каждой единицы работы.

Как определяется единица работы и как она будет реализована, зависит от характера вашего приложения; например, для приложения ASP.Net MVC, время жизни DbContext обычно является временем жизни HttpRequest, то есть каждый новый контекст создается каждый раз, когда пользователь генерирует новый веб-запрос.


ИЗМЕНИТЬ:

Чтобы ответить на ваш комментарий:

Одним из решений было бы ввести через конструктор метод factory. Вот несколько основных примеров:

public class MyService : IService
{
    private readonly IRepositoryFactory repositoryFactory;

    public MyService(IRepositoryFactory repositoryFactory)
    {
        this.repositoryFactory = repositoryFactory;
    }

    public void BusinessMethod()
    {
        using (var repo = this.repositoryFactory.Create())
        {
            // ...
        }
    }
}

public interface IRepositoryFactory
{
    IRepository Create();
}

public interface IRepository : IDisposable
{
    //methods
}

Ответ 6

1-й код не имеет отношения к проблеме масштабируемости, причина в том, что он плохой, потому что он создает новый контекст для каждого репозитория, который является плохим, что один из комментаторов комментирует, но он не смог даже ответить. В Интернете было 1 запрос 1 dbContext, если вы планируете использовать шаблон репозитория, то он переводится в 1 запроs > много репозитория > 1 dbContext. это легко достичь с помощью IoC, но не обязательно. так вы делаете это без IoC:

var dbContext = new DBContext(); 
var repository = new UserRepository(dbContext); 
var repository2 = new ProductRepository(dbContext);
// do something with repo

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

Ответ 7

В основном класс DbContext - это не что иное, как оболочка, которая обрабатывает все связанные с базой данных вещи: 1. Создать соединение 2. Выполните запрос. Теперь, если мы делаем вышеупомянутый материал с использованием обычного ado.net, нам нужно явно закрыть соединение правильно либо путем написания кода в инструкции using, либо путем вызова метода close() объекта класса соединения.

Теперь, когда класс контекста реализует внутреннюю внутреннюю структуру IDisposable, хорошей практикой является запись dbcontext в использовании оператора, так что нам не нужно заботиться о закрытии соединения.

Спасибо.

Ответ 8

Я использую первый способ (вводя dbContext). Конечно, это должен быть IMyDbContext, и ваш механизм впрыска зависимостей управляет жизненным циклом контекста, поэтому он доступен только в реальном времени, пока он требуется.

Это позволяет вам обмануть контекст для тестирования, второй способ делает невозможным просмотр объектов без базы данных для использования контекста.

Ответ 9

Второй подход (использование) лучше, поскольку он, безусловно, поддерживает соединение только в течение минимально необходимого времени и легче сделать потокобезопасным.

Ответ 10

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

 public class DatabaseFactory : Disposable, IDatabaseFactory {
    private XXDbContext dataContext;

    public ISefeViewerDbContext Get() {
        return dataContext ?? (dataContext = new XXDbContext());
    }

    protected override void DisposeCore() {
        if (dataContext != null) {
            dataContext.Dispose();
        }
    }
}

И используйте этот экземпляр в репозитории:

public class Repository<TEntity> : IRepository<TEntity> where TEntity : class
{
    private IXXDbContext dataContext;

    private readonly DbSet<TEntity> dbset;

    public Repository(IDatabaseFactory databaseFactory) {
        if (databaseFactory == null) {
            throw new ArgumentNullException("databaseFactory", "argument is null");
        }
        DatabaseFactory = databaseFactory;
        dbset = DataContext.Set<TEntity>();
    }

    public ISefeViewerDbContext DataContext {
        get { return (dataContext ?? (dataContext = DatabaseFactory.Get()));
    }

    public virtual TEntity GetById(Guid id){
        return dbset.Find(id);
    }
....