我正在创建一个通用存储库,但不知道实现dispose功能的正确方法是什么:
我没有使用IoC / DI,但我将来会重构我的代码,所以:
我的代码:
IUnitOfWork Interface:
namespace MyApplication.Data.Interfaces
{
public interface IUnitOfWork
{
void Save();
}
}
DatabaseContext类:
namespace MyApplication.Data.Infra
{
public class DatabaseContext : DbContext, IUnitOfWork
{
public DatabaseContext(): base("SQLDatabaseConnectionString")
{
Database.SetInitializer<DatabaseContext>(null);
}
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
// Same code.
base.OnModelCreating(modelBuilder);
}
#region Entities mapping
public DbSet<User> User { get; set; }
// >>> A lot of tables
#endregion
public void Save()
{
base.SaveChanges();
}
}
}
IGenericRepository接口:
namespace MyApplication.Data.Interfaces
{
public interface IGenericRepository<T> where T : class
{
IQueryable<T> GetAll();
IQueryable<T> Get(Expression<Func<T, bool>> predicate);
T Find(params object[] keys);
T GetFirstOrDefault(Expression<Func<T, bool>> predicate);
bool Any(Expression<Func<T, bool>> predicate);
void Insert(T entity);
void Edit(T entity);
void Delete(Expression<Func<T, bool>> predicate);
}
}
GenericRepository类:
namespace MyApplication.Data.Repositories
{
public class GenericRepository<T> : IDisposable, IGenericRepository<T> where T : class
{
private DatabaseContext _context;
private DbSet<T> _entity;
public GenericRepository(IUnitOfWork unitOfWork)
{
if (unitOfWork == null)
throw new ArgumentNullException("unitofwork");
_context = unitOfWork as DatabaseContext;
_entity = _context.Set<T>();
}
public IQueryable<T> GetAll()
{
return _entity;
}
public IQueryable<T> Get(Expression<Func<T, bool>> predicate)
{
return _entity.Where(predicate).AsQueryable();
}
// I delete some of the code to reduce the file size.
#region Dispose
public void Dispose()
{
// HERE IS MY FIRST DOUBT: MY METHOD ITS OK?!
// I saw implementations with GC.Suppress... and dispose in destructor, etc.
_context.Dispose();
}
#endregion
}
}
IUserRepository接口:
namespace MyApplication.Data.Interfaces
{
public interface IUserRepository : IGenericRepository<User> { }
}
UserRepository类:
namespace MyApplication.Data.Repositories
{
public class UserRepository : GenericRepository<User>, IUserRepository, IDisposable
{
public UserRepository(IUnitOfWork unitOfWork) : base(unitOfWork) {}
}
}
UserController控制器类:
namespace MyApplication.Presentation.MVCWeb.Controllers
{
[Authorize]
public class UserController : Controller
{
private IUserRepository _userRepository;
private IProfileRepository _profileRepository;
private IUnitOfWork _unitOfWork;
public UserController()
{
this._unitOfWork = new DatabaseContext();
this._userRepository = new UserRepository(_unitOfWork);
this._profileRepository = new ProfileRepository(_unitOfWork);
}
public ActionResult List()
{
return View(this._userRepository.GetAll().ToList());
}
public ActionResult Create()
{
ViewBag.Profiles = new SelectList(this._profileRepository.GetAll().ToList(), "Id", "Name");
return View(new User());
}
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Create([Bind(Exclude = "Id, Status, CompanyId")] User model)
{
ViewBag.Profiles = new SelectList(this._profileRepository.GetAll().ToList(), "Id", "Name");
if (ModelState.IsValid)
{
model.EmpresaId = 1;
model.Status = Status.Active;
_userRepository.Insert(model);
_unitOfWork.Save();
return RedirectToAction("List");
}
else
{
return View();
}
}
}
那么,何时以及如何处置我的控制器和/或我的存储库和上下文?
答案 0 :(得分:13)
更新的答案:
我的控制器示例:
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
中的类级字段。
请原谅这个答案的长度。我必须建立一些基本原则才能使我的建议有意义。
<强> S.O.L.I.D。强>
SOLID ...代表面向对象编程和设计的五个基本原则。这里关注的两个原则是I
和S
。< / p>
接口隔离原则(ISP)
在IDisposable
界面上包含IGenericRepository<T>
会明确违反ISP。
这样做是因为存储库的可处置性(以及正确处理对象的必要性)与其设计目的无关,即获取和存储聚合根对象。通过将接口组合在一起,您将获得一个非隔离的接口。
除了违反某些理论原则外,为什么这很重要?我将在下面解释。但首先我必须介绍另一个SOLID原则:
单一责任原则
当我将功能代码重新编入优秀的OO代码时,我总是保留这篇文章Taking the Single Responsibility Principle Seriously。这不是一个容易的主题,文章非常密集。但它作为对SRP的彻底解释是非常宝贵的。
了解SRP并忽略99.9%的MVC控制器中存在的缺陷,这些控制器采用了许多DI构造函数参数,这里涉及的一个缺陷是:
让控制器负责使用存储库和处理存储库交叉到不同的抽象级别,这违反了SRP。
说明:
因此,您在存储库对象上至少调用了两个公共方法。一个到Get
一个对象,一个到Dispose
的存储库。这没什么不对,对吧?通常不会,在存储库或任何对象上调用两个方法没有任何问题。
但是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
以返回更新的客户视图。但这会爆发。
<强>结论强>
将存储库作为参数接收意味着其他东西负责创建存储库。其他东西也应该负责妥善处理存储库。
将对象置于与使用其(其他)公共成员相同的抽象层次的替代方案,当对象的生命周期和对象的可能后续使用不受直接控制时,是一个滴答作响的定时炸弹。 / p>
IDisposable
的规则是:在另一个功能接口声明中继承IDisposable
永远不可接受,因为IDisposable
永远不是功能性问题,而是实现细节仅
答案 1 :(得分:3)
让Interface
继承自IDisposable
:
public interface IGenericRepository<T> : IDisposable where T : class
{
//...
}
类别:
public class GenericRepository<T> : IGenericRepository<T>
{
public void Dispose()
{
//....
}
}
答案 2 :(得分:0)
单一责任原则
如果您查看SRP并在MVC中使用它,它会告诉您,当您具有由多种方法(CRUDS
)组成的事务时,这不是实现事务的正确方法。
因此您应该将多个(CRUD
)方法放在一个方法中,并且Transaction
方法为using
实现database context
,以便您可以通过database context
代表构成交易方法的private
方法。如果所有这些(CRUD
方法都成功,则可以调用Commit
方法(SaveChanges
)。当这些方法失败时,您什么也不做(= {Rollback
)。因此,请确保没有在这些方法中执行Commit
,而是将其委托给Transaction方法。
在存储库中不需要实施第二个项目符号IDispose
,因为using
负责清理database context
。
如果使用Connection Pooling
,这是一种有效的实现方式,并且可以扩展。
在上面设置类的方式感觉不对。因为在存储库的构造函数中,您可以设置database context
并在几种方法中使用相同的context
,但是由于您需要自己清理连接,因此您需要实现IDispose
。在存储库的许多示例中,它都是以这种方式实现的,但是从SRP的角度来看,这是不正确的,应根据上面的第二个项目符号进行更改。
答案 3 :(得分:0)
上下文:您要求实现处置功能的正确方法。
解决方案::这是此情况以及任何其他情况的替代方法。这种自动替代方法是Visual Studio本身建议的(我在VS2017和VS2019中测试过):