Entity Framework DbContext Lifetime в ASP.NET MVC Использование Ninject?

У меня есть следующая единица рабочего шаблона, настроенная для приложения MVC 5 с использованием Entity Framework. Единица работы имеет все репозитории, определенные следующим образом, так что все они используют один и тот же dbcontext, и у него есть один метод сохранения для координации транзакции с использованием одного и того же контекста:

public class UnitOfWork : IUnitOfWork
{
    private readonly ApplicationDbContext _context;

    public IProductRepository ProductRepository { get; private set; }
    public ICustomerRepository CustomerRepository { get; private set; }
    // Other reposistories

    public UnitOfWork(ApplicationDbContext context)
    {
        _context = context;
        ProductRepository = new ProductRepository(_context);
        CustomerRepository = new CustomerRepository(_context);

        // Other reposistories
    }

    public void Complete()
    {
        _context.SaveChanges();
    }
}

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

public class ProductRepository : IProductRepository
{
    private readonly ApplicationDbContext _context;

    public ProductRepository(ApplicationDbContext context)
    {
        _context = context;
    }

    public Product GetProduct(int productId)
    {
       return _context.Ticket.SingleOrDefault(p => p.Id == productId);
    }

     public void Add(Product product)
    {
        _context.Product.Add(product);
    }

    // Other methods
}

Я ввожу единицу рабочего класса в свой контроллер следующим образом, используя Ninject:

 public class ProductsController : Controller
{

    private readonly IUnitOfWork _unitOfWork;
    private readonly IFileUploadService _FileUploadService;

    public ProductsController(IUnitOfWork unitOfWork, 
            IFileUploadService fileUploadService)
    {
        _unitOfWork = unitOfWork; 
        _FileUploadService = fileUploadService;
    }

    [HttpPost]
    [ValidateAntiForgeryToken]
    public ActionResult Create(CreateEditProductViewModel viewModel)
    {
          var product = new Product
          {
              // Do stuff
          };

          _unitOfWork.ProductRepository.Add(product);

          // Call file upload service
          _fileUploadService.Upload();

          _unitOfWork.Complete();

    }

 }

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

public class FileUploadService : IFileUploadService
{
    private readonly IUnitOfWork _unitOfWork;

    public FileUploadService(IUnitOfWork unitOfWork)
    {
        _unitOfWork = unitOfWork;
    }

    public uploadResult Upload()
    {   
        // Some stuff
         var uploadedFile = new UploadedFile
         {
            //some stuff
         };

         _unitOfWork.UploadedFileRepository.Add(uploadedFile);
    }
 }  

Я провел немало исследований в Интернете, и мне не удалось найти какой-либо ресурс, который предоставил бы практический пример для решения этой проблемы. Я прочитал довольно много материала о ролевых единицах работы и репозиториях и просто об использовании сущностных фреймворков dbset. Однако, как объяснено выше, целью использования репо является объединение запросов. Мои вопросы: как мне согласовать единицу работы с классом обслуживания?

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

* ОБНОВИТЬ *

В моем DI-контейнере я разрешаю все интерфейсы, используя следующий фрагмент:

    private static IKernel CreateKernel()
    {

            RegisterServices(kernel);

            kernel.Bind<IUnitOfWork>().To<UnitOfWork>().InRequestScope();

            // default binding for everything except unit of work
            kernel.Bind(x => x.FromAssembliesMatching("*")
             .SelectAllClasses()
             .Excluding<UnitOfWork>()
             .BindDefaultInterface());

            return kernel;

    } 

Будет ли добавление строки kernel.Bind<IUnitOfWork>().To<UnitOfWork>().InRequestScope(); убедитесь, что создано не более одного ApplicationDbContext, даже если запрос завершается попаданием на несколько контроллеров или уровней обслуживания, для которых требуется IUnitOfWork (ApplicationDbContext)?

2 ответа

Если вы используете MVC, то ваша единица работы - это ваш веб-запрос. Если бы я был тобой, я бы отказался от реализации UOW и просто убедился, что твой dbcontext создан в Application_BeginRequest. Тогда я вставлю это в HttpContext для безопасного хранения. В Application_EndRequest я избавляюсь от DbContext.

Я бы перенес сохранение в ваш репозиторий.

Я бы создал атрибут [Transaction], который бы поддерживал TransactionScope примерно так:

[AttributeUsage(AttributeTargets.Method | AttributeTargets.Class)]
public class TransactionAttribute : ActionFilterAttribute
{
    private TransactionScope Transaction { get; set; }

    public override void OnActionExecuting(ActionExecutingContext filterContext)
    {
        Transaction = new TransactionScope( TransactionScopeOption.Required);
    }

    public override void OnActionExecuted(ActionExecutedContext filterContext)
    {
        if (filterContext.Exception == null)
        {
            Transaction.Complete();
            return;
        }

        Transaction.Dispose();
    }
}

Затем вы можете просто пометить методы вашего контроллера с помощью [Transaction].

Я просто болтаюсь здесь, но я делаю что-то похожее с NHibernate вместо EF, и это хорошо работает для меня.

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

Я не уверен, как вы проходите ApplicationDbContext в ваш UnitOfWork. Я предполагаю, что вы используете Ninject для этой инъекции тоже. Просто убедитесь, что вы связываете ApplicationDbContext с использованием InRequestScope()Bind.To().InRequestScope();,

Таким образом, ваш экземпляр ApplicationDbContext будет создаваться один раз для каждого запроса и удаляться в конце.

Кроме того, использование InRequestScope для типов, которые являются одноразовыми, так что вы можете также выпустить ресурсы в методе Dispose вашего UnitOfWork метод тоже.

Другие вопросы по тегам