gpt4 book ai didi

.net - 数据库抽象层设计 - 以正确的方式使用 IRepository?

转载 作者:行者123 更新时间:2023-12-03 21:19:37 26 4
gpt4 key购买 nike

我正在设计我的 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 在创建 Controller 方面是如何工作的(不确定是每个用户有 1 个 Controller 还是每个应用程序有 1 个 Controller ),所以我对最好的类(class)并不乐观行动。

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

所以我的问题围绕着人们如何在应用程序中做到这一点,以及什么被认为是好的做法。

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

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

有私有(private) IRepository每个 Controller 上的好方法是什么?或者我应该实例化一个新的 IRepository每次都想用?

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

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

最佳答案

泛型思想的一些非常明显的问题 IRepository<T> :

  • 它假设每个实体都使用相同类型的 key ,这在几乎任何非平凡系统中都是不正确的。一些实体将使用 GUID,其他实体可能具有某种自然和/或复合键。 NHibernate 可以很好地支持这一点,但Linq to SQL 在这方面做得很糟糕——你必须编写大量的hackish 代码来进行自动键映射。
  • 这意味着每个存储库只能处理一种实体类型,并且只支持最琐碎的操作。当存储库被降级为这样一个简单的 CRUD 包装器时,它根本没有什么用处。你不妨直接给客户一个 IQueryable<T>Table<T> .
  • 它假定您对每个实体执行完全相同的操作。实际上,这与事实相去甚远。当然,也许你想得到那个 Order通过它的 ID,但更有可能你想得到一个列表 Order特定客户和某个日期范围内的对象。完全通用的概念 IRepository<T>不允许您几乎肯定要对不同类型的实体执行不同类型的查询。

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

    理想情况下,我可能会从一个看起来像这样的通用存储库开始:
    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 检索语义,但一般来说,我们实际上永远不会传递它,当然不会传递给 Controller ​​类。

    现在对于 Controller ,你必须正确地做这件事,否则你几乎否定了你刚刚将所有存储库放在一起所做的所有工作。

    Controller 需要 从外界获取其存储库 .您创建这些存储库的原因是您可以进行某种控制反转。您在这里的最终目标是能够将一个存储库替换为另一个存储库 - 例如,进行单元测试,或者您决定在将来的某个时候从 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; }
    }

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

    但是在生产站点中,如果您没有通过构造函数或公共(public)属性传入存储库依赖项,那么您根本就在浪费时间拥有存储库,因为 Controller 仍然与数据库层紧密耦合。您需要能够编写这样的测试代码:
    [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,您不能这样做正在关闭并创建自己的存储库。这个测试方法不应该进行任何数据访问,它只是确保 Controller 根据操作调用正确的存储库方法。

    这还不是 DI,请注意,这只是假装/ mock 。当您认为 Linq to SQL 对您来说做得还不够,并且您真的想要 NHibernate 中的 HQL 时,DI 就出现了,但是您需要 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 版本,逐个移植功能,而不会在中间破坏任何东西。

    总结一下我提出的所有观点:
  • 不要严格依赖通用 IRepository<T>界面。您希望从存储库中获得的大部分功能都是特定的,而不是通用的。如果您想包含 IRepository<T>在类/接口(interface)层次结构的上层,这很好,但是 Controller 应该依赖于特定的存储库,这样当您发现通用存储库缺少重要方法时,您最终不必在 5 个不同的地方更改代码。
  • Controller 应该接受来自外部的存储库,而不是创建自己的存储库。这是消除耦合和提高可测试性的重要一步。
  • 通常,您需要使用依赖注入(inject)框架来连接 Controller ,其中许多可以与 ASP.NET MVC 无缝集成。如果这对您来说太多了,那么至少您应该使用某种静态服务提供者,以便您可以集中所有存储库创建逻辑。 (从长远来看,您可能会发现学习和使用 DI 框架更容易)。
  • 关于.net - 数据库抽象层设计 - 以正确的方式使用 IRepository?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/2347337/

    26 4 0
    Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
    广告合作:1813099741@qq.com 6ren.com