I put together a sample of how I am using the Unit of Work & Repository pattern based on my understanding. Can anyone please let me know if I am implementing this the correct way? If I am not, how can I improve it?
我根据我的理解汇总了一个如何使用工作单元和存储库模式的示例。如果我以正确的方式实施这个,请问有谁能告诉我吗?如果我不是,我该如何改进呢?
Thanks in advance, it's much appreciated.
在此先感谢,非常感谢。
I have an EF model with two entities: Topic and Subtopic. The EF model is called CommonGood.
我有一个带有两个实体的EF模型:Topic和Subtopic。 EF模型称为CommonGood。
Unit of Work:
工作单位:
/// <summary>
/// Implementation of a UnitOfWork class
/// </summary>
public static class UnitOfWork
{
/// <summary>
/// Gets the default context
/// </summary>
/// <returns>A new instance of the default context</returns>
public static CommonGoodEntities GetContext()
{
return new CommonGoodEntities();
}
}
IGenericRepository:
public interface IRepository<T>
{
/// <summary>
/// Gets all entities
/// </summary>
/// <returns>All entities</returns>
IEnumerable<T> GetAll();
/// <summary>
/// Gets all entities matching the predicate
/// </summary>
/// <param name="predicate">The filter clause</param>
/// <returns>All entities matching the predicate</returns>
IEnumerable<T> GetAll(Expression<Func<T, bool>> predicate);
/// <summary>
/// Set based on where condition
/// </summary>
/// <param name="predicate">The predicate</param>
/// <returns>The records matching the given condition</returns>
IQueryable<T> Where(Expression<Func<T, bool>> predicate);
/// <summary>
/// Finds an entity matching the predicate
/// </summary>
/// <param name="predicate">The filter clause</param>
/// <returns>An entity matching the predicate</returns>
IEnumerable<T> Find(Expression<Func<T, bool>> predicate);
/// <summary>
/// Determines if there are any entities matching the predicate
/// </summary>
/// <param name="predicate">The filter clause</param>
/// <returns>True if a match was found</returns>
bool Any(Expression<Func<T, bool>> predicate);
/// <summary>
/// Returns the first entity that matches the predicate
/// </summary>
/// <param name="predicate">The filter clause</param>
/// <returns>An entity matching the predicate</returns>
T First(Expression<Func<T, bool>> predicate);
/// <summary>
/// Returns the first entity that matches the predicate else null
/// </summary>
/// <param name="predicate">The filter clause</param>
/// <returns>An entity matching the predicate else null</returns>
T FirstOrDefault(Expression<Func<T, bool>> predicate);
/// <summary>
/// Adds a given entity to the context
/// </summary>
/// <param name="entity">The entity to add to the context</param>
void Add(T entity);
/// <summary>
/// Deletes a given entity from the context
/// </summary>
/// <param name="entity">The entity to delete</param>
void Delete(T entity);
/// <summary>
/// Attaches a given entity to the context
/// </summary>
/// <param name="entity">The entity to attach</param>
void Attach(T entity);
}
Generic Repository:
public class GenericRepository<T> : IRepository<T> where T : class
{
/// <summary>
/// The database context for the repository
/// </summary>
private DbContext _context;
/// <summary>
/// The data set of the repository
/// </summary>
private IDbSet<T> _dbSet;
/// <summary>
/// Initializes a new instance of the <see cref="GenericRepository{T}" /> class.
/// </summary>
/// <param name="context">The context for the repository</param>
public GenericRepository(DbContext context)
{
this._context = context;
this._dbSet = this._context.Set<T>();
}
/// <summary>
/// Gets all entities
/// </summary>
/// <returns>All entities</returns>
public IEnumerable<T> GetAll()
{
return this._dbSet;
}
/// <summary>
/// Gets all entities matching the predicate
/// </summary>
/// <param name="predicate">The filter clause</param>
/// <returns>All entities matching the predicate</returns>
public IEnumerable<T> GetAll(System.Linq.Expressions.Expression<Func<T, bool>> predicate)
{
return this._dbSet.Where(predicate);
}
/// <summary>
/// Set based on where condition
/// </summary>
/// <param name="predicate">The predicate</param>
/// <returns>The records matching the given condition</returns>
public IQueryable<T> Where(Expression<Func<T, bool>> predicate)
{
return this._dbSet.Where(predicate);
}
/// <summary>
/// Finds an entity matching the predicate
/// </summary>
/// <param name="predicate">The filter clause</param>
/// <returns>An entity matching the predicate</returns>
public IEnumerable<T> Find(System.Linq.Expressions.Expression<Func<T, bool>> predicate)
{
return this._dbSet.Where(predicate);
}
/// <summary>
/// Determines if there are any entities matching the predicate
/// </summary>
/// <param name="predicate">The filter clause</param>
/// <returns>True if a match was found</returns>
public bool Any(Expression<Func<T, bool>> predicate)
{
return this._dbSet.Any(predicate);
}
/// <summary>
/// Returns the first entity that matches the predicate
/// </summary>
/// <param name="predicate">The filter clause</param>
/// <returns>An entity matching the predicate</returns>
public T First(Expression<Func<T, bool>> predicate)
{
return this._dbSet.First(predicate);
}
/// <summary>
/// Returns the first entity that matches the predicate else null
/// </summary>
/// <param name="predicate">The filter clause</param>
/// <returns>An entity matching the predicate else null</returns>
public T FirstOrDefault(Expression<Func<T, bool>> predicate)
{
return this._dbSet.FirstOrDefault(predicate);
}
/// <summary>
/// Adds a given entity to the context
/// </summary>
/// <param name="entity">The entity to add to the context</param>
public void Add(T entity)
{
this._dbSet.Add(entity);
}
/// <summary>
/// Deletes a given entity from the context
/// </summary>
/// <param name="entity">The entity to delete</param>
public void Delete(T entity)
{
this._dbSet.Remove(entity);
}
/// <summary>
/// Attaches a given entity to the context
/// </summary>
/// <param name="entity">The entity to attach</param>
public void Attach(T entity)
{
this._dbSet.Attach(entity);
}
}
Controller:
public class HomeController : Controller
{
/// <summary>
/// The context used for the controller
/// </summary>
private DbContext _context;
/// <summary>
/// Initializes a new instance of the <see cref="HomeController"/> class.
/// </summary>
public HomeController()
{
this._context = UnitOfWork.GetContext();
}
public JsonResult GetTopics()
{
var topics = new GenericRepository<Topic>(this._context).GetAll().ToList();
return this.Json(topics, JsonRequestBehavior.AllowGet);
}
/// <summary>
/// Disposes of the context if the currently disposing
/// </summary>
/// <param name="disposing">A value indicating whether or not the application is disposing</param>
protected override void Dispose(bool disposing)
{
if (disposing)
{
this._context.Dispose();
}
base.Dispose(disposing);
}
}
Essentially I want to make sure I am accessing data in the proper way and making sure I am not overlooking anything. Again, thanks!
基本上我想确保我以正确的方式访问数据并确保我没有忽略任何东西。再次,谢谢!
1 个解决方案
#1
8
It's not perfect to implement UnitOfWork as a static class. Define an interface IUnitOfWork. Your db context will implement this interface. It may look like:
将UnitOfWork实现为静态类并不完美。定义IUnitOfWork接口。您的db上下文将实现此接口。它可能看起来像:
public interface IUnitOfWork {
int SaveChanges();
}
public class EFDbContext: DbContext, IUnitOfWork {
public DbSet<User> User { get; set; }
public EFDbContext(string connectionString)
: base(connectionString) { }
public override int SaveChanges() {
return base.SaveChanges();
}
}
I usually create several repositories inherited from generic repository. Thus names of repository find methods could have more specific names. This also prevents from duplication of repository logic in different controllers.
我通常创建从通用存储库继承的几个存储库。因此,存储库查找方法的名称可以具有更具体的名称。这还可以防止不同控制器中的存储库逻辑重复。
For example:
public class EFUserRepository: EFRepository<User>, IUserRepository {
public EFUserRepository(IUnitOfWork context)
: base(context) { }
protected override DbSet<User> Table {
get { return Context.User; }
}
public User Find(string email) {
return Table.FirstOrDefault(u => u.Email == email);
}
public bool Validate(string email, string password) {
string passwordHash = Cryptography.GenerateHash(password);
User user = Find(email);
return user != null && user.Password == passwordHash;
}
Now about controller: To simplify testing it's better to use IoC Container, e.g. NInject So the dependencies between controller <-> repo <-> unitOfWork will be resolved by NInject.
现在关于控制器:为了简化测试,最好使用IoC Container,例如: NInject因此,控制器< - > repo < - > unitOfWork之间的依赖关系将由NInject解决。
This how could look UserController with Login method:
这怎么可以用Login方法看看UserController:
public class UserController: Controller {
[Ninject.Inject]
public IUserRepository UserRepository { get; set; }
public ActionResult Login(AuthorizationViewModel vm) {
if(ModelState.IsValid) {
if(UserRepository.Validate(vm.Email, vm.Password)) {
FormsAuthentication.SetAuthCookie(vm.Email, true);
if(Url.IsLocalUrl(vm.ReturnUrl)) {
return Redirect(vm.ReturnUrl);
}
else {
return RedirectToAction("Page", "Main");
}
}
else {
ModelState.AddModelError("", Resources.Validation.WrongEmailOrPassword);
}
}
return View(vm);
}
}
The dependencies resolving could be done by custom controller factory, like this:
依赖关系解析可以由自定义控制器工厂完成,如下所示:
public class NInjectControllerFactory: DefaultControllerFactory {
public IKernel Kernel { get; private set; }
public NInjectControllerFactory() {
Kernel = new StandardKernel();
AddBindings();
}
protected override IController GetControllerInstance(System.Web.Routing.RequestContext requestContext, Type controllerType) {
return controllerType == null
? null
: (IController)Kernel.Get(controllerType);
}
private void AddBindings() {
Kernel.Bind<IUnitOfWork>().To(typeof(EFDbContext)).InRequestScope();
Kernel.Bind<IUserRepository>().To(typeof(EFUserRepository).InRequestScope();
}
}
And replace current controller factory with your custom. You can do it in Application_Start handler of Global.asax:
并使用您的自定义替换当前控制器工厂。您可以在Global.asax的Application_Start处理程序中执行此操作:
protected void Application_Start() {
...
ControllerBuilder.Current.SetControllerFactory(new NInjectControllerFactory());
}
If you decide to use NInject, you can simply add it with Nuget. To enable binding InRequestScope you also need NInject.Web.Common. Of course, there a lot of other alternatives like Castle Windsor or StructureMap, but NInject is the simplest one.
如果你决定使用NInject,你可以简单地用Nuget添加它。要启用绑定InRequestScope,您还需要NInject.Web.Common。当然,还有许多其他选择,如Castle Windsor或StructureMap,但NInject是最简单的。
Hope it will help.
希望它会有所帮助。
#1
8
It's not perfect to implement UnitOfWork as a static class. Define an interface IUnitOfWork. Your db context will implement this interface. It may look like:
将UnitOfWork实现为静态类并不完美。定义IUnitOfWork接口。您的db上下文将实现此接口。它可能看起来像:
public interface IUnitOfWork {
int SaveChanges();
}
public class EFDbContext: DbContext, IUnitOfWork {
public DbSet<User> User { get; set; }
public EFDbContext(string connectionString)
: base(connectionString) { }
public override int SaveChanges() {
return base.SaveChanges();
}
}
I usually create several repositories inherited from generic repository. Thus names of repository find methods could have more specific names. This also prevents from duplication of repository logic in different controllers.
我通常创建从通用存储库继承的几个存储库。因此,存储库查找方法的名称可以具有更具体的名称。这还可以防止不同控制器中的存储库逻辑重复。
For example:
public class EFUserRepository: EFRepository<User>, IUserRepository {
public EFUserRepository(IUnitOfWork context)
: base(context) { }
protected override DbSet<User> Table {
get { return Context.User; }
}
public User Find(string email) {
return Table.FirstOrDefault(u => u.Email == email);
}
public bool Validate(string email, string password) {
string passwordHash = Cryptography.GenerateHash(password);
User user = Find(email);
return user != null && user.Password == passwordHash;
}
Now about controller: To simplify testing it's better to use IoC Container, e.g. NInject So the dependencies between controller <-> repo <-> unitOfWork will be resolved by NInject.
现在关于控制器:为了简化测试,最好使用IoC Container,例如: NInject因此,控制器< - > repo < - > unitOfWork之间的依赖关系将由NInject解决。
This how could look UserController with Login method:
这怎么可以用Login方法看看UserController:
public class UserController: Controller {
[Ninject.Inject]
public IUserRepository UserRepository { get; set; }
public ActionResult Login(AuthorizationViewModel vm) {
if(ModelState.IsValid) {
if(UserRepository.Validate(vm.Email, vm.Password)) {
FormsAuthentication.SetAuthCookie(vm.Email, true);
if(Url.IsLocalUrl(vm.ReturnUrl)) {
return Redirect(vm.ReturnUrl);
}
else {
return RedirectToAction("Page", "Main");
}
}
else {
ModelState.AddModelError("", Resources.Validation.WrongEmailOrPassword);
}
}
return View(vm);
}
}
The dependencies resolving could be done by custom controller factory, like this:
依赖关系解析可以由自定义控制器工厂完成,如下所示:
public class NInjectControllerFactory: DefaultControllerFactory {
public IKernel Kernel { get; private set; }
public NInjectControllerFactory() {
Kernel = new StandardKernel();
AddBindings();
}
protected override IController GetControllerInstance(System.Web.Routing.RequestContext requestContext, Type controllerType) {
return controllerType == null
? null
: (IController)Kernel.Get(controllerType);
}
private void AddBindings() {
Kernel.Bind<IUnitOfWork>().To(typeof(EFDbContext)).InRequestScope();
Kernel.Bind<IUserRepository>().To(typeof(EFUserRepository).InRequestScope();
}
}
And replace current controller factory with your custom. You can do it in Application_Start handler of Global.asax:
并使用您的自定义替换当前控制器工厂。您可以在Global.asax的Application_Start处理程序中执行此操作:
protected void Application_Start() {
...
ControllerBuilder.Current.SetControllerFactory(new NInjectControllerFactory());
}
If you decide to use NInject, you can simply add it with Nuget. To enable binding InRequestScope you also need NInject.Web.Common. Of course, there a lot of other alternatives like Castle Windsor or StructureMap, but NInject is the simplest one.
如果你决定使用NInject,你可以简单地用Nuget添加它。要启用绑定InRequestScope,您还需要NInject.Web.Common。当然,还有许多其他选择,如Castle Windsor或StructureMap,但NInject是最简单的。
Hope it will help.
希望它会有所帮助。