更新的答案:
我的控制器的示例:
private IGenericRepository<Profile> _repository;
private IUnitOfWork _unitOfWork = new DatabaseContext();
public ProfileController()
{
this._repository = new GenericRepository<Profile>(_unitOfWork);
}
public class ProfileController : Controller
{
private IGenericRepository<Profile> _repository;
private IUnitOfWork _unitOfWork = new DatabaseContext();
public ProfileController()
{
this._repository = new GenericRepository<Profile>(_unitOfWork);
}
}
使用您现在拥有的代码,最好的办法就是覆盖Controller.Dispose(bool disposing)
并处理那里的存储库。
protected override void Dispose(bool disposing)
{
if (disposing)
{
IDisposable d = _repository as IDisposable;
if (d != null)
d.Dispose();
GC.SupressFinalize(this);
}
base.Dispose(disposing);
}
一旦开始使用 IOC 容器,所有这些处理代码都将消失。建造和处置应在容器层面进行。容器将是唯一知道或关心存储库和工作单元是一次性的地方。
但我怀疑这些类首先不需要是一次性的。您应该在 using 块中使用 SqlConnection。它不需要是类级别的字段DatabaseContext
.
请原谅这个答案的长度。为了使我的建议有意义,我必须建立一些基础知识。
坚硬的。
SOLID ...代表面向对象编程和设计的五个基本原则。这里关注的两个原则是I
and S
.
接口隔离原则(ISP)
包括IDisposable
on the IGenericRepository<T>
接口明确违反了 ISP。
这样做是因为存储库的可处置性(以及正确处置对象的必要性)与其设计目的无关,即获取和存储聚合根对象。通过将这些接口组合在一起,您将获得一个非隔离的接口。
除了违反一些理论原则之外,为什么这很重要?我将在下面解释这一点。但首先我必须介绍另一个 SOLID 原则:
单一责任原则
这篇文章我一直保留着认真对待单一责任原则 http://www.developerfusion.com/article/137636/taking-the-single-responsibility-principle-seriously/,当我将功能代码重构为良好的 OO 代码时很方便。这不是一个简单的主题,而且文章非常密集。但作为对 SRP 的彻底解释,它的价值是无价的。
了解 SRP 并忽略 99.9% 的 MVC 控制器中存在的缺陷(这些控制器采用许多 DI 构造函数参数),这里相关的一个缺陷是:
让控制器负责使用存储库和处置存储库跨越了不同的抽象级别,这违反了 SRP。
解释:
因此,您在存储库对象上调用至少两个公共方法。一到Get
一个对象和一个对象Dispose
存储库的。这没什么问题吧?通常不会,在存储库或任何对象上调用两个方法没有任何问题。
But Dispose()
很特别。物体处置的惯例是,它在被处置后将不再有用。该公约是使用模式建立一个单独的代码块:
using (var foo = new Bar())
{
... // This is the code block
}
foo.DoSomething(); // <- Outside the block, this does not compile
这在技术上是合法的:
var foo = new Bar();
using (foo)
{
... // This is the code block
}
foo.DoSomething(); // <- Outside the block, this will compile
但这会在对象被处置后对其使用发出警告。这是不正确的,这就是为什么您在 MS 文档中看不到此用法的示例。
由于这种独特的约定,Dispose()
,与构造和析构对象的关系比与对象其他成员的使用关系更密切,即使它作为简单的公共方法公开。
构建和处置处于相同的低抽象级别。但由于控制器本身并不构建存储库,因此它存在于更高的抽象级别上。在处置存储库时,它会超出其抽象级别以在不同级别上摆弄存储库对象。这违反了 SRP。
代码现实
好吧,就我的代码而言,所有这些理论到底意味着什么?
考虑一下控制器代码在处理存储库本身时的样子:
public class CustomerController : Controller
{
IGenericRepository<Customer> _customerRepo;
IMapper<Customer, CustomerViewModel> _mapper;
public CustomerController(
IMapper<Customer, CustomerViewModel> customerRepository,
IMapper<Customer, CustomerViewModel> customerMapper)
{
_customerRepo = customerRepository;
_customerMapper = customerMapper;
}
public ActionResult Get(int id)
{
CustomerViewModel vm;
using (_customerRepo) // <- This looks fishy
{
Customer cust = _customerRepo.Get(id);
vm = _customerMapper.MapToViewModel(cust);
}
return View(wm);
}
public ActionResult Update(CustomerViewModel vm)
{
Customer cust = _customerMapper.MapToModel(vm);
CustomerViewModel updatedVm;
using(_customerRepo) // <- Smells like 3 week old flounder, actually
{
Customer updatedCustomer = _customerRepo.Store(cust);
updatedVm = _customerMapper.MapToViewModel(updatedCustomer);
}
return View(updatedVm);
}
}
控制器在构建时必须接收一个有用的(未处置的)存储库。这是一个普遍的期望。但不要在控制器中调用两个方法,否则会崩溃。该控制器只是一次性交易。另外,您甚至无法从一个公共方法调用另一个公共方法。例如。这Update
方法可以调用Get
将模型存储在存储库中以返回更新的客户视图后。但这会爆炸。
结论
接收存储库作为参数意味着有其他东西负责创建存储库。其他东西也应该负责正确处置存储库。
当对象的生命周期和对象的可能的后续使用不受直接控制时,在与使用其(其他)公共成员相同的抽象级别上处理对象的替代方案是一个定时炸弹。
的规则IDisposable
这是:继承是绝对不能接受的IDisposable
在另一个函数式接口声明中,因为IDisposable
从来都不是功能问题,而只是实现细节。