问题 ASP.NET MVC使用Repository Pattern


目前我正在使用EF并在我的所有动作中直接使用其datacontext,但是因为我开始阅读松散耦合和可测试性,我认为这不是最好的方法。在我开始重构所有当前代码之前,我试图理解所有的专家和骗子。

问题1: 考虑到每个实体都需要自己的存储库,因此必须建立自己与数据源的连接(假设使用EF的数据库),如果我需要来自单个页面上5个不同实体的数据,那么会产生很多开销吗?

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

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


8692
2017-09-30 09:39


起源



答案:


你可以阅读 这本书。有一个使用Repository模式和LINQ的好例子。
还有这篇文章 使用存储库和工作单元模式与Entity Framework 4.0


3
2017-09-30 09:50



我不久前读了那本书,再看看他的例子 - Fabian
第二个链接最能指导我最终实现的内容。 - Fabian


答案:


你可以阅读 这本书。有一个使用Repository模式和LINQ的好例子。
还有这篇文章 使用存储库和工作单元模式与Entity Framework 4.0


3
2017-09-30 09:50



我不久前读了那本书,再看看他的例子 - Fabian
第二个链接最能指导我最终实现的内容。 - Fabian


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

至于如何实现它,我会使用一些IRepository接口。然后,您可以创建特定的接口,即PostRepository> IRepository,最后,在具体的类中实现它(例如,一个真正的类,以及一个用于测试的虚假内存)。


3
2017-09-30 09:48



我知道sql服务器使用连接池这一事实,但我主要担心的是EF或LINQ维护其上下文的方式,似乎有很多在水下进行,而且5(或更多)时间等于很多开销,或者我我完全是基地吗? - Fabian
它的设计完全是为了避免这种开销。通常,您可以一次创建大量的ObjectContext对象,而不会出现任何问题。但是不要采取错误的方式;你不应该粗心大意。
EF / LINQ使用ADO.NET,因此他们最终使用相同的连接池机制。 - Andrew Barber


首先,我不知道每个实体都需要拥有它自己的存储库,所以我会破坏这个限制。

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

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

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

实际上我使用带有Linq to SQL的MVC的存储库作为我的后备存储,这使我在实际的后备存储上具有一定程度的灵活性,因此我在BL中使用手工制作的L2S对象,这些对象具有不持久的其他字段和功能到后盾商店。这样我就可以从L2S方面获得一些很棒的功能,更改跟踪,对象层次结构等,同时还允许我用模拟的存储库代替TDD。


3
2017-09-30 09:55



我认为我成为那些纯粹主义者之一,如果我想阻止很多左手右手编码,那么自动播放器是实例化的唯一方法吗?手工制作的L2S对象引起了我的兴趣,那就像POCO但不是真的吗? - Fabian
你可以使用automapper然后扩展类(我相信像L2S这样的EF创建了部分)但是EF带来了很多其他问题,我还没有必要使用它,至少知道一个人想要转储它。手工制作的L2S对象实际上是POCO,但使用Linq To SQL属性进行修饰,并通过存储库实例化的数据上下文进行访问。 - Lazarus


在识别使用实体作为业务对象的难度时,您可以在头上敲击。经过多次试验和错误,这是我们已经解决的模式,这对我们来说非常有效:

我们的应用程序分为模块,每个模块分为三层:Web(前端),Core(业务)和Data。在我们的例子中,这些层中的每一层都有自己的项目,因此存在强制执行,导致我们的依赖关系无法紧密耦合。

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

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

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

为了减少重复,我们有一个抽象的,通用的 EntityMapper class,提供将实体映射到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在它们的构造函数中做的很少,所以如果一个控制器有多个存储库依赖项就没关系。实体框架不仅重用连接,而且实体上下文本身仅在调用其中一个存储库方法时创建。

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


2
2017-09-30 16:40





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


2
2018-02-01 18:54



我现在使用ADO.NET C#POCO实体生成器将Entity Framework 4.1和Structure Maps作为依赖注入。我们还使用MvcScaffolding来生成基于pocos的控制器,存储库和接口。到目前为止,事情进展顺利,测试用例和模拟非常容易,并且生成的pocos干净且易于使用。我建议调查这个工具。 - Chris


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

存储库的原因是使您能够抽象/替换实体的创建方式以进行测试等。实体对象可以像没有Context服务的普通对象一样实例化,因此测试存储会为测试数据执行此操作


1
2017-09-30 09:50



有关连接池的附加说明:这假定所有连接都使用相同的连接字符串。您可以使用的每个单独的连接字符串(即使唯一的区别是添加的空间)都会获得自己的连接对象池。 - Andrew Barber
因此,如果我使用单个datacontext或10,如果你看一下性能,这没关系? - Fabian
就连接而言,根本没有可衡量的差异。所有这些也假设没有人在datacontext或存储库中添加了一堆“重”代码。 - Andrew Barber
不应该提什么? :P - Andrew Barber
绝对没有!