使用存储库模式支持多个提供者
好吧,不确定这是否是正确的标题,但基本上,我在 MVC 应用程序中使用存储库时遇到了很多问题,您可以用一组存储库替换一组存储库,实现不同的数据存储技术。
例如,假设我想在我的应用程序中使用实体框架。但是,我还希望在硬编码列表中实现一组测试数据。我希望有一组接口(IUserRepository、IProductRepository 等——现在我们不讨论更通用的 IRepository
简而言之,问题如下:
-- 如果您要使用实体框架,您希望存储库返回 IQueryable
-- 如果您要使用硬编码列表,您不希望您的存储库返回 IQueryable,因为它会极大地增加开销,而且,Linq to Entities 与 Linq to Objects 显着不同,导致代码中出现许多令人头疼的问题这对于两个提供商来说都是共同的。
换句话说,我发现最好的方法是隔离存储库中所有依赖 EF 的代码,以便存储库本身返回 IEnumerable 或 IList 或类似的代码 - 然后 EF 和其他一些技术都可以使用相同的存储库。因此,所有 IQueryable 都将包含在 EF 存储库中。这样,您就可以将 Linq to Entities 与 EF 存储库结合使用,将 Linq to Objects 与 Test 存储库结合使用。
然而,这种方法将大量的业务逻辑放入存储库中,并导致大量重复的代码——逻辑必须在每个存储库中重复,即使实现有些不同。
存储库作为这一层非常薄且仅连接到数据库的整个概念就消失了——存储库是业务逻辑以及数据存储连接的“存储库”。您不能只拥有“查找”、“保存”、“更新”等功能。
我一直无法解决需要隔离依赖于提供者的代码与将业务逻辑放在集中位置之间的差异。
有什么想法吗?如果有人能给我指出一个解决这个问题的实现示例,我将不胜感激。 (我读了很多书,但找不到任何专门讨论这些问题的内容。)
更新:
我想我开始觉得可能不可能拥有可以替换为不同提供者的存储库 -例如,如果您要使用实体框架,则只需将整个应用程序投入实体框架即可。单元测试?我正在为此苦苦挣扎。到目前为止,我的做法是使用硬编码数据设置一个单独的存储库,并将其用于单元测试,以及在设置数据库之前测试应用程序本身。我想我将不得不寻找不同的解决方案,也许是一些模拟工具。
但这又提出了一个问题:为什么要使用存储库,特别是为什么要使用存储库接口。我正在研究这个。我认为确定最佳实践需要进行一些研究。
Well, not sure if that's exactly the right title, but basically I have been having a lot of problems using repositories in MVC applications in such a way that you can substitute one set of repositories, implementing a different data storage technology, for another.
For example, suppose I want to use Entity Framework for my application. However, I also want to have a set of test data implemented in hard-coded Lists. I would like to have a set of interfaces (IUserRepository, IProductRepository, etc. -- let's not talk about a more generic IRepository<T> for now) that both approaches can instantiate. Then, using (say) a Dependency Injection tool such as Ninject or Castle Windsor, I can switch back and forth between the entity framework provider (accessing the actual database) and the test provider (accessing the lists).
In a nutshell, here's the problem:
-- If you are going to use Entity Framework, you want your repositories returning IQueryable<SomeType>.
-- If you are going to use hard-coded lists, you do NOT want your repositories returning IQueryable, because it adds hugely to the overhead, and plus, Linq to Entities is significantly different from Linq to Objects, causing many headaches in the code that is common to both providers.
In other words, I have found that the best approach isolates all the EF-dependent code within the repositories, so that the repositories themselves return IEnumerable or IList or some such -- then both EF and some other technology can use the same repositories. Thus, all the IQueryable's would be contained WITHIN the EF repositories. That way, you can use Linq to Entities with the EF repositories, and Linq to Objects with the Test repositories.
Yet this approach puts an enormous amount of the business logic into the repositories, and results in much duplicated code -- the logic has to be duplicated in each of the repositories, even if the implementations are somewhat different.
The whole idea of the repositories as this layer that is very thin and just connects to the database is then lost -- the repositories are "repositories" of business logic as well as of data store connectivity. You can't just have Find, Save, Update, etc.
I've been unable to resolve this discrepancy between needing to isolate provider-dependent code, and having business logic in a centralized location.
Any ideas? If anyone could point me to an example of an implementation that addresses this concern, I would be most appreciative. (I've read a lot, but can't find anything that specifically talks about these issues.)
UPDATE:
I guess I'm starting to feel that it's probably not possible to have repositories that can be swapped out for different providers -- that if you are going to use Entity Framework, for example, you just have to devote your whole application to Entity Framework. Unit tests? I'm struggling with that. My practice to this point has been to set up a separate repository with hard-coded data and use that for unit testing, as well as to test the application itself before the database is set up. I think I will have to look to a different solution, perhaps some mocking tool.
But then that raises the question of why use repositories, and especially why use repository interfaces. I'm working on this. I think determining the best practice is going to take a bit of research.
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(1)
我能说什么?欢迎来到俱乐部...
您发现的是许多遵循 EFv4 的“存储库热潮”的开发人员遇到的问题。是的,这就是问题,而且问题确实很复杂。我多次讨论过这个问题:
单独的主题是为什么使用存储库:
基本上你提出的方法是一个解决方案,但你真的想要它吗?在我看来,结果不是存储库,而是暴露大量访问方法的数据访问对象(DAO)。 Martin Fowler 的存储库定义是:
我相信公开
IQueryable
比创建一个类似于存储过程时代存储库的公共接口(每个存储过程一个访问方法(固定查询))要好 100 倍。这个问题可以用泄漏抽象的规则来概括。
IQueryable
是数据库查询的抽象,但IQueryable
提供的功能取决于提供者。不同的提供商=不同的功能集。什么是结论?您是否因为测试而想要这样的架构?在这种情况下,开始使用前两个链接答案中建议的集成测试,因为在我看来,这是最不痛苦的方法。如果您采用建议的方法,您仍然应该使用集成测试来验证隐藏所有 EF 相关逻辑和查询的存储库。
What I can say? Welcome to the club ...
What you found is problem reached by many developers who followed "repository boom" with EFv4. Yes it is the problem and the problem is really complex. I discussed this several times:
Separate topic is why to use repositories:
Basically your proposed way is a solution but do you really want it? In my opinion the result is not repository but the Data Access Object (DAO) exposing plenty of access methods. Repository definition by Martin Fowler is:
I believe exposing
IQueryable
fulfils this 100 times better then creating a public interface similar to repositories from Stored procedures era - one access method per stored procedure (fixed query).The problem can be summarized by the rule of leaky abstraction.
IQueryable
is an abstraction of the database query but the features provided byIQueryable
are dependent on the provider. Different provider = different feature set.What is a conclusion? Do you want such architecture because of testing? In such case start using integration tests as proposed in first two linked answers because in my opinion it is the lest painful way. If you go with your proposed approach you should still use integration tests to verify your repositories hiding all EF related logic and queries.