Как развернуть Dbcontexts (чтобы предотвратить одноэлементный контекст для всего приложения)

Мне было интересно, как вы раскрываете свои Dbcontexts в Entity Framework, чтобы не использовать один Dbcontext для всего вашего приложения. Я новичок в Entity Framework и читаю учебные пособия, но все они использовали один Dbcontext в качестве примера, поэтому EF для меня сейчас является черным ящиком.

Скажем, например, у меня есть 3 модели:

  • Сообщение
  • Пользователь
  • Комментарий

Каждая модель связана друг с другом (сообщение принадлежит пользователю, комментарий принадлежит пользователю и сообщению). Я делаю Dbcontext для каждого отдельно? Но это было бы неправильно, поскольку все они связаны друг с другом, или я бы создал Dbcontext для каждого сценария, который мне нужен? Например, если мне нужно только запрашивать сообщение и комментарии, а не пользователь, это будет PostCommentsContext. И тогда у нас будет PostUserCommentContext...

Ответ 1

Лучшим решением было бы использовать Unit of Work, чтобы обернуть контекст данных, а также управлять временем жизни соединения и позволить вам работать с несколькими репозиториями (если вы так склонны идти вниз по этому пути).

Краткое описание реализации:

  • Создайте интерфейс (IUnitOfWork), который предоставляет свойства для DbSet, а также один метод под названием Commit
  • Создайте реализацию (EntityFrameworkUnitOfWork), выполнив при необходимости. Commit просто вызывает SaveChanges в базовом классе (DbContext), а также обеспечивает хороший крючок для последней минуты.
  • Ваш контроллер принимает IUnitOfWork, используйте DI (желательно), чтобы разрешить EntityFrameworkUnitOfWork, с параметром с привязкой к контексту HTTP ( StructureMap подходит для этого)
  • (необязательно, но рекомендуется) создать репозиторий, который также принимает IUnitOfWork, и отработает это через ваш контроллер.

НТН

РЕДАКТИРОВАТЬ - В ответ на комментарии

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

Учитывая, что вы используете ASP.NET MVC, ваши контроллеры должны принимать IUnitOfWork в своем конструкторе.

Вот пример, основанный на том, что вы просили

public SomeController : Controller
{
   private IUnitOfWork _unitOfWork;
   private IUserRepo _userRepo;
   private IPostRepo _postRepo;

   public SomeController(IUnitOfWork unitOfWork, IUserRepo userRepo, IPostRepo postRepo)
   {
      _unitOfWork = unitOfWork; // use DI to resolve EntityFrameworkUnitOfWork
      _userRepo = userRepo;
      _postRepo = postRepo;
   }

   [HttpPost]
   public ActionResult CreateUserAndPost(User user, Post post)
   {
      // at this stage, a HTTP request has come in, been resolved to be this Controller
      // your DI container would then see this Controller needs a IUnitOfWork, as well
      // as two Repositories. DI smarts will resolve each dependency.
      // The end result is a single DataContext (wrapped by UoW) shared by all Repos.
      try
      {
         userRepo.Add(user);
         postRepo.Add(post);
         // nothing has been sent to DB yet, only two objects in EF graph set to EntityState.Added
         _unitOfWork.Commit(); // two INSERT pushed to DB
      }
      catch (Exception exc)
      {
          ModelState.AddError("UhOh", exc.ToString());
      }
   }
}

И еще один вопрос: что такое продолжительность жизни, связанная с HTTP-контекстом?

Объекты DI-talk имеют параметры управления областью, которые включают в поток, за сеанс, для каждого запроса HTTP, singleton и т.д.

Контекст HTTP-контекста является рекомендуемым параметром для веб-приложений. Это означает "новый контекст, когда приходит HTTP-запрос и избавляется от него, когда запрос завершен".

Ответ 2

Использовать 1 DbContext! Это облегчит вам жизнь. Не беспокойтесь о производительности, данные, которые не нужны или не запрашиваются, не будут загружены и не будут потреблять какие-либо ресурсы.

public class UserContext : DbContext
{
    public DbSet<User> Users { get; set; }
    public DbSet<Post> Posts { get; set; }
    public DbSet<Comment> Comments { get; set; }
}

Для некоторых сценариев вам может понадобиться два или более контекста.

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

Ответ 3

Я экспериментирую с UnitofWork, вот что я придумал...

Сначала я создал IUnitofWork, который содержит только один метод. Commit();

Тогда мой dbContext выглядит следующим образом

public class myContext : DbContext, IUnitOfWork
{
    public DbSet<Users> Users { get; set; }
    public DbSet<Addresses> Address { get; set; }

    public void Save()
    {
        SaveChanges();
    }
}

Мои классы хранилища берут UnitofWork в их ctors.

public class UserRepository : IRepository<Position>    
{
    private myContext _context;

    public UserRepository (IUnitOfWork unitOfWork)
    {
        if (unitOfWork == null)
            throw new ArgumentNullException("unitOfWork");

        _context = unitOfWork as myContext;
    }
    /// other methods ///
}

Тогда код в контроллере будет что-то вроде этого

_unitOfWork = new myContext();
_userDB = new UserRepository(_unitOfWork);
_addressDB = new AddressRepository(_unitOfWork);
_userDB.Add(newUser);
_addresesDB.Add(newAddress);
_unitOfWork.Save();

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