使用存储库模式的 ASP.NET MVC

发布于 2024-09-25 16:35:32 字数 431 浏览 7 评论 0原文

目前,我在所有操作中使用 EF 并直接使用其数据上下文,但自从我开始阅读有关松散耦合和可测试性的内容后,我认为这不是最好的方法。在开始重构所有当前代码之前,我试图了解所有的优点和缺点。

问题 1: 考虑到每个实体都需要自己的存储库,因此必须设置自己与数据源的连接(假设使用 EF 的数据库),如果我需要单个页面上 5 个不同实体的数据,这会不会带来大量开销?

问题2: 我在网上找到的所有示例中还看到,大多数人(甚至像 shanselman 这样的人)使用由 LINQ 或 EF 生成的实体类来实现存储库模式,这是否违背了存储库模式的目的关于松耦合?另一方面,替代方案是什么,将 POCO 类与 AutoMapper 等结合使用? (这让我有点害怕)

我希望有人能对此有所了解,因为我现在有点困惑存储库模式是否是网站的正确选择。

Currently im using EF and using its datacontext directly in all of my actions, but since i started reading about loose coupling and testability im thinking that thats not the best way to go. Im trying to understand all the pro's and con's before i start refactor all my current code.

Problem 1:
Considering that every entity needs its own repository, and thus has to setup its own connection to a datasource (lets assume a database using EF), wouldnt that give alot of overhead if i need data from 5 different entities on a single page?

Problem 2:
What im seeing aswell in all the examples which i found online is that most people (even people like shanselman) implement the repository pattern using the entity classes which are generated by either LINQ or EF, doesn't this defeat the purpose of repository pattern with regards to loose coupling? On the other hand, what is the alternative, using POCO classes in combination with for example AutoMapper? (this scares me a little)

Im hoping that a few people can shed some light on this, because im a bit confused at the moment if the repository pattern is the right choice for a website.

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

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

发布评论

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

评论(6

骄傲 2024-10-02 16:35:32

您可以阅读这本书。有一个使用存储库模式和 LINQ 的好示例。
还有这篇文章 将存储库和工作单元模式与实体框架 4.0 结合使用

You can read this book. There is a good example of the using Repository pattern and LINQ.
Also there is this article Using Repository and Unit of Work patterns with Entity Framework 4.0.

风渺 2024-10-02 16:35:32

ObjectContext 使用连接池,因此它不会像您想象的那样低效。此外,SQL 服务器(即 MSSQL)确实针对大量并发连接进行了优化。

至于如何实现它,我会使用一些 IRepository 接口。然后就可以制作具体的接口了,即PostRepository > IRepository,最后,在具体类中实现它(例如,一个真实的类,和一个用于测试的假内存中类)。

ObjectContext uses connection pooling, so it won't be as inefficient as you might think. Also, SQL servers (i.e. MSSQL) are really optimized for tons of concurrent connections.

As for how to implement it, I'd go with some IRepository interface. You can then make specific interfaces, i.e. PostRepository > IRepository, and finally, implement that in concrete classes (for example, a real class, and a fake in-memory one for testing).

跨年 2024-10-02 16:35:32

首先,我不知道每个实体都需要拥有自己的存储库,因此我会放弃该限制。

对于 Scott H 的实现,我假设您指的是 Nerddinner 应用程序,他自己承认这并不是真正的存储库模式。

正如您所猜测的,存储库模式的目标是将数据存储与其上面的层隔离。这不仅仅是出于测试原因,它还允许您更改后备存储而不影响您的 UI/业务逻辑。

用纯粹的术语来说,您将创建 POCO,并将其从存储库返回到您的 BL,通过使用接口来定义存储库合约,然后您可以传递并使用该接口,而不是具体的实现。这将允许您传入实现 Repository 接口的任何对象,无论是您的实时存储库还是模拟存储库。

实际上,我使用带有 Linq to SQL 的 MVC 存储库作为我的后备存储,这使我在实际后备存储上具有一定程度的灵活性,因此我在 BL 中使用手工制作的 L2S 对象,这些对象具有不持久的附加字段和功能到后备存储。通过这种方式,我可以从 L2S 方面、更改跟踪、对象层次结构等方面获得一些出色的功能,同时还允许我用模拟存储库替换 TDD。

Firstly, I'm not aware of a requirement for each entity to have it's own repository so I'd junk that restriction.

For Scott H's implementation, I assume you are referring to the Nerd Dinner app, which by his own admission isn't really the repository pattern.

The objective of the repository pattern is, as you surmise, to isolate the data store from the layers above it. It's not purely for testing reasons, it also allows you to change the backing store without affecting your UI/Business Logic.

In purist terms you would create POCOs that you would return from the Repository to your BL, by using an interface to define the Repository contract you could then pass and use the interface rather than a concrete implementation. This would allow you to pass in any object that implemented the Repository interface, whether your live repository or a mocked repository.

In reality I use a repository with MVC with Linq to SQL as my backing store which allows me a degree of flexibility over the actual backing store so I use hand crafted L2S objects in my BL, these have additional fields and functionality that isn't persisted to the backing store. This way I get some great functionality from the L2S aspects, change tracking, object hierarchy, etc, while also allowing me to substitute a mocked repository for TDD.

幸福还没到 2024-10-02 16:35:32

您一针见血地确定了使用实体作为业务对象的困难。经过多次尝试和错误,我们已经采用了以下模式,该模式对我们来说非常有效:

我们的应用程序分为模块,每个模块分为三层:Web(前端)、Core(业务)和数据。在我们的例子中,每一层都有自己的项目,因此有严格的强制措施来防止我们的依赖关系变得紧密耦合。

核心层包含实用程序类、POCO 和存储库接口。

Web层利用这些类和接口来获取所需的信息。例如,MVC 控制器可以将特定的存储库接口作为构造函数参数,因此我们的 IoC 框架在创建控制器时注入该存储库的正确实现。存储库接口定义了返回 POCO 对象的选择器方法(也在核心业务层中定义)。

数据层的全部职责是实现核心层中定义的存储库接口。它有一个代表我们的数据存储的实体框架上下文,但它不是返回实体(技术上是“数据”对象),而是返回核心层中定义的 POCO(我们的“业务”对象)。

为了减少重复,我们有一个抽象的通用 EntityMapper 类,它提供将实体映射到 POCO 的基本功能。这使得我们的大多数存储库实现都非常简单。例如:

public class EditLayoutChannelEntMapper : EntityMapper<Entity.LayoutChannel, EditLayoutChannel>,
    IEditLayoutChannelRepository
{
    protected override System.Linq.Expressions.Expression<Func<Entity.LayoutChannel, EditLayoutChannel>> Selector
    {
        get
        {
            return lc => new EditLayoutChannel
                             {
                                 LayoutChannelId = lc.LayoutChannelId,
                                 LayoutDisplayColumnId = lc.LayoutDisplayColId,
                                 ChannelKey = lc.PortalChannelKey,
                                 SortOrder = lc.Priority
                             };
        }
    }
    public EditLayoutChannel GetById(int layoutChannelId)
    {
        return SelectSingle(c => c.LayoutChannelId == layoutChannelId);
    }
}

由于 EntityMapper 基类实现的方法,上述存储库实现了以下接口:

public interface IEditLayoutChannelRepository
{
    EditLayoutChannel GetById(int layoutChannelId);
    void Update(EditLayoutChannel editLayoutChannel);
    int Insert(EditLayoutChannel editLayoutChannel);
    void Delete(EditLayoutChannel layoutChannel);
}

EntityMappers 在其构造函数中执行的操作很少,因此如果控制器具有多个存储库依赖项也没关系。实体框架不仅重用连接,而且仅在调用存储库方法之一时才创建实体上下文本身。

每个模块还有一个特殊的Test项目,其中包含这三层中的类的单元测试。我们甚至想出了一种方法,使我们的存储库和其他数据访问类在某种程度上可进行单元测试。现在我们已经设置了这个基本的基础设施,向 Web 应用程序添加功能通常非常顺利,而且不太容易出错。

You hit the nail on the head in identifying the difficulty with using Entities as business objects. After much trial and error, here's the pattern that we've settled into, which has been working very well for us:

Our application is divided into modules, and each module is divided into three tiers: Web (front-end), Core (business), and Data. In our case, each of these tiers is given its own project, so there is a hard enforcement preventing our dependencies from becoming tightly-coupled.

The Core layer contains utility classes, POCOs, and repository interfaces.

The Web layer leverages these classes and interfaces to get the information it needs. For example, an MVC controller can take a particular repository interface as a constructor argument, so our IoC framework injects the correct implementation of that repository when the controller is created. The repository interface defines selector methods that return our POCO objects (also defined in the Core business layer).

The Data layer's entire responsibility is to implement the repository interfaces defined in the Core layer. It has an Entity Framework context that represents our data store, but rather than returning the Entities (which are technically "data" objects), it returns the POCOs defined in the Core layer (our "business" objects).

In order to reduce repetition, we have an abstract, generic EntityMapper class, which provides basic functionality for mapping Entities to POCOs. This makes the majority of our repository implementations extremely simple. For example:

public class EditLayoutChannelEntMapper : EntityMapper<Entity.LayoutChannel, EditLayoutChannel>,
    IEditLayoutChannelRepository
{
    protected override System.Linq.Expressions.Expression<Func<Entity.LayoutChannel, EditLayoutChannel>> Selector
    {
        get
        {
            return lc => new EditLayoutChannel
                             {
                                 LayoutChannelId = lc.LayoutChannelId,
                                 LayoutDisplayColumnId = lc.LayoutDisplayColId,
                                 ChannelKey = lc.PortalChannelKey,
                                 SortOrder = lc.Priority
                             };
        }
    }
    public EditLayoutChannel GetById(int layoutChannelId)
    {
        return SelectSingle(c => c.LayoutChannelId == layoutChannelId);
    }
}

Thanks to the methods implemented by the EntityMapper base class, the above repository implements the following interface:

public interface IEditLayoutChannelRepository
{
    EditLayoutChannel GetById(int layoutChannelId);
    void Update(EditLayoutChannel editLayoutChannel);
    int Insert(EditLayoutChannel editLayoutChannel);
    void Delete(EditLayoutChannel layoutChannel);
}

EntityMappers do very little in their constructors, so it's okay if a controller has multiple repository dependencies. Not only does the Entity Framework reuse connections, but the Entity Contexts themselves are only created when one of the repository methods gets called.

Each module also has a special Test project, which contains unit tests for the classes in these three tiers. We've even come up with a way to make our repositories and other data-access classes somewhat unit-testable. Now that we've got this basic infrastructure set up, adding functionality to our web application is generally pretty smooth and not too error-prone.

感性不性感 2024-10-02 16:35:32

问题 2:避免这种情况的方法是使用类似“ADO.NET C# POCO 实体生成器"。

Problem 2: A way to avoid this would be to use something like the "ADO.NET C# POCO Entity Generator".

蹲墙角沉默 2024-10-02 16:35:32

ADO.NET 连接池将在幕后管理连接。您使用多少个不同的实体(因此具有自己的上下文的存储库)基本上并不重要;每个数据库操作都将从同一个池中获取连接。

存储库的原因是使您能够抽象/替换为测试等而创建实体的方式。实体对象可以像普通对象一样实例化,而无需上下文的服务,因此测试存储库将为测试数据执行此操作

ADO.NET connection pooling will be managing the connections behind-the-scenes. It basically won't matter at all how many different entities (and therefore Repositories with their own context) you use; each DB operation will be taking connections from the same pool.

The reason for the repository is to enable you to abstract/replace the way the entities are being created for testing, etc. The entity objects can be instantiated like normal objects without the Context's services, so the test repo would do that for the test data

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