通用存储库有真正的优势吗?


28

正在阅读一些有关为新应用创建通用存储库的优势的文章(示例)。这个主意看起来不错,因为它让我可以使用同一个存储库同时为几种不同的实体类型做几件事:

IRepository repo = new EfRepository(); // Would normally pass through IOC into constructor 
var c1 = new Country() { Name = "United States", CountryCode = "US" };
var c2 = new Country() { Name = "Canada", CountryCode = "CA" };
var c3 = new Country() { Name = "Mexico", CountryCode = "MX" };
var p1 = new Province() { Country = c1, Name = "Alabama", Abbreviation = "AL" };
var p2 = new Province() { Country = c1, Name = "Alaska", Abbreviation = "AK" };
var p3 = new Province() { Country = c2, Name = "Alberta", Abbreviation = "AB" };
repo.Add<Country>(c1);
repo.Add<Country>(c2);
repo.Add<Country>(c3);
repo.Add<Province>(p1);
repo.Add<Province>(p2);
repo.Add<Province>(p3);
repo.Save();

但是,存储库的其余实施都严重依赖Linq:

IQueryable<T> Query();
IList<T> Find(Expression<Func<T,bool>> predicate);
T Get(Expression<Func<T,bool>> predicate);
T First(Expression<Func<T,bool>> predicate);
//... and so on

此存储库模式对Entity Framework效果非常好,并且几乎提供了DbContext / DbSet上可用方法的一对一映射。但是,鉴于LINQ在Entity Framework之外的其他数据访问技术上使用缓慢,与直接使用DbContext相比,这提供了什么优势?

我尝试编写存储库的PetaPoco版本,但PetaPoco不支持Linq表达式,这使得创建通用IRepository接口几乎没有用,除非您仅将其用于基本的GetAll,GetById,Add,Update,Delete和Save。方法并将其用作基类。然后,您必须使用专门的方法创建特定的存储库,以处理我以前可能作为谓词传入的所有“ where”子句。

通用存储库模式对实体框架以外的其他内容有用吗?如果不是,为什么有人要使用它而不直接使用Entity Framework?


原始链接无法反映我在示例代码中使用的模式。这是(更新的链接)。



1
这个问题真的是关于“通用存储库的优势”还是更多的“如何在通用存储库接口后面隐藏复杂的查询”?如果其接口和用法取决于linq,是否可能是通用的?我们的Repositoryapi具有QueryByExample方法,该方法完全独立于搜索技术,并且可以转移实现。
2012年

“它使我可以使用同一个存储库来针对几种不同的实体类型执行多项操作” =>是我还是您只是误解了通用存储库是什么(包括关于所提到的文章)?对我来说,通用存储库始终意味着用单个或接口来模板化所有存储库,而不用单个存储库实例来管理所有实体的持久性,无论其类型如何……
guillaume31 2012年

Answers:


35

通用存储库对于Entity Framework 甚至是无用的(恕我直言,也是不好的)。它不会给已经提供的内容带来任何额外的价值IDbSet<T>(这是通用存储库)。

正如您已经发现的那样,可以用其他数据访问技术的实现替换通用存储库的论点非常薄弱,因为它可能要求编写您自己的Linq提供程序。

关于简化的单元测试的第二个普遍论点也是错误的,因为带有内存数据存储的模拟存储库/集将Linq提供程序替换为另一个具有不同功能的提供程序。Linq-to-entities提供程序仅支持Linq功能的子集-它甚至不支持IQueryable<T>界面上可用的所有方法。在数据访问层和业务逻辑层之间共享表达式树可防止伪造数据访问层-必须将查询逻辑分开。

如果您想拥有强大的“泛型”抽象,则还必须涉及其他模式。在这种情况下,您需要使用抽象查询语言,该抽象查询语言可以由存储库转换为使用过的数据访问层支持的特定查询语言。这由规范模式处理。Linq on IQueryable是规范(但翻译需要提供程序-或一些自定义访问者将表达式树转换为查询),但是您可以定义自己的简化版本并使用它。例如,NHibernate使用Criteria API。仍然最简单的方法是使用具有特定方法的特定存储库。这种方法在单元测试中是最简单的实现,最简单的测试和最简单的伪造,因为查询逻辑被完全隐藏在抽象之后并被分开。


只是一个简单的问题,NHibernate具有ISession可以轻松进行通用单元测试目的模拟的功能,我也很高兴在使用EF时也放弃了“存储库”,但是有没有更简单,直接的方法来重新创建它?类似于ISessionISessionFactory',IDbContext因为据我所知……
PatrykĆwiek2012年

不,没有IDbContext-如果您愿意IDbContext,可以简单地创建一个并在派生上下文中实现它。
Ladislav Mrnka 2012年

因此,您说的是,对于日常的MVC应用程序,IDbSet <T>应该提供足够好的通用存储库以完全忽略存储库模式。除特殊情况外,例如在MVC项目中不允许任何DAL引用的情况。
ProfK

1
好答案。一些开发人员无缘无故地创建了另一层抽象。IMO,EF不需要通用存储库也不需要工作单元(它们是内置的)
ashraf 2013年

1
IDbSet <T>是对实体框架具有依赖关系的通用存储库,这违背了使用通用存储库抽象化对实体框架的依赖关系的目的。
乔尔·麦克贝斯

7

问题不是存储库模式。在获取数据和如何获取数据之间进行抽象是一件好事。

这里的问题是实现。假定一个任意表达式可以用于过滤是最好的选择。

直接使所有对象的存储库工作有点错了。数据对象很少会直接映射到业务对象。在这些情况下,将T传递给过滤器变得没有意义。提供这么多的功能几乎可以保证一旦其他提供商出现后,您将无法支持所有功能。


因此,使用特定方法(例如GetById(int id),SortedList()等)为每个数据对象或一组紧密相关的对象创建一个Repository更有意义吗?我是从存储库中返回数据对象列表,还是将它们转换为具有必要字段的业务对象?Kinda认为这就是服务/业务层中发生的情况。
山姆

1
@sam-是的,我倾向于更具体的存储库。他们是否进行翻译取决于情况可能会发生变化。如果您的域定义正确,我将以域的形式返回。如果数据定义正确,我将以数据结构的形式返回内容。如果两者都不是,我将有一个实体作为明确定义的基础,以便以此为基础并对其进行调整。
Telastyn 2012年

1
您没有理由没有特定的存储库,这些特定的存储库将常见的东西委派给通用的存储库,但是也没有其他特定于存储库的方法来进行更复杂的调用。我已经看过几次了。
埃里克·金

@EricKing我也有,它在那里很好,但是它倾向于泄漏一些抽象,因为通用的东西往往仅由于数据存储方式的通用性而存在(例如,GetByID需要具有ID的表)。
Telastyn 2012年

@Telastyn是的,我同意这一点,但是它也发生在特定的存储库中。泄漏抽象并不是特定于通用存储库。(哇,我还能再笨拙地说一下吗?)
埃里克·金

2

通用数据层(存储库是数据类型的一种特殊类型)的价值在于允许代码更改底层存储机制,而对调用代码几乎没有影响。

从理论上讲,这很好。如您所见,在实践中,抽象通常是泄漏的。一种用于访问数据的机制不同于另一种机制。在某些情况下,您最终要编写两次代码:一次在业务层,然后在数据层重复。

创建通用数据层的最有效方法是事先了解应用程序将使用的不同类型的数据源。如您所见,假设LINQ或SQL是通用的可能会出现问题。尝试改造新的数据存储区可能会导致重写。

[编辑:添加了以下内容。]

它还取决于应用程序对数据层的需求。如果应用程序只是加载或存储对象,则数据层可能非常简单。随着对搜索,排序和过滤器的需求增加,数据层的复杂性增加,抽象开始泄漏(例如,在问题中公开LINQ查询)。但是,一旦用户可以提供自己的查询,则需要仔细权衡数据层的成本/收益。


1

在几乎所有情况下,在数据库上方都具有代码层是值得的。通常,我更喜欢在上述代码中使用“ GetByXXXXX”模式-它使您可以根据需要优化其背后的查询,同时保持UI不受数据接口混乱的影响。

利用泛型绝对是公平的游戏-拥有一种Load<T>(int id)方法很有意义。但是,围绕LINQ建立存储库相当于在2010年代到处都删除sql查询,并增加了一些类型安全性。


0

好了,有了提供的链接,我可以看到它可能是的便捷包装器DataServiceContext,但是并没有减少代码操作,也没有提高可读性。此外,访问权限DataServiceQuery<T>受阻,限制了.Where()和的灵活性.Single()。也没有提供AddRange()或替代。也没有Delete(Predicate)提供可能有用的(repo.Delete<Person>( p => p.Name=="Joe" );删除Joe-s)。等等。

结论:这样的API妨碍了本机API并将其限制为一些简单的操作。


你是对的。那不是使用示例代码中的模式的文章。我到家后会尝试找到链接。
2012年

更新的链接添加到问题的底部。
山姆

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.