使用仓储模式在MVC 5&EF 6中实现工作单元

6
我整理了一个示例,展示了我如何基于自己的理解使用工作单元和存储库模式。请问有人能告诉我我是否以正确的方式实现了它?如果不是,我该如何改进?
提前感谢您的帮助。
我有一个 EF 模型,其中包含两个实体:Topic 和 Subtopic。EF 模型名为 CommonGood。
工作单元:
/// <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);
}

通用仓储库:

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);
    }
}

控制器:

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);
    }
}

实际上我希望确保我以正确的方式访问数据,并确保我没有忽略任何东西。再次感谢!


5
你可以尝试访问http://codereview.stackexchange.com/。这并不是一个适合在SO上提问的问题。 - Jace Rhea
4
这个问题似乎不适合在此讨论,因为它更适合于CodeReview。 - Gert Arnold
1个回答

8
实现UnitOfWork作为静态类并不完美。 定义一个接口IUnitOfWork。你的数据库上下文将实现这个接口。 它可能看起来像这样:

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();
    }
}

我通常会创建几个继承自通用仓库的仓库。因此,仓库查找方法的名称可以更具体。这也可以防止不同控制器中重复仓库逻辑的出现。
例如:
    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;
    }

现在谈论控制器:为了简化测试,最好使用IoC容器,例如NInject。因此,控制器与repo<->unitOfWork之间的依赖关系将由NInject解决。

这是UserController及其Login方法的样子:

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);
    }
}

依赖项的解析可以通过自定义控制器工厂来完成,例如:
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();
    }
}

并用自己的替换当前控制器工厂。您可以在Global.asax的Application_Start处理程序中执行此操作:

protected void Application_Start() {
    ...
ControllerBuilder.Current.SetControllerFactory(new NInjectControllerFactory());

}

如果您决定使用NInject,您只需通过Nuget添加它。要启用InRequestScope绑定,还需要NInject.Web.Common。当然,还有很多其他选择,比如Castle Windsor或StructureMap,但NInject是最简单的选择。
希望这能帮到您。

1
你能解释一下在这个例子中使用 IUnitOfWork 接口的好处吗(相对于传递一个普通的 EFDbContext)?我曾经看到过(并使用过)这种模式,当 IUnitOfWork 接口提供抽象的查询或更改跟踪方法时(为了避免直接与 EF 类耦合),但是你实现它的方式需要访问 IUnitOfWorkEFDbContext 实例 - 你仍然有紧密的耦合,但现在你还必须处理一个额外的接口,它提供的功能已经可以通过你的 EFDbContext 访问。 - Jeremy Todd
1
正如你所说,它可以避免EF和存储库之间的耦合。在我的情况下,我有两种类型的存储库用于EF和Azure表格存储。这就是为什么我真的需要这种解耦。在当前情况下,你是正确的,我们可以不用IUnitOfWork。无论如何,在常见情况下减少耦合更好。 - tabalin

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接