数据库抽象层设计 - 使用IRepository的正确方法? [英] Database abstraction layer design - Using IRepository the right way?

查看:2381
本文介绍了数据库抽象层设计 - 使用IRepository的正确方法?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我在设计我的ASP.NET MVC应用程序的过程中,我跑过了几个有趣的想法。

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

我所看到的描述和使用Repository模式( IRepository )的样本很多,所以这是我做的,而我是学习MVC的方式。

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.

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

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

现在我不完全理解MVC如何工作的问候,当用户创建一个链接(不知道是否有每个用户1控制器或每个应用程序1控制器)创建一个控制器,所以我还不能肯定的行动最好的办法。

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.

我发现关于使用一个通用存储库接口 IRepository℃的大问题; T&GT; <一个href="http://stackoverflow.com/questions/1853414/generic-repository-irepositoryt-or-irepository">here并且还似乎对一些博客的静态 RepositoryFactory 的想法。基本上库只有一个实例通过这个工厂一直不断,它是获得

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.

请人根据每张桌子上都有单独的repositorys( IUserRepository )?
他们使用一个通用的 IRepository&LT; T&GT;
他们是否使用静态库的工厂?
还是其他什么东西完全?

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?

编辑: 我才意识到我也许应该问及:

I just realised I should probably ask as well:

是具有私人 IRepository 每个控制器上的一个很好的方式去?或者我应该实例化一个新的 IRepository 每次我要使用它?

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编辑: 我开始赏金争取多一些的观点(而不是蒂姆的是没有帮助)。

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

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

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

推荐答案

与怀念一些非常明显的问题,一个普通的 IRepository&LT; T&GT;

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

  • 有假设每个实体使用相同类型的键,这是不正确的,几乎在任何非平凡系统。一些实体将使用的GUID,其他人可能有某种天然和/或组合键。 NHibernate的可以支持这个还算不错,但LINQ到SQL是pretty的坏吧 - 你必须写一个很好的协议的hackish code做自动键映射

  • 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.

这意味着,每一个仓库只能处理只有一个实体类型,只支持最琐碎的操作。当库被转移到这样一个简单的CRUD的包装也有很少用都没有。你还不如刚刚到手的客户端的的IQueryable&LT; T&GT; 表&LT; T&GT;

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>.

它假定您在每一个实体执行完全一样的操作。在现实中,这将是与事实很远。当然,的也许的你想获得的订单的编号,但更可能你想获得的清单购买对象为特定客户,并在一定时间范围。的概念是完全通用的 IRepository&LT; T&GT; 不允许的事实,你几乎肯定要在不同类型的实体执行不同类型的查询

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.

的存储库模式的整点是创建一个抽象通过共同的数据访问模式。我觉得有些程序员厌倦了创建资料库,使他们说:嘿,我知道,我会创建一个尤伯杯库,可以处理任何实体类型!这是伟大的不同之处在于存储库是pretty的无用的80%,你在做什么。它的罚款作为基类/接口,但如果这就是工作的全部情况,你做那么你只是懒惰(并保证将来头痛)。

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

您会注意到这个有一个列表 GETALL 功能 - 这是因为它是荒谬的认为这是可以接受的,在任何地方,一旦在code检索整个表中的数据。这是当你需要开始进入具体的库:

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

等等 - 你的想法。这样,我们有通用信息库如果我们真正需要的令人难以置信的简单检索按ID语义,但总体上我们从来没有真正要传递的身边,肯定不是一个控制器类。

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.

现在作为控制器,你必须这样做的权利,否则你pretty的多否定你只是放在一起的所有存储库所做的工作。

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.

一个控制器需要的利用其与外界存储库。您创建这些库的原因是这样你就可以做一些反向控制的。在这里你的最终目标是能够换出一个存储库的另一个 - 例如,做单元测试,或者如果你决定从切换的LINQ到SQL实体框架在未来的某个时候

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

在换句话说,控制器具有不知道如何建立一个信息库,也不应该。如果您有任何资源库建设正在进行在那里,它的创造,你真的不想要的耦合。其原因是,ASP.NET MVC样本控制器有参数构造函数创建具体的资料库是网站需要能够编译和运行不强迫你建立一个完整的依赖注入框架。

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.

但在生产现场,如果你没有通过在资源库中​​的依赖,通过构造函数或公共财产,那么你就是在浪费有仓库,在所有的时间,因为控制器仍然紧耦合的数据库层。你需要能够编写测试code是这样的:

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

您不能,如果你的 OrderController 会关闭并创立了自己的资料库做到这一点。这种测试方法是不应该做任何的数据访问,它只是可以确保控制器调用基于操作的正确库的方法。

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.

这是不是DI的是,你要知道,这只是假装/嘲讽。凡迪进入画面是,当你决定LINQ到SQL做得不够你和你真正想要的HQL在NHibernate的,但它会带你3个月的端口所做的一切,并希望能做一个资料库的时间。因此,例如,使用DI框架,比如 Ninject ,你所要做的就是改变这样的:

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

要:

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

和你瞧,现在一切都取决于 IOrderRepository 正在使用NHibernate的版本,你只需要修改一行的code,而不是潜在的数百行。我们正在运行的LINQ to SQL和NHibernate的版本并列,而没有破坏任何东西在中间移植了一块一块的功能。

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. 不要依赖于严格的通用 IRepository&LT; T&GT; 接口。大多数时候,你想从一个存储库中的功能的的具体的,不会的通用的。如果你想包括 IRepository&LT; T&GT; 在类/接口层次结构的上层,这很好,但控制器应依靠的的具体的库这样你就不会最终不得不改变你的code在5个不同的地方,当你发现通用存储库缺少的重要手段。

  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.

控制器应该接受库从外面看,没有建立自己的。这是在消除耦合,提高可测试性的重要一步。

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

通常你会想连线了使用依赖注入框架的控制器,其中许多可与ASP.NET MVC无缝集成。如果这是你太多参加,那么最起码,你应该使用某种静态的服务供应商,以便您可以集中所有的资源库,创建逻辑的。 (从长远来看,你可能会发现很容易只是学习和使用DI框架)。

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).

这篇关于数据库抽象层设计 - 使用IRepository的正确方法?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆