什么是IRepository,它有什么用途?

47

什么是IRepository?为什么要使用它?简单的例子不会有坏处。


我知道这可能听起来有点宽泛,但可以有人请涵盖一下基础知识吗? - Sevki
4个回答

65
MVC提倡关注点分离,但这不止于MVC层次。
数据访问本身就是一个关注点。 它应该在MVC的M位(模型)中完成。您如何构建模型取决于您自己,但人们通常遵循经过试验的模式(为什么要重新发明轮子?)。存储库模式是当前标准。但请不要期望有一个简单的公式,因为变化多端,几乎和开发人员一样多。
IRepository只是一个接口,由您创建(它不是MVC或ASP.NET或.NET的一部分)。它允许您将"repositories"与实际实现进行"解耦"。解耦合很好,因为它意味着你的代码...:
  1. 代码更具可重用性。 这非常好。
  2. 您的代码可以使用控制反转(或依赖注入)。 这有助于保持良好的关注点分离。 尤其是很好的,因为这允许单元测试...
  3. 您的代码可以进行单元测试。 在具有复杂算法的大型项目中尤其好。 因为它增加了您对正在使用的技术和您试图在软件中建模的领域的理解。
  4. 您的代码建立在最佳实践基础上,遵循常见模式。 这很好,因为它使维护变得更容易。
所以,既然让您解耦合了,那么IRepository就是一个接口,您可以创建并使您的repositories从中继承。它为您提供可靠的类层次结构。
我通常使用泛型IRepository:
IRepository
其中TEntity就是一个实体。 我使用的代码是:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace Wingspan.Web.Mvc
{
    public interface IRepository<TEntity> where TEntity : class
    {
        List<TEntity> FetchAll();
        IQueryable<TEntity> Query {get;}
        void Add(TEntity entity);
        void Delete(TEntity entity);
        void Save();
    }
}

这个接口的一个具体实现如下:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Data.Linq;

using Wingspan.Web.Mvc;

namespace ES.eLearning.Domain
{
    public class SqlRepository<T> : IRepository<T> where T : class
    {
        DataContext db;
        public SqlRepository(DataContext db)
        {
            this.db = db;
        }

        #region IRepository<T> Members

        public IQueryable<T> Query
        {
            get { return db.GetTable<T>(); }
        }

        public List<T> FetchAll()
        {
            return Query.ToList();
        }

        public void Add(T entity)
        {
            db.GetTable<T>().InsertOnSubmit(entity);
        }

        public void Delete(T entity)
        {
            db.GetTable<T>().DeleteOnSubmit(entity);
        }

        public void Save()
        {
            db.SubmitChanges();
        }

        #endregion
    }
}

这使我能够写:
SqlRepository<UserCourse> UserCoursesRepository = new SqlRepository<UserCourse>(db);

在这里,db是一个注入到服务中的DataContext实例。

有了UserCoursesRepository,我现在可以在我的Service类中编写如下方法:

public void DeleteUserCourse(int courseId)
        {
            var uc = (UserCoursesRepository.Query.Where(x => x.IdUser == UserId && x.IdCourse == courseId)).Single();
            UserCoursesRepository.Delete(uc);
            UserCoursesRepository.Save();
        }

现在,在我的控制器中,我只需要编写:

MyService.DeleteUserCourse(5);
MyService.Save();

使用这种模式,您的应用程序开发变得更像是一个装配线,最终导致一个非常简单的控制器。每个装配线的部分都可以独立于其他所有部分进行测试,因此错误可以在萌芽状态下消除。
如果这是一个冗长而笨重的答案,那是因为真正的答案是:
购买Steven Sanderson的书Pro ASP.NET MVC 2 Framework并学会以MVC思维方式思考。

3
仓储库与MVC完全正交。实际上,它是DDD提出的。 - hammett
5
上述设计的缺陷:只处理单个实体,未使用工作单元模式。在现实世界中,您需要在一个事务中处理多个实体,并且SaveChanges方法应在工作单元的末尾而不是在存储库类中调用。
  • 不使用工作单元会导致与数据库进行多次往返,而不是每个事务只使用一个连接。
- VahidN
5
"听起来很合理,但如果你用Units of Work回答一个问'什么是IRepository'的人,他们可能会摸不着头脑。这些评论在记录超越仓库和单个实体的内容,但学习是一个迭代的过程。" - awrigley
是我一个人觉得还是不太好的主意,把实现数据库接口的库引用到你的MVC网站中吗?难道你不应该将领域逻辑放在一个单独的库中,该库执行必要的查询操作并将结果提供给请求的客户端吗? - devlord
(在此处插入IRepository文档的链接) - Danny Bullis

17

IRepository是在您想要实现存储库模式时指定的接口。正如Brian Ball所述,它不是.NET的一部分,而是您创建的接口。

广泛使用存储库模式的开发人员推荐使用接口进行实现。例如,在我正在开发的应用程序中,我有5个存储库。其中4个是特定的,1个是通用的。每个存储库都继承自IRepository,这确保了我将来不会因为实现的差异而出现问题。

至于代码示例,我尝试一下:

interface IRepository<T> where T : class {
    IQueryable<T> Select();
}

实现为通用仓库:

public class Repository<T> : IRepository<T> where T : class {
    public IQueryable<T> Select() {
        return this.ObjectContext.CreateObjectSet<T>();
    }
}

作为专门的存储库实现:

public class EmployeeRepository : IRepository<Employee> {
    public IQueryable<Employee> Select() {
        return this.ObjectContext.Employees;
    }
}

Repository<T>EmployeeRepository都实现了IRepository接口,但它们执行查询的方式略有不同。泛型仓储库必须在尝试执行任何操作之前创建一个T对象集。

请记住,Repository<T>应该锁定到接口,而EmployeeRepository可以实现更专业的方法来完成更复杂的逻辑。

希望这能对您有所帮助。


6
+1 很好的解释/代码。我想补充一点是,我的个人偏好是让专门的存储库继承通用存储库。例如 public class EmployeeRepository : Repository<Employee>。这样你就不必重新编写通用方法,比如 IQueryable<Employee> Select(),因为它已经在基础存储库中定义了。 - RPM1984
1
加一分给你,我的朋友,那是非常好的建议。为什么我之前没想到呢... :) - Gup3rSuR4c

4

IRepository不是.Net框架中定义的类型。通常,当你看到这样命名的接口时,程序会使用仓储模式(https://web.archive.org/web/20110503184234/http://blogs.hibernatingrhinos.com/nhibernate/archive/2008/10/08/the-repository-pattern.aspx)。一般来说,当人们使用这种模式时,他们会创建一个所有存储库都要遵守的接口。这样做有许多好处。其中一些好处是代码解耦和单元测试。

这也是常见的操作,以便利用IoC(http://en.wikipedia.org/wiki/Inversion_of_control)。


3

仓库是一个抽象,它将任何底层和任意的数据存储表示为内存中的对象集合。

由于常见实践和系统限制,这个定义变成了更实用的形式,即“内存中的对象集合,表示一些底层和任意的数据存储,可能是一个断开的存储”。在幕后,仓库可能链接到数据库、平面文件、内存中的对象集合或其他任何你能想象到的东西。仓库的使用者不关心。

因此,IRepository是接口契约,它定义了Api代码希望客户端代码与仓库交互的方式。这通常包括添加、更新、删除和获取契约,例如,这是一个非常常见的仓库契约示例:

public interface IRepository<TEntity> where TEntity : class
{
    List<TEntity> GetAll();
    void Add(TEntity entity);
    void Delete(TEntity entity);
    void Save();
}

但是,出于几个原因,我更喜欢使用不同的接口。
首先,您通常不会仅使用存储库,而是可能会将其与工作单元模式一起使用,因此存储库不应具有Save()方法。它可能具有Update(T entity)方法-但为什么呢?您从存储库收到的对象将自动可更新/已更新,就像您从任何对象集合中获取的其他对象一样,因为您已检索到对象本身的引用。(例如:如果您的TEntity是Person对象,并且您获取人物"Chuck",并将他的姓氏从"Bartowski"更改为"Carmichael",则存储库可能已经更新了该实体。如果您认为这很牵强,请实现一个Update(T entity)方法也可以)。
其次,大多数存储库都应能够处理离线环境。如果您的解决方案没有此要求,仍然可以创建处理断开连接情况的接口,并将其留为空未实现状态。现在您已为未来做好了准备。
最后,我们的合同对存储库的真实本质描述得更加清晰-内存中表示某些任意数据存储的对象集合,可能是断开连接的。
public interface IRepository<TEntity> where TEntity : class
{

    List<TEntity> GetAll();
    List<TEntity> Get(Func<TEntity, bool> where);
    void Insert(TEntity entity);
    void Insert(IEnumerable<TEntity> entities);
    void Remove(TEntity entity);
    void Remove(IEnumerable<TEntity> entities);

    void SyncDisconnected(TEntity entity, bool forDeletion = false);
    void SyncDisconnected(IEnumerable<TEntity> entities, bool forDeletion = false);
}

如果你为所有实体定义一个基类,我们称之为DomainObject,并给它一个Id字段,那么你就可以这样做:
public interface IRepository<TEntity> where TEntity : DomainObject
{
    TEntity GetById(object Id);

    List<TEntity> GetAll();
    List<TEntity> Get(Func<TEntity, bool> where);
    void Insert(TEntity entity);
    void Insert(IEnumerable<TEntity> entities);
    void Remove(TEntity entity);
    void Remove(IEnumerable<TEntity> entities);

    void SyncDisconnected(TEntity entity, bool forDeletion = false);
    void SyncDisconnected(IEnumerable<TEntity> entities, bool forDeletion = false);
}

如果您不喜欢可选参数forDeletion,您可以添加一个方法来同步已删除的对象:

    void SyncDisconnectedForDeletion(TEntity entity);

你需要这样做的原因是,大多数情况下,将断开连接的对象同步删除与将断开连接的对象同步添加或修改不兼容(请尝试一下。您会发现删除对存储的要求与添加或修改迥然不同)。因此,接口应该定义一个合同,以便实现可以区分两者。

您可以针对任何基础数据存储库(包括其他抽象到基础数据存储的抽象)实现此接口,无论其是否连接或断开连接。


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