数据库抽象层设计 - 使用 IRepository 的正确方法?

发布于 2024-08-23 13:12:47 字数 1962 浏览 8 评论 0原文

我正在设计 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

所以我的问题围绕人们如何在这些应用程序中执行此操作,以及什么被认为是良好实践。

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

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

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

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

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

I'm in the process of designing my ASP.NET MVC application and I ran across a couple of interesting thoughts.

Many samples I have seen describe and use the Repository pattern (IRepository) so this is the way I did it while I was learning MVC.

Now I know what it's all doing, I starting to look at my current design and wonder if it's the best way to go.

Currently I have a basic IUserRepository, which defines methods such as FindById(), SaveChanges(), etc.

Currently, whenever I want to load/query the user table in the DB, I do something along the lines of the following:

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

Now I don't fully understand how MVC works in regards to creating a controller when a user creates a link (not sure if there is 1 controller per user or 1 controller per application), so I'm not positive of the best course of action.

I found a great question regarding the use of a generic repository interface IRepository<T> here and have also seem the idea of a static RepositoryFactory on a number of blogs. Basically only 1 instance of the repository is kept ever and it is obtained via this factory

So my question revolves around how people do it in there apps, and whats considered good practice.

Do people have individual repositorys based on each table (IUserRepository)?
Do they use a generic IRepository<T>?
Do they use a static repository factory?
Or something else completely?

EDIT:
I just realised I should probably ask as well:

Is having a private IRepository on each controller a good way to go? or should I instantiate a new IRepository every time I want to use it?

BOUNTY EDIT:
I'm starting a bounty to get some more perspectives (not that Tim's wasn't helpful).

I'm more curious to know what people do in their MVC apps or what they think is a good idea.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(5

dawn曙光 2024-08-30 13:12:48

通用 IRepository 的想法存在一些非常明显的问题:

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

  • 这意味着每个存储库只能处理一种实体类型,并且只支持最简单的操作。当存储库被降级为这样一个简单的 CRUD 包装器时,它根本没有什么用处。您不妨只向客户端提供一个 IQueryableTable

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

存储库模式的全部要点是在常见的数据访问模式上创建抽象。我认为有些程序员对创建存储库感到厌倦,所以他们说“嘿,我知道,我将创建一个可以处理任何实体类型的超级存储库!”这很棒,只是存储库对于您尝试做的 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 到实体框架。

这个原则的一个例子是:

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,这只是伪造/嘲笑。当您认为 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 接口。您希望从存储库获得的大部分功能都是特定的,而不是通用的。如果您想在类/接口层次结构的上层包含 IRepository ,那没问题,但控制器应该依赖于特定存储库,因此您不需要当您发现通用存储库缺少重要方法时,最终不得不在 5 个不同的位置更改代码。

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

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

Some very obvious problems with the idea of a generic IRepository<T>:

  • It assumes that every entity uses the same type of key, which is not true in almost any non-trivial system. Some entities will use GUIDs, others may have some kind of natural and/or composite key. NHibernate can support this fairly well but Linq to SQL is pretty bad at it - you have to write a good deal of hackish code to do automatic key mapping.

  • It means that each repository can only deal with exactly one entity type and only supports the most trivial operations. When a repository is relegated to such a simple CRUD wrapper it has very little use at all. You might as well just hand the client an IQueryable<T> or Table<T>.

  • It assumes that that you perform exactly the same operations on every entity. In reality this is going to be very far from the truth. Sure, maybe you want to get that Order by its ID, but more likely you want to get a list of Order objects for a specific customer and within some date range. The notion of a totally generic IRepository<T> doesn't allow for the fact that you'll almost certainly want to perform different types of queries on different types of entities.

The whole point of the repository pattern is to create an abstraction over common data access patterns. I think some programmers get bored with creating repositories so they say "Hey, I know, I'll create one über-repository that can handle any entity type!" Which is great except that the repository is pretty much useless for 80% of what you're trying to do. It's fine as a base class/interface, but if that's the full extent of the work that you do then you're just being lazy (and guaranteeing future headaches).


Ideally I might start with a generic repository that looks something like this:

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

You'll notice that this doesn't have a List or GetAll function - that's because it's absurd to think that it's acceptable to retrieve the data from an entire table at once anywhere in the code. This is when you need to start going into specific repositories:

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

And so on - you get the idea. This way we have the "generic" repository for if we ever actually need the incredibly simplistic retrieve-by-id semantics, but in general we're never actually going to pass that around, certainly not to a controller class.


Now as for controllers, you have to do this right, otherwise you've pretty much negated all the work you just did in putting together all the repositories.

A controller needs to take its repository from the outside world. The reason you created these repositories is so you can do some kind of Inversion of Control. Your ultimate goal here is to be able to swap out one repository for another - for example, to do unit testing, or if you decide to switch from Linq to SQL to Entity Framework at some point in the future.

An example of this principle is:

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

In other words the Controller has no idea how to create a repository, nor should it. If you have any repository-construction going on in there, it's creating coupling that you really don't want. The reason that the ASP.NET MVC sample controllers have parameterless constructors that creates concrete repositories is that the sites need to be able to compile and run without forcing you to set up an entire Dependency Injection framework.

But in a production site, if you aren't passing in the repository dependency through a constructor or public property, then you're wasting your time having repositories at all, because the controllers are still tightly coupled to the database layer. You need to be able to write test code like this:

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

You can't do this if your OrderController is going off and creating its own repository. This test method isn't supposed to do any data access, it just makes sure that the controller is invoking the correct repository method based on the action.


This isn't DI yet, mind you, this is just faking/mocking. Where DI comes into the picture is when you decide that Linq to SQL isn't doing enough for you and you really want the HQL in NHibernate, but it's going to take you 3 months to port everything over, and you want to be able to do this one repository at a time. So, for example, using a DI Framework like Ninject, all you have to do is change this:

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

To:

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

And there you are, now everything that depends on IOrderRepository is using the NHibernate version, you've only had to change one line of code as opposed to potentially hundreds of lines. And we're running the Linq to SQL and NHibernate versions side by side, porting functionality over piece by piece without ever breaking anything in the middle.


So to summarize all the points I've made:

  1. Don't rely strictly on a generic IRepository<T> interface. Most of the functionality you want from a repository is specific, not generic. If you want to include an IRepository<T> at the upper levels of the class/interface hierarchy, that's fine, but controllers should depend on specific repositories so you don't end up having to change your code in 5 different places when you find that the generic repository is missing important methods.

  2. Controllers should accept repositories from the outside, not create their own. This is an important step in eliminating coupling and improving testability.

  3. Normally you'll want to wire up the controllers using a Dependency Injection framework, and many of them can be seamlessly integrated with ASP.NET MVC. If that's too much for you to take in, then at the very least you should be using some kind of static service provider so you can centralize all of the repository-creation logic. (In the long run, you'll probably find it easier to just learn and use a DI framework).

时间你老了 2024-08-30 13:12:48

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

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

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

Do people have individual repositorys based on each table (IUserRepository)?
I tend to have a repository for each aggregate, not each table.

Do they use a generic IRepository?
if possible, yes

Do they use a static repository factory?
I prefer the injection of a Repository instance through an IOC container

々眼睛长脚气 2024-08-30 13:12:48

这是我的使用方法。我使用 IRepository 来执行我的所有存储库通用的所有操作。

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

我还为每个 聚合 使用一个专用的 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 结构,你可以从中学到很多东西。

您可以从中学习的另一个项目是谁可以帮助我。我还没有对此进行足够的挖掘。

Here is how I'm using it. I'm using IRepository for all the operations that are common to all of my repositories.

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

and I use also a dedicated an ITRepository for each aggregate for the operation that are distinct to this repository. For example for user I will use IUserRepository to add method that are distinct to UserRepository:

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

The implementation will look like this:

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

Than in the UserController will look as:

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

Than the repository is instantiated using dependency injection pattern using custom controller factory. I'm using StructureMap as my dependency injection layer.

The database layer is NHibernate. ISession is the gateway to the the database in this session.

I'm suggest you to look on CodeCampServer structure, you can learn a lot from it.

Another project that you can learn fro it is Who Can Help Me. Which I don't dig enough in it yet.

木緿 2024-08-30 13:12:48

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

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

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

他们使用通用的 IRepository 吗?

是的,完全,受 DDD 实体/值方案的启发,我创建了 IRepositoryEntity / IRepositoryValue 和用于其他内容的通用 IRepository。

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

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

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

Do people have individual repositorys based on each table (IUserRepository)?

Yes, this was the better choice for 2 reasons :

  • my DAL is based on Linq-to-Sql (but my DTOs are interfaces based on the LTS entities)
  • the performed operations are atomic (adding is an atomic operation, saving is another one, etc.)

Do they use a generic IRepository?

Yes, exclusively, inspired on DDD Entity/Value scheme I created IRepositoryEntity / IRepositoryValue and a generic IRepository for other stuff.

Do they use a static repository factory?

Yes and no : I use an IOC container invoked via a static class.
Well... we can say it's a kind of factory.

Note : I designed this architecture on my own and a colleague of mine found it so great that we are currently creating our whole company framework on this model (yes it's a young company). This is definitely something worth trying, even if I feel that frameworks of that kind will be released by major actors.

梦幻的心爱 2024-08-30 13:12:48

您可以找到一个优秀的 Generic Repopsitory 库,该库的编写目的是使其能够用作 codeplex 上的 WebForms asp:ObjectDataSource:MultiTierLinqToSql

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

You can find an excellent Generic Repopsitory library that was written to enable it to be used as a WebForms asp:ObjectDataSource on codeplex: MultiTierLinqToSql

Each of my controllers have private repositories for the actions they need to support.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文