【问题标题】:Database abstraction layer design - Using IRepository the right way?数据库抽象层设计 - 以正确的方式使用 IRepository?
【发布时间】:2010-02-27 12:43:41
【问题描述】:

我正在设计我的 ASP.NET MVC 应用程序,我遇到了一些有趣的想法。

我看到的许多示例都描述并使用了存储库模式 (IRepository),所以我在学习 MVC 时就是这样做的。

现在我知道它在做什么,我开始审视我目前的设计,想知道这是否是最好的方法。

目前我有一个基本的IUserRepository,它定义了FindById()SaveChanges()等方法。

目前,每当我想加载/查询数据库中的用户表时,我都会执行以下操作:

    private IUserRepository Repository;

    public UserController()
        : this(new UserRepository())
    { }

    [RequiresAuthentication]
    [AcceptVerbs(HttpVerbs.Get)]
    public ActionResult Edit(string ReturnUrl, string FirstRun)
    {
        var user = Repository.FindById(User.Identity.Name);

        var viewModel = Mapper.Map<User, UserEditViewModel>(user);
        viewModel.FirstRun = FirstRun == "1" ? true : false;

        return View("Edit", viewModel);
    }

    [AcceptVerbs(HttpVerbs.Post), ValidateAntiForgeryToken(Salt = "SaltAndPepper")]
    public ActionResult Edit(UserEditViewModel viewModel, string ReturnUrl)
    {
        //Map the ViewModel to the Model
        var user = Repository.FindById(User.Identity.Name);

        //Map changes to the user
        Mapper.Map<UserEditViewModel, User>(viewModel, user);

        //Save the DB changes
        Repository.SaveChanges();

        if (!string.IsNullOrEmpty(ReturnUrl))
            return Redirect(ReturnUrl);
        else
            return RedirectToAction("Index", "User");
    }

现在我不完全了解 MVC 在用户创建链接时如何创建控制器(不确定每个用户是否有 1 个控制器或每个应用程序有 1 个控制器),所以我不肯定最佳行动方案。

我发现了一个关于使用通用存储库接口IRepository&lt;T&gt;here 的好问题,并且在许多博客上似乎也有静态RepositoryFactory 的想法。基本上只保留 1 个存储库实例,它是通过这个工厂获得的

所以我的问题围绕着人们如何在应用程序中进行操作,以及什么被认为是好的做法。

人们是否有基于每个表的单独存储库 (IUserRepository)?
他们是否使用通用的IRepository&lt;T&gt;
他们是否使用静态存储库工厂?
或者完全是别的什么?

编辑: 我刚刚意识到我可能也应该问:

在每个控制器上都有一个私人IRepository 是一个好方法吗?还是每次我想使用它时都应该实例化一个新的IRepository

赏金编辑: 我开始赏金以获得更多观点(并不是说蒂姆没有帮助)。

我更想知道人们在他们的 MVC 应用程序中做了什么,或者他们认为什么是好主意。

【问题讨论】:

  • 通用静态存储库!耶! SingletonSquared,正是我们所需要的! ;-)
  • @Sky,永远都不够。我个人在考虑静态部分通用存储库。

标签: .net asp.net-mvc database-abstraction


【解决方案1】:

泛型IRepository&lt;T&gt; 的想法存在一些非常明显的问题:

  • 它假定每个实体都使用相同类型的密钥,这在几乎所有重要的系统中都是不正确的。一些实体将使用 GUID,其他实体可能具有某种自然键和/或复合键。 NHibernate 可以很好地支持这一点,但 Linq to SQL 在这方面做得很差——你必须编写大量的 hackish 代码来进行自动键映射。

  • 表示每个存储库只能处理一种实体类型,只支持最琐碎的操作。当存储库被降级为这样一个简单的 CRUD 包装器时,它几乎没有用处。您不妨将IQueryable&lt;T&gt;Table&lt;T&gt; 交给客户。

  • 它假定您对每个实体执行完全相同的操作。实际上,这与事实相去甚远。当然,也许您希望通过其 ID 获取 Order,但更有可能您希望获取特定客户在某个日期范围内的 Order 对象列表。完全通用的IRepository&lt;T&gt; 的概念不允许您几乎肯定希望对不同类型的实体执行不同类型的查询。

存储库模式的重点是在常见数据访问模式上创建一个抽象。我认为有些程序员厌倦了创建存储库,所以他们说“嘿,我知道,我将创建一个可以处理任何实体类型的 über-repository!”这很好,只是存储库对于您尝试做的 80% 几乎没有用处。作为基类/接口很好,但如果这是您所做工作的全部范围,那么您只是懒惰(并保证将来会头疼)。


理想情况下,我可能会从一个看起来像这样的通用存储库开始:

public interface IRepository<TKey, TEntity>
{
    TEntity Get(TKey id);
    void Save(TEntity entity);
}

您会注意到这个没有具有ListGetAll 函数 - 这是因为认为可以在任何地方一次从整个表中检索数据是可以接受的,这是很荒谬的在代码中。这是您需要开始进入特定存储库的时候:

public interface IOrderRepository : IRepository<int, Order>
{
    IEnumerable<Order> GetOrdersByCustomer(Guid customerID);
    IPager<Order> GetOrdersByDate(DateTime fromDate, DateTime toDate);
    IPager<Order> GetOrdersByProduct(int productID);
}

等等 - 你明白了。这样,如果我们真的需要非常简单的按 id 检索语义,我们就有了“通用”存储库,但一般来说,我们永远不会真正传递它,当然不会传递给控制器​​类。


现在,对于控制器,您必须正确地执行此操作,否则您几乎否定了您在将所有存储库放在一起所做的所有工作。

控制器需要从外界获取其存储库。您创建这些存储库的原因是您可以进行某种控制反转。您的最终目标是能够将一个存储库换成另一个存储库 - 例如,进行单元测试,或者如果您决定在未来某个时候从 Linq 切换到 SQL 再到 Entity Framework。

这个原则的一个例子是:

public class OrderController : Controller
{
    public OrderController(IOrderRepository orderRepository)
    {
        if (orderRepository == null)
            throw new ArgumentNullException("orderRepository");
        this.OrderRepository = orderRepository;
    }

    public ActionResult List(DateTime fromDate, DateTime toDate) { ... }
    // More actions

    public IOrderRepository OrderRepository { get; set; }
}

换句话说,控制器不知道如何创建存储库,也不应该这样做。如果您在那里进行任何存储库构建,那么它正在创建您真正不想要的耦合。 ASP.NET MVC 示例控制器具有创建具体存储库的无参数构造函数的原因是,站点需要能够编译和运行,而无需强制您设置整个依赖注入框架。

但是在生产站点中,如果您没有通过构造函数或公共属性传递存储库依赖项,那么您就完全浪费了拥有存储库的时间,因为控制器仍然与数据库层紧密耦合。你需要能够编写这样的测试代码:

[TestMethod]
public void Can_add_order()
{
    OrderController controller = new OrderController();
    FakeOrderRepository fakeRepository = new FakeOrderRepository();
    controller.OrderRepository = fakeRepository; //<-- Important!
    controller.SubmitOrder(...);
    Assert.That(fakeRepository.ContainsOrder(...));
}

如果您的 OrderController 正在关闭并创建自己的存储库,您将无法执行此操作。此测试方法不应该进行任何数据访问,它只是确保控制器根据操作调用正确的存储库方法。


这还不是 DI,请注意,这只是伪装/嘲笑。 DI 出现的地方是当您认为 Linq to SQL 对您来说还不够,并且您真的想要 NHibernate 中的 HQL,但是您需要 3 个月的时间来移植所有内容,并且您希望能够一次做一个存储库。因此,例如,使用像 Ninject 这样的 DI 框架,你所要做的就是改变这个:

Bind<ICustomerRepository>().To<LinqToSqlCustomerRepository>();
Bind<IOrderRepository>().To<LinqToSqlOrderRepository>();
Bind<IProductRepository>().To<LinqToSqlProductRepository>();

收件人:

Bind<ICustomerRepository>().To<LinqToSqlCustomerRepository>();
Bind<IOrderRepository>().To<NHibernateOrderRepository>();
Bind<IProductRepository>().To<NHibernateProductRepository>();

现在一切都依赖于IOrderRepository 正在使用 NHibernate 版本,您只需要更改 一行 代码,而不是可能的数百行代码。我们同时运行 Linq to SQL 和 NHibernate 版本,逐个移植功能,而不会破坏中间的任何内容。


总结一下我提出的所有观点:

  1. 不要严格依赖通用的IRepository&lt;T&gt; 接口。您希望从存储库获得的大部分功能是特定的,而不是通用。如果您想在类/接口层次结构的上层包含IRepository&lt;T&gt;,那很好,但控制器应该依赖于特定存储库,因此您最终不必更改代码当您发现通用存储库缺少重要方法时的 5 个不同位置。

  2. 控制器应该接受来自外部的存储库,而不是创建自己的存储库。这是消除耦合和提高可测试性的重要一步。

  3. 通常,您需要使用依赖注入框架连接控制器,其中许多可以与 ASP.NET MVC 无缝集成。如果这对你来说太多了,那么至少你应该使用某种静态服务提供者,这样你就可以集中所有的存储库创建逻辑。 (从长远来看,您可能会发现学习和使用 DI 框架会更容易)。

【讨论】:

  • 这是一个绝妙的答案,真的值得赏金。感谢所有提示和想法。
  • 我知道这是一个老问题,但这是一个很好的答案。它刚刚回答了我关于使用标准 IRepository 接口的许多问题。谢谢!
  • 无论年龄多大,答案都很好。这是一个很好的建议。
【解决方案2】:

人们是否有基于每个表的单独存储库 (IUserRepository)? 我倾向于为每个聚合而不是每个表都有一个存储库。

他们使用通用的 IRepository 吗? 如果可能的话,是的

他们是否使用静态存储库工厂? 我更喜欢通过 IOC 容器注入 Repository 实例

【讨论】:

  • 我很好奇你所说的聚合是什么意思。
  • 我认为对聚合的引用与域模型有关 - 我已经阅读了域驱动设计 (DDD) 上下文中的“聚合”,但我想这应该适用于所有域。 Eric Evans 将聚合定义为“属于一起的一组对象(一组代表一个单元的单个对象)”
  • 确实是我想说的
  • 啊,我明白你的意思了。我可以在我的应用程序中想到几个地方,这将比 per-table repo 更合适。
  • 随着时间的推移,通用存储库往往会变成 custard。它通常也不多,所以如果可以的话,请避免
【解决方案3】:

这是我的使用方法。我将 IRepository 用于我的所有存储库共有的所有操作。

public interface IRepository<T> where T : PersistentObject
{
    T GetById(object id);
    T[] GetAll();
    void Save(T entity);
}

我还为每个aggregate 使用一个专用的 ITRepository,用于与此存储库不同的操作。例如对于用户,我将使用 IUserRepository 添加与 UserRepository 不同的方法:

public interface IUserRepository : IRepository<User>
{
    User GetByUserName(string username);
}

实现将如下所示:

public class UserRepository : RepositoryBase<User>, IUserRepository
{
    public User GetByUserName(string username)
    {
        ISession session = GetSession();
        IQuery query = session.CreateQuery("from User u where u.Username = :username");
        query.SetString("username", username);

        var matchingUser = query.UniqueResult<User>();

        return matchingUser;
    }
}


public class RepositoryBase<T> : IRepository<T> where T : PersistentObject
{
    public virtual T GetById(object id)
    {
        ISession session = GetSession();
        return session.Get<T>(id);
    }

    public virtual T[] GetAll()
    {
        ISession session = GetSession();
        ICriteria criteria = session.CreateCriteria(typeof (T));
        return criteria.List<T>().ToArray();
    }

    protected ISession GetSession()
    {
        return new SessionBuilder().GetSession();
    }

    public virtual void Save(T entity)
    {
        GetSession().SaveOrUpdate(entity);
    }
}

比在 UserController 中看起来像:

public class UserController : ConventionController
{
    private readonly IUserRepository _repository;
    private readonly ISecurityContext _securityContext;
    private readonly IUserSession _userSession;

    public UserController(IUserRepository repository, ISecurityContext securityContext, IUserSession userSession)
    {
        _repository = repository;
        _securityContext = securityContext;
        _userSession = userSession;
    }
}

比使用自定义控制器工厂的依赖注入模式实例化存储库。我使用StructureMap 作为我的依赖注入层。

数据库层是 NHibernate。 ISession 是此会话中数据库的网关。

我建议你看看CodeCampServer结构,你可以从中学到很多东西。

您可以从中学习的另一个项目是Who Can Help Me。我还没有深入了解它。

【讨论】:

  • 我忘了提到它使用 NHibernate 作为持久层。 ISession 接口为您提供了与该会话的服务器通信所需的内容。
  • 我实际上已经回到了 RepositoryBase 抽象类,因为存储库之间的方法有相当多的交叉。
【解决方案4】:

人们是否有基于每个表的单独存储库 (IUserRepository)?

是的,这是更好的选择,原因有两个:

  • 我的 DAL 基于 Linq-to-Sql(但我的 DTO 是基于 LTS 实体的接口)
  • 执行的操作是原子的(添加是原子操作,保存是另一个,等等)

他们使用通用的 IRepository 吗?

是的,独家,受 DDD 实体/值方案的启发,我创建了 IRepositoryEntity / IRepositoryValue 和用于其他东西的通用 IRepository。

他们是否使用静态存储库工厂?

是和否:我使用通过静态类调用的 IOC 容器。 嗯...我们可以说它是一种工厂。

注意:我自己设计了这个架构,我的一位同事发现它非常棒,以至于我们目前正在这个模型上创建我们的整个公司框架(是的,它是一家年轻的公司)。这绝对是值得一试的,即使我觉得这种框架会由主要参与者发布。

【讨论】:

    【解决方案5】:

    您可以找到一个出色的 Generic Repopsitory 库,该库被编写为能够在 codeplex 上用作 WebForms asp:ObjectDataSource:MultiTierLinqToSql

    我的每个控制器都有他们需要支持的操作的私有存储库。

    【讨论】:

      猜你喜欢
      • 2011-02-02
      • 2015-12-14
      • 1970-01-01
      • 2011-03-17
      • 1970-01-01
      • 2015-05-15
      • 1970-01-01
      • 2018-09-05
      • 1970-01-01
      相关资源
      最近更新 更多