不使用存储库模式,使用ORM原样(EF)

我总是使用Repository模式,但对于我最近的项目,我想看看能否完善它的使用和“工作单元”的实现。 我开始挖掘越多,就开始问自己这个问题: “我真的需要它吗?

现在,这一切都从Stackoverflow上的一些评论开始,在他的博客上跟踪Ayende Rahien的post,其中有2个具体的,

  • 存储库是最新单
  • 问-ayende生活,而无需储存库,是-他们价值-生活

这可能会被永远讨论,并取决于不同的应用。 我想知道什么,

  1. 这种方法是否适合entity framework项目?
  2. 使用这种方法是业务逻辑仍然在一个服务层,或扩展方法(如下所述,我知道,扩展方法是使用NHib会话)?

这很容易使用扩展方法完成。 干净,简单,可重复使用。

public static IEnumerable GetAll( this ISession instance, Expression<Func<T, bool>> where) where T : class { return instance.QueryOver().Where(where).List(); } 

使用这种方法和Ninject作为DI,我需要使Context成为一个接口,并在我的控制器注入?

我已经走了许多路,并在不同的项目上创build了许多存储库的实现,我已经扔掉了毛巾,放弃了,这是为什么。

编码例外

你的代码是否有1%的机会将你的数据库从一种技术转换到另一种? 如果你正在考虑你的企业的未来状态,并说是的,那么a)他们必须有很多钱才能迁移到另一个数据库技术,或b)你select一个数据库技术的乐趣或c )你决定使用的第一项技术有些可怕的错误。

为什么扔掉丰富的LINQ语法?

LINQ和EF是开发的,所以你可以做它整齐的东西来读取和遍历对象图。 创build和维护一个可以给你相同的灵活性,这是一个可怕的任务。 根据我的经验,任何时候我已经创build了一个仓库,我总是有业务逻辑泄漏到仓库层,以使查询更加执行和/或减less命中数据库的次数。

我不想为每个我必须编写的查询创build一个方法。 我不妨写存储过程。 我不想GetOrder,GetOrderWithOrderItem,GetOrderWithOrderItemWithOrderActivity,GetOrderByUserId,等等…我只是想获得主要的实体和遍历,包括对象图,所以我请。

存储库的大多数例子都是废话

除非你正在开发一些像博客一样的东西,否则你的查询不会像在互联网上发现的90%的例子那样简单。 我无法强调这一点! 这是一个人必须爬过泥土弄清楚。 总会有这样一个查询,它打破了你所创build的存储库/解决scheme的完美思路,直到你第二次猜测自己和技术债务/侵蚀开始。

不要unit testing我兄弟

但是如果我没有一个仓库,那么unit testing呢? 我将如何模拟? 简单的你不会。 让我们从两个angular度来看待它:

没有存储库 – 你可以使用IDbContext或其他一些技巧来模拟DbContext,但是你真的unit testingLINQ到对象,而不是LINQ to Entities,因为查询是在运行时确定的…好吧,这样做不好! 所以,现在它的集成testing,以涵盖这一点。

使用存储库 – 您现在可以模拟您的存储库,并unit testing中间的图层。 很好吗? 那么不是真的…在上面的情况下,你必须泄漏逻辑到存储库层,使查询更加执行和/或更less的命中数据库,你的unit testing如何覆盖? 它现在在回购层,你不想testingIQueryable的权利? 另外,说实话,你的unit testing不会覆盖那些有20行的查询.Where()子句和.Include()一堆关系,再次点击数据库去做所有其他的事情,无论如何,无论如何是因为查询是在运行时生成的。 此外,由于您创build了一个存储库来保持上层持久性是无知的,所以如果您现在想要更改数据库技术,那么抱歉,您的unit testing在运行时肯定无法保证获得相同的结果,返回集成testing。 所以这个版本库的全部内容看起来很奇怪

2美分

当使用EF来处理普通的存储过程(批量插入,批量删除,CTE等)时,我们已经失去了很多function和语法,但是我也使用C#代码,所以我不必input二进制。 我们使用EF,所以我们有可能使用不同的提供者,并以很好的相关方式处理对象图。 某些抽象是有用的,有些则不是。

我希望这可以帮助某个人在互联网上…

存储库模式是一个抽象 。 其目的是降低复杂性,并使其余的代码永远无知。 作为奖励,它允许你写单元testing,而不是集成testing。

问题是,许多开发人员无法理解模式的用途,并创build存储库,将持久性特定信息泄露给调用者(通常通过公开IQueryable<T> )。 通过这样做,他们不会直接使用OR / M。

更新来解决另一个答案

编码例外

使用存储库不是能够切换持久性技术(即更改数据库或使用web服务等)。 这是关于将业务逻辑与持久性分离,以减less复杂性和耦合性。

unit testing与集成testing

您不要为存储库编写unit testing。 期。

但是通过引入仓库(或持久性和业务之间的任何其他抽象层),您可以为业务逻辑编写unit testing。 即不必担心由于configuration不正确的数据库而导致testing失败。

至于查询。 如果你使用LINQ,你也必须确保你的查询正常工作,就像你必须处理存储库一样。 这是使用集成testing完成的。

不同之处在于,如果您没有将您的业务与LINQ语句混合在一起,那么您可以100%确定这是您的持久性代码失败,而不是别的。

如果你分析你的testing,你也会看到,如果你没有混淆担忧(比如LINQ +业务逻辑)

存储库的例子

大多数例子都是废话。 这是非常真实的。 但是,如果你谷歌任何devise模式,你会发现很多蹩脚的例子。 这是没有理由避免使用模式。

构build正确的存储库实现非常简单。 事实上,你只需遵循一个规则:

在你需要的时候,不要将任何东西添加到存储库类中

很多编码器都是懒惰的,并试图build立一个通用的存储库,并使用一个基类,并有许多他们可能需要的方法。 YAGNI。 只要应用程序处于活动状态(可以是几年),就可以编写一次存储库类并保留它。 为什么他妈的懒惰。 保持它干净,没有任何基类inheritance。 这将使它更容易阅读和维护。

(上面的陈述是一个准则,而不是一个法则,一个基类很有动机,在你添加它之前想一下,所以你添加它是为了正确的理由)

老东西

结论:

如果你不介意在你的业务代码中使用LINQ语句,也不关心unit testing,我没有理由不直接使用entity framework。

更新

我已经写了关于存储库模式和什么“抽象”真正的意思: http : //blog.gauffin.org/2013/01/repository-pattern-done-right/

更新2

对于具有20多个字段的单个实体types,您将如何devise查询方法来支持任何排列组合? 你不想限制只search名称,如何search导航属性,列出具有特定价格代码的项目的所有订单,3级导航属性search。 IQueryable被发明出来的全部原因是能够组合search与数据库的任何组合。 理论上一切看起来都很棒,但用户的需求胜过理论。

再次:具有20个字段的实体被错误地build模。 这是一个神的实体。 分解。

我并不是说IQueryable不是用于查询的。 我在说,像Repository模式这样的抽象层是不对的,因为它是泄漏的。 没有100%完整的LINQ To Sql提供者(如EF)。

他们都有特定于实现的东西,比如如何使用预加载/延迟加载或者如何执行SQL“IN”语句。 在存储库中公开IQueryable强制用户知道所有这些事情。 因此,抽象数据源的整个尝试是完全失败的。 您只是增加了复杂性,而没有直接使用OR / M的好处。

要么正确实施Repository模式,要么完全不使用它。

(如果你真的想要处理大的实体,你可以把Repository模式和Specification模式结合起来,这样就可以完成一个抽象的testing。

IMO的Repository抽象和UnitOfWork抽象在任何有意义的开发中都是非常有价值的。 人们会争论实现的细节,但正如有很多方法来剥皮,有很多方法来实现抽象。

你的问题是专门使用或不使用,为什么。

正如你毫无疑问已经意识到你已经有了这些模式内置到entity framework, DbContextUnitOfWorkDbSetRepository 。 您通常不需要unit testingUnitOfWorkRepository本身,因为它们只是简单地在您的类和基础数据访问实现之间进行协助。 当你unit testing你的服务的逻辑时,你会发现自己需要做的,一次又一次地嘲笑这两个抽象。

你可以模拟,假冒或者任何外部库添加代码依赖关系(你不控制)层之间的逻辑做testing和被testing的逻辑。

所以,一个小的观点是, UnitOfWorkRepository自己的抽象,在模拟你的unit testing时给你最大的控制和灵活性。

一切都很好,但对我来说,这些抽象的真正威力在于它们提供了一种简单的方法来应用面向方面编程技术并遵循SOLID原则

所以你有你的IRepository

 public interface IRepository<T> where T : class { T Add(T entity); void Delete(T entity); IQueryable<T> AsQueryable(); } 

其实施:

 public class Repository<T> : IRepository<T> where T : class { private readonly IDbSet<T> _dbSet; public Repository(PPContext context) { _dbSet = context.Set<T>(); } public T Add(T entity) { return _dbSet.Add(entity); } public void Delete(T entity) { _dbSet.Remove(entity); } public IQueryable<T> AsQueryable() { return _dbSet.AsQueryable(); } } 

到目前为止,没有什么不同寻常的,但现在我们想要添加一些日志logging – 使用日志logging装饰器很容易。

 public class RepositoryLoggerDecorator<T> : IRepository<T> where T : class { Logger logger = LogManager.GetCurrentClassLogger(); private readonly IRepository<T> _decorated; public RepositoryLoggerDecorator(IRepository<T> decorated) { _decorated = decorated; } public T Add(T entity) { logger.Log(LogLevel.Debug, () => DateTime.Now.ToLongTimeString() ); T added = _decorated.Add(entity); logger.Log(LogLevel.Debug, () => DateTime.Now.ToLongTimeString()); return added; } public void Delete(T entity) { logger.Log(LogLevel.Debug, () => DateTime.Now.ToLongTimeString()); _decorated.Delete(entity); logger.Log(LogLevel.Debug, () => DateTime.Now.ToLongTimeString()); } public IQueryable<T> AsQueryable() { return _decorated.AsQueryable(); } } 

全部完成,不改变我们现有的代码 。 还有很多我们可以添加的交叉切割问题,例如exception处理,数据caching,数据validation等等,并且在我们的devise和构build过程中,我们拥有最有价值的东西,使我们能够添加简单的function,而无需更改任何现有的代码是我们的IRepository抽象

现在,我多次在StackOverflow上看到了这个问题 – “您如何使Entity Framework在多租户环境中工作?”。

https://stackoverflow.com/search?q=%5Bentity-framework%5D+multi+tenant

如果你有一个Repository抽象,那么答案是“这很容易添加一个装饰器”

 public class RepositoryTennantFilterDecorator<T> : IRepository<T> where T : class { //public for Unit Test example public readonly IRepository<T> _decorated; public RepositoryTennantFilterDecorator(IRepository<T> decorated) { _decorated = decorated; } public T Add(T entity) { return _decorated.Add(entity); } public void Delete(T entity) { _decorated.Delete(entity); } public IQueryable<T> AsQueryable() { return _decorated.AsQueryable().Where(o => true); } } 

国际海事组织(IMO)你应该总是把一个简单的抽象放在任何第三方组件上,这些组件将在几个地方被引用。 从这个angular度来看,一个ORM是很好的候选,因为它在很多我们的代码中被引用。

当有人说“我为什么要对这个或那个第三方库有一个抽象(例如Repository )”的时候,通常会想到这个答案是“为什么不呢?

PS装饰器使用IoC容器(如SimpleInjector)非常简单。

 [TestFixture] public class IRepositoryTesting { [Test] public void IRepository_ContainerRegisteredWithTwoDecorators_ReturnsDecoratedRepository() { Container container = new Container(); container.RegisterLifetimeScope<PPContext>(); container.RegisterOpenGeneric( typeof(IRepository<>), typeof(Repository<>)); container.RegisterDecorator( typeof(IRepository<>), typeof(RepositoryLoggerDecorator<>)); container.RegisterDecorator( typeof(IRepository<>), typeof(RepositoryTennantFilterDecorator<>)); container.Verify(); using (container.BeginLifetimeScope()) { var result = container.GetInstance<IRepository<Image>>(); Assert.That( result, Is.InstanceOf(typeof(RepositoryTennantFilterDecorator<Image>))); Assert.That( (result as RepositoryTennantFilterDecorator<Image>)._decorated, Is.InstanceOf(typeof(RepositoryLoggerDecorator<Image>))); } } } 

首先,正如一些答案所build议的那样,EF本身就是一个存储库模式,不需要创build进一步的抽象就可以将其命名为存储库。

可testing的unit testing版本库,我们真的需要吗?

我们让EF通信在unit testing中testing数据库,以直接对SQLtesting数据库testing我们的业务逻辑。 我根本没有看到任何存储库模式的模拟的好处。 对testing数据库进行unit testing真的是错误的吗? 由于这是批量操作是不可能的,我们最终编写原始SQL。 内存中的SQLite是针对真实数据库进行unit testing的完美select。

不必要的抽象

你想创build存储库,以便将来你可以很容易地取代EF与NHibernate的等或其他? 听起来很棒,但它真的有效吗?

Linq杀死unit testing?

我希望看到有关如何杀死的例子。

dependency injection,IoC

哇,这些都是好话,理论上他们看起来很棒,但是有时你必须在伟大的devise和伟大的解决scheme之间select权衡。 我们确实使用了所有这一切,最后我们抛弃了所有垃圾,并select了不同的方法。 大小与速度(代码的大小和开发速度)在现实生活中是巨大的。 用户需要灵活性,他们不关心你的代码在DI或IoC方面是否有很好的devise。

除非你正在构buildVisual Studio

如果你正在构build一个复杂的程序,比如Visual Studio或者Eclipse,而这个程序将会被很多人开发并且需要高度可定制的话,那么所有这些优秀的devise都是需要的。 这些IDE经过多年的发展,都经历了一个伟大的发展模式,并在所有这些伟大的devise模式非常重要的地方发展。 但是,如果您正在进行简单的基于Web的工资单或简单的业务应用程序,那么随着时间的推移,您最好随着您的开发进度,而不是花费时间为仅为数百个用户部署的数百万用户构build它。

存储库作为筛选视图 – ISecureRepository

另一方面,资源库应该是EF的过滤视图,它通过应用基于当前用户/angular色的必要填充来保护对数据的访问。

但是,这样做会使存储库变得更复杂,因为它最终需要庞大的代码库来维护。 人们最终为不同的用户types或实体types的组合创build不同的存储库。 不仅如此,我们也结束了很多的DTOs。

以下答案是Filtered Repository的示例实现,不需要创build整套的类和方法。 它可能不会直接回答问题,但可以用来推导出问题。

免责声明:我是Entity REST SDK的作者。

http://entityrestsdk.codeplex.com

牢记上面,我们开发了一个SDK,它基于SecurityContext创build过滤视图的存储库,其中包含用于CRUD操作的filter。 只有两种规则可以简化任何复杂的操作。 首先是访问实体,另一个是读/写规则的属性。

优点是,您不会为不同的用户types重写业务逻辑或存储库,只需简单地阻止或授予他们的访问权限即可。

 public class DefaultSecurityContext : BaseSecurityContext { public static DefaultSecurityContext Instance = new DefaultSecurityContext(); // UserID for currently logged in User public static long UserID{ get{ return long.Parse( HttpContext.Current.User.Identity.Name ); } } public DefaultSecurityContext(){ } protected override void OnCreate(){ // User can access his own Account only var acc = CreateRules<Account>(); acc.SetRead( y => x=> x.AccountID == UserID ) ; acc.SetWrite( y => x=> x.AccountID == UserID ); // User can only modify AccountName and EmailAddress fields acc.SetProperties( SecurityRules.ReadWrite, x => x.AccountName, x => x.EmailAddress); // User can read AccountType field acc.SetProperties<Account>( SecurityRules.Read, x => x.AccountType); // User can access his own Orders only var order = CreateRules<Order>(); order.SetRead( y => x => x.CustomerID == UserID ); // User can modify Order only if OrderStatus is not complete order.SetWrite( y => x => x.CustomerID == UserID && x.OrderStatus != "Complete" ); // User can only modify OrderNotes and OrderStatus order.SetProperties( SecurityRules.ReadWrite, x => x.OrderNotes, x => x.OrderStatus ); // User can not delete orders order.SetDelete(order.NotSupportedRule); } } 

这些LINQ规则针对每个操作的SaveChanges方法中的数据库进行评估,并且这些规则充当数据库前面的防火墙。

关于哪种方法是正确的,我有很多的争论,所以我把它看作是可以接受的,所以我使用哪一个我最喜欢的(哪个不是仓库,UoW)。

在EF中,UoW是通过DbContext实现的,DbSets是存储库。

至于如何使用数据层,我只是直接在DbContext对象上工作,对于复杂的查询,我将为可以重用的查询制定扩展方法。

我相信Ayende也有一些关于如何抽象CUD操作不好的post。

我总是做一个接口,并从我的上下文inheritance,所以我可以使用IoC容器的DI。

Linq是当今的“知识库”。

ISession + Linq已经是存储库,您既不需要GetXByY方法也不需要QueryData(Query q)泛化。 对于DAL使用有点偏执,我仍然更喜欢版本库接口。 (从可维护性的angular度来看,我们还需要对特定的数据访问接口有一定的了解)。

这里是我们使用的版本库 – 它使我们不再直接使用nhibernate,而是提供了linq接口(在例外的情况下,作为ISession访问,最终会受到重构的影响)。

 class Repo { ISession _session; //via ioc IQueryable<T> Query() { return _session.Query<T>(); } } 

在这个时候, Repository (或者其中一个select调用它)主要是关于抽象出持久层。

我使用它与查询对象,所以我没有耦合到我的应用程序中的任何特定的技术。 而且它也简化了很多testing。

所以,我倾向于

 public interface IRepository : IDisposable { void Save<TEntity>(TEntity entity); void SaveList<TEntity>(IEnumerable<TEntity> entities); void Delete<TEntity>(TEntity entity); void DeleteList<TEntity>(IEnumerable<TEntity> entities); IList<TEntity> GetAll<TEntity>() where TEntity : class; int GetCount<TEntity>() where TEntity : class; void StartConversation(); void EndConversation(); //if query objects can be self sustaining (ie not need additional configuration - think session), there is no need to include this method in the repository. TResult ExecuteQuery<TResult>(IQueryObject<TResult> query); } 

可能使用callback作为代表添加asynchronous方法。 回购很容易实现一般 ,所以我不能触摸从应用程序到应用程序的执行线。 那么,至less在使用NH的时候是这样,我也是用EF做的,但是让我讨厌EF。 谈话是交易的开始。 非常酷,如果几个类共享存储库实例。 另外,对于NH,我的实现中的一个回购等于在第一个请求时打开的一个会话。

然后查询对象

 public interface IQueryObject<TResult> { /// <summary>Provides configuration options.</summary> /// <remarks> /// If the query object is used through a repository this method might or might not be called depending on the particular implementation of a repository. /// If not used through a repository, it can be useful as a configuration option. /// </remarks> void Configure(object parameter); /// <summary>Implementation of the query.</summary> TResult GetResult(); } 

对于我只在NH中使用的configuration来传入ISession。 在EF中没有任何意义。

一个示例查询将是..(NH)

 public class GetAll<TEntity> : AbstractQueryObject<IList<TEntity>> where TEntity : class { public override IList<TEntity> GetResult() { return this.Session.CreateCriteria<TEntity>().List<TEntity>(); } } 

要执行EF查询,您必须在抽象基础中,而不是会话中具有上下文。 但当然,ifc会是一样的。

这样查询本身就被封装起来了,而且易于testing。 最重要的是,我的代码只依赖于接口。 一切都很干净。 域(业务)对象就是这样的,例如,当使用难以testing的活动logging模式时,不存在混合的责任,并且在域对象中混合数据访问(查询)代码,并且这样做是混合问题(提取对象本身??)。 每个人仍然可以自由创build数据传输的POCO。

总而言之,这种方法提供了大量的代码重用和简单性,但却没有任何我能想象的东西。 有任何想法吗?

并感谢Ayende的伟大的职位,并继续奉献。 这是他的想法(查询对象),而不是我的。

最适用于EF的不是存储库模式。 这是一个Facade模式(将EF方法的调用抽象为更简单,更易于使用的版本)。

EF是应用存储库模式(以及工作单元模式)的一个。 也就是说,EF是抽象数据访问层的人,所以用户不知道他们正在处理SQLServer。

而在这方面,EF上的大多数“知识库”甚至都不是很好的Facades,因为它们只是简单地映射EF中的单一方法,甚至是具有相同签名的点。

因此,在EF上应用这种所谓的“存储库”模式的两个原因是允许更简单的testing并build立一个“固定”调用的子集。 本身不错,但显然不是一个知识库。

对我而言,这是一个简单的决定,只有相对较less的因素。 这些因素是:

  1. 存储库是为域类。
  2. 在我的一些应用程序中,域类与我的持久性(DAL)类相同,而在其他类中则不然。
  3. 当他们一样的时候,EF已经给我提供了知识库。
  4. EF提供延迟加载和IQueryable。 我喜欢这些。
  5. Abstracting/'facading'/re-implementing repository over EF usually means loss of lazy and IQueryable

So, if my app can't justify #2, separate domain and data models, then I usually won't bother with #5.