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
метод тоже.