ASP.NET MVC使用仓储模式

11

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

问题1: 考虑到每个实体都需要自己的存储库,因此必须为数据源(假设使用EF的数据库)设置连接,如果我需要在单个页面上从5个不同实体中获取数据,这是否会产生很多额外开销?

问题2: 我在所有在线例子中看到的也是大多数人(甚至像Shanselman这样的人)使用由LINQ或EF生成的实体类来实现存储库模式,这难道不违背了与松散耦合有关的存储库模式的目的吗?另一方面,替代方案是什么,例如结合AutoMapper的POCO类?(这让我有点害怕)

我希望有几个人能够解答这个问题,因为我有点困惑存储库模式是否适用于网站。

6个回答

3

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

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


我知道SQL服务器使用连接池的事实,但我的主要关注点是EF或LINQ如何维护其上下文,似乎有很多事情在水下进行,而这个过程会带来很多开销,或者我完全错了? - Fabian
它的设计正是为了避免这种开销。通常情况下,您可以一次性创建许多ObjectContext对象而不会出现任何问题。但不要误解;您不应该粗心大意。 - user438034
EF/LINQ在底层使用ADO.NET,因此它们最终会使用相同的连接池机制。 - Andrew Barber

3

我之前读过那本书,现在打算再看一遍他的例子。 - Fabian
那个第二个链接对我最有帮助,指导了我最终实现的内容。 - Fabian

3
首先,我不知道每个实体都需要有自己的存储库的要求,因此我会放弃这种限制。
对于Scott H的实现,我想您指的是Nerd Dinner应用程序,他自己承认并没有真正使用存储库模式。
存储库模式的目的就像您所猜测的那样,是将数据存储库与其上层分离。它不仅仅是为了测试而存在,还允许您更改后端存储库而不影响UI /业务逻辑。
在纯粹的术语中,您将创建POCO,然后从存储库返回给BL,通过使用接口来定义存储库契约,您可以传递和使用接口而不是具体的实现。这将允许您传递实现存储库接口的任何对象,无论是实时存储库还是模拟存储库。
实际上,我在MVC中使用带有Linq to SQL作为后备存储库的存储库,这样我就可以在我的BL中使用手工制作的L2S对象,这些对象具有未持久化到后备存储库的附加字段和功能。这样,我就可以从L2S方面获得一些很棒的功能,如更改跟踪、对象层次结构等,同时还允许我替换TDD的模拟存储库。

我觉得我正在成为那些纯粹主义者之一,如果我想要避免很多左手右手编码,是自动映射器(automapper)唯一的实例化方式吗?而手工制作的L2S对象让我感到好奇,这就像POCO但又不完全相同? - Fabian
您可以使用自动映射器扩展类(我相信EF会创建部分类),但是EF存在许多其他问题,我还没有需要使用它,并且至少有一个人想要放弃它。手工创建的L2S对象实际上是POCO,但使用Linq To SQL属性进行修饰,并通过由存储库实例化的数据上下文进行访问。 - Lazarus

2
你很准确地指出了将实体用作业务对象的困难。经过多次尝试,我们已经确定了一个模式,将应用程序分为模块,并将每个模块分为三个层:Web(前端)、Core(业务)和Data。在我们的情况下,每个层都有自己的项目,因此有一项强制措施防止我们的依赖关系变得耦合紧密。
Core层包含实用类、POCOs和仓储接口。
Web层利用这些类和接口获取所需信息。例如,MVC控制器可以将特定的仓储接口作为构造函数参数,因此当创建控制器时,我们的IoC框架会注入该仓储的正确实现。仓储接口定义了选择器方法,返回在Core业务层中定义的POCO对象。
Data层的全部责任是实现在Core层中定义的仓储接口。它具有表示数据存储的Entity Framework上下文,但不返回实体(技术上是“数据”对象),而是返回在Core业务层中定义的POCOs(我们的“业务”对象)。
为了减少重复,我们有一个抽象的通用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在它们的构造函数中几乎不执行任何操作,因此如果控制器具有多个存储库依赖项也是可以的。Entity Framework不仅重用连接,而且只有在调用存储库方法之一时才创建实体上下文。
每个模块还有一个特殊的“测试”项目,其中包含这三个层中类的单元测试。我们甚至想出了一种使我们的存储库和其他数据访问类有些可单元测试的方法。现在我们已经建立了这个基本的基础设施,向我们的Web应用程序添加功能通常非常顺畅,而且不太容易出错。

2

我现在正在使用ADO.NET C# POCO实体生成器,配合Entity Framework 4.1和Structure Maps作为我们的依赖注入。我们还使用MvcScaffolding来生成基于pocos的控制器、存储库和接口。到目前为止,一切都运行得很顺利,测试用例和模拟也很容易完成,生成的pocos干净且易于使用。我建议您研究一下这个工具。 - Chris

1

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

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


1
关于连接池的附加说明:这假定所有连接都使用相同的连接字符串。您可能使用的每个单独的连接字符串(即使唯一的区别是添加了一个空格)都会获得自己的连接对象池。 - Andrew Barber
如果从性能角度来看,我使用一个数据上下文还是10个数据上下文都无所谓? - Fabian
就连接而言,没有任何可测量的差异。这一切还假设没有人向数据上下文或存储库添加了大量“沉重”的代码。 - Andrew Barber
正确。唯一的问题可能是GC开销,但这个开销非常微不足道,我甚至不应该提到它。 - user438034
1
不应该提到什么? :P - Andrew Barber

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接