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


95

我一直使用存储库模式,但是对于我的最新项目,我想看看是否可以完美使用它以及实现“工作单元”。我越开始挖掘,就会开始问自己一个问题:“我真的需要吗?”

现在,这一切都从对Stackoverflow的一些评论开始,并追溯到Ayende Rahien在他的博客上的帖子,其中包括2个具体的,

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

  1. 这种方法是否适合实体框架项目?
  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一个接口并将其注入控制器中?

Answers:


103

我走了许多路,并在不同项目上创建了许多存储库实现,并且...我放弃了,放弃了,这就是原因。

编码例外

您是否为数据库将要从一种技术转换为另一种技术的1%机会编写代码?如果您正在考虑企业的未来状态,并说是有可能,则a)他们必须有很多钱来负担向另一种DB技术的迁移,或者b)您出于娱乐目的选择DB技术或c )您决定使用的第一项技术出了什么问题。

为什么要放弃丰富的LINQ语法?

LINQ和EF是经过开发的,因此您可以使用它做一些整洁的事情来读取和遍历对象图。创建和维护一个存储库,可以给您同样的灵活性,这是一项艰巨的任务。在我的经验,任何时候,我已经创建了一个仓库,我总是有业务逻辑泄露到存储库层要么使查询更好的性能和/或降低命中数据库的数量。

我不想为我必须编写的查询的每个单个排列创建一个方法。我不如写存储过程。我不想GetOrderGetOrderWithOrderItemGetOrderWithOrderItemWithOrderActivityGetOrderByUserId,等等......我只想让主实体和遍历,包括对象图,因为我如此的愉悦。

存储库的大多数示例都是胡扯

除非您正在开发诸如博客之类的真正准系统,否则您的查询绝不会像在Internet上围绕存储库模式的示例中的90%那样简单。我对此压力还不够!这是一个必须在泥泞中爬行才能弄清楚的东西。总会有一个查询打破您所创建的完美存储库/解决方案,直到您再次猜测自己并且技术债务/侵蚀开始为止。

不要对我进行单元测试

但是,如果没有存储库,单元测试该怎么办?我将如何嘲笑?很简单,您不需要。让我们从两个角度来看:

没有存储库-您可以DbContext使用IDbContext或其他技巧来模拟使用,但是您实际上是在对LINQ to Object而不是LINQ to Entities进行单元测试因为查询是在运行时确定的。因此,现在由集成测试来解决。

使用存储库-您现在可以模拟您的存储库并在其之间对层进行单元测试。很好吗?好吧,不是真的。在上述情况下,您必须将逻辑泄漏到存储库层中,以使查询的性能更高和/或对数据库的命中次数更少,您的单元测试如何覆盖呢?现在在仓库层,您不想测试IQueryable<T>吗?另外,说实话,您的单元测试将不会涵盖具有20行.Where()子句和.Include()一堆关系,然后再次访问数据库以执行所有其他工作,等等,等等,因为查询是在运行时生成的。同样,由于创建了一个存储库以使上层的持久性无知,因此,如果您现在想更改数据库技术,那么很遗憾,单元测试绝对不能保证在运行时返回到集成测试时得到相同的结果。因此,整个存储库的内容似乎很奇怪。

2美分

在普通存储过程(批量插入,批量删除,CTE等)上使用EF时,我们已经失去了很多功能和语法,但是我也用C#编写代码,因此不必键入二进制。我们使用EF,因此我们可以使用不同的提供程序,并可以在许多事物之间以一种很好的相关方式使用对象图。某些抽象很有用,而有些则没有。


16
您没有创建存储库以能够对其进行单元测试。您创建存储库以能够对业务逻辑进行单元测试。至于确保查询有效:为存储库编写集成测试要容易得多,因为它们仅包含逻辑而不包含任何业务。
jgauffin

16
Coding for the exception注意:使用存储库不能切换数据库引擎。这是将业务与持久性分开。
jgauffin

2
这些都是非常有效的观点,背后有很多真理。但是,缺乏的认识是,LINQ散布在应用程序中而不是约束在一致的位置上,从而在代码隐藏页中创建了SQL调用的EF等效项。每个LINQ查询都是应用程序中潜在的维护点,并且数量越多(它们越广泛),维护成本和风险就越高。想象一下,向实体添加“已删除”标志,并且不得不在查询该实体的大型应用程序中定位每个位置,必须修改每个位置……
DVK 2016年

2
我认为这是短视和厌倦的。为什么将逻辑泄漏到存储库中?如果您这样做了,那为什么重要呢?这是一个数据实现。我们正在做的就是将LINQ与其他代码隔离开,将其隐藏在仓库中。您说不进行测试,但是您将无法进行测试作为反对这样做的理由。因此,请创建存储库,不要公开IQueryable,也不要对其进行测试。至少您可以独立于数据实现来测试其他所有内容。数据库更改的1%机会仍然是一个巨大的明智选择。
Sinaesthetic '16

5
为此答案+1。我发现我们真的不需要Entity Framework Core的存储库。该DbSet仓库,并且DbContext工作单位。为什么在ORM已经为我们做到这一点时实施存储库模式!要进行测试,只需将提供程序更改为InMemory。并进行测试!它在MSDN中有详细记录。
Mohammed Noureldin

49

存储库模式是一种抽象。目的是降低复杂性并使其余的代码持久性变得无知。另外,它允许您编写单元测试而不是集成测试。

问题在于,许多开发人员无法理解模式的目的,无法创建存储库,这些存储库将持久性特定的信息泄漏给调用者(通常是通过公开IQueryable<T>)。这样做与直接使用OR / M无关。

更新以解决另一个答案

编码例外

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

单元测试与集成测试

您不为存储库编写单元测试。期。

但是,通过引入存储库(或持久性和业务之间的任何其他抽象层),您可以编写业务逻辑的单元测试。也就是说,您不必担心由于数据库配置错误而导致测试失败。

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

区别在于,如果您没有将业务与LINQ语句混合使用,则可以100%确保失败的原因是持久性代码,而不是其他原因。

如果您对测试进行分析,并且您没有混合考虑(例如LINQ +业务逻辑),您还将看到它们更加干净。

储存库示例

多数例子是胡扯。这是真的。但是,如果您用Google搜索任何设计模式,都会发现很多糟糕的示例。那是避免使用模式的理由。

构建正确的存储库实现非常容易。实际上,您只需要遵循一个规则:

直到需要时才将任何东西添加到存储库类中

许多编码人员都很懒惰,他们试图建立一个通用存储库,并使用具有许多可能需要的方法的基类。亚尼 您只需编写一次存储库类,并在应用程序有效期(可能是数年)内保留它。为什么通过懒惰来干它。保持干净无任何基类继承。这将使其更易于阅读和维护。

(上面的陈述只是一个准则,而不是法律。基类很容易受到激励。在添加它之前,请三思而后行,以便出于正确的理由添加它)

老东西

结论:

如果您不介意在您的业务代码中使用LINQ语句,也不关心单元测试,那么我认为没有理由不直接使用Entity Framework。

更新资料

我已经在博客中介绍了存储库模式以及“抽象”的真正含义:http : //blog.gauffin.org/2013/01/repository-pattern-done-right/

更新2

对于具有20多个字段的单个实体类型,您将如何设计查询方法以支持任何排列组合?您不希望仅按名称限制搜索,使用导航属性进行搜索,列出具有特定价格代码的商品的所有订单,3级导航属性搜索。IQueryable发明的全部原因是能够对数据库进行搜索的任何组合。理论上一切都看起来不错,但用户的需求胜过理论。

再次:具有20个以上字段的实体建模错误。这是神的实体。分解。

我不是在说那IQueryable不是出于质疑。我说的是像存储库模式这样的抽象层不正确,因为它是泄漏的。没有100%完整的LINQ To Sql提供程序(如EF)。

它们都有特定于实现的内容,例如如何使用预先加载/延迟加载或如何执行SQL“ IN”语句。IQueryable在资源库中公开会迫使用户了解所有这些内容。因此,将数据源抽象出来的整个尝试都是完全失败的。您只是增加了复杂性,而没有直接使用OR / M带来任何好处。

要么正确实现存储库模式,要么根本不使用它。

(如果您真的想处理大型实体,则可以将Repository模式与Specification模式结合使用。这将为您提供完整的抽象,这也是可测试的。)


6
不公开IQueryable会导致搜索受限,人们最终会为不同类型的查询创建更多的Get方法,最终使存储库更加复杂。
Akash Kava 2013年

3
您根本没有解决核心问题:通过存储库公开IQueryable并不是一个完整的抽象。
jgauffin

1
使用imo的方法是拥有一个查询对象,该对象包含要在其中执行的所有必需基础结构。您为它提供搜索词字段,它会返回结果列表。在QO内部,您可以做任何您想做的事情。它是一个界面,因此易于测试。请参阅上面的我的帖子。这是最好的。
h.alex

2
就我个人而言,我认为在Repository类上实现IQueryable <T>接口也是有意义的,而不是在其成员之一中公开基础集。
dark_perfect 2013年

3
@yat:每个聚合根有一个存储库。但是恕我直言,它不是汇总表的根和汇总,而只是汇总根和。实际的存储可能只使用一个表或其中的很多表,即它可能不是每个聚合和一个表之间的一对一映射。我使用存储库来降低复杂性并删除基础存储的任何依赖性。
jgauffin

27

IMO 在任何有意义的开发中,Repository抽象和UnitOfWork抽象都具有非常重要的地位。人们会争论实现细节,但是就像有很多方法可以给猫皮一样,也有很多实现抽象的方法。

您的问题专门用于使用或不使用以及原因。

毫无疑问,您已经在Entity Framework中内置了这两种模式,分别DbContextUnitOfWorkDbSetRepository。你不一般需要单元测试UnitOfWorkRepository自己,因为他们只是你的类和底层数据访问的实现之间方便。当您对服务的逻辑进行单元测试时,您会发现自己需要一次又一次地模拟这两个抽象。

您可以使用外部库进行模拟,伪造或其他任何模拟,在执行测试的逻辑与被测试的逻辑之间添加代码层依赖关系(您无法控制)

因此,有一点要注意的是,在模拟单元测试时拥有自己的抽象UnitOfWorkRepository为您提供最大的控制和灵活性。

很好,但是对我而言,这些抽象的真正力量在于它们提供了一种应用面向方面的编程技术并遵循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();
    }
}

到目前为止没有什么异常,但是现在我们想使用日志记录装饰器添加一些日志记录功能。

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();
    }
}

全部完成,无需更改我们现有的代码。我们还可以添加许多其他跨领域关注点,例如异常处理,数据缓存,数据验证等,在整个设计和构建过程中,我们拥有的最有价值的东西使我们能够添加简单的功能而无需更改任何现有代码是我们的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,您应该始终将一个简单的抽象放在将要在多个地方引用的任何第三方组件上。从这个角度来看,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>)));
        }
    }
}

11

首先,正如一些答案所暗示的那样,EF本身是一个存储库模式,无需创建进一步的抽象即可将其命名为存储库。

用于单元测试的可模拟存储库,我们真的需要它吗?

我们让EF与单元测试中的测试数据库进行通信,以直接针对SQL测试数据库测试我们的业务逻辑。我完全没有模拟任何存储库模式的任何好处。对测试数据库进行单元测试的真正错误是什么?由于批量操作是不可能的,因此我们最终编写了原始SQL。内存中的SQLite是针对实际数据库进行单元测试的理想选择。

不必要的抽象

您是否只是想创建存储库,以便将来可以轻松地用NHbibernate等替换EF或其他?听起来不错的计划,但是真的划算吗?

Linq杀死单元测试?

我想看看有关如何杀死它的任何例子。

依赖注入,IoC

哇,这些都是很棒的字眼,可以肯定它们在理论上看起来很棒,但是有时候您必须在出色的设计和出色的解决方案之间进行权衡。我们确实使用了所有这些内容,最终将所有内容扔到了垃圾桶,然后选择了不同的方法。大小与速度(代码大小和开发速度)在现实生活中至关重要。用户需要灵活性,他们并不关心您的代码在DI或IoC方面是否出色。

除非您要构建Visual Studio

如果您要构建像Visual Studio或Eclipse这样的复杂程序,则需要所有这些出色的设计,这些程序将由许多人开发,并且需要高度可定制。这些IDE经过了数年的开发,所有出色的开发模式都应运而生,并且它们在所有这些出色的设计模式都起着举足轻重的作用。但是,如果您正在执行简单的基于Web的工资单或简单的业务应用程序,则最好是随着时间的发展而发展,而不是花时间为数百万个用户构建它,而仅将其部署给数百个用户。

存储库作为筛选视图-ISecureRepository

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

但是这样做会使存储库变得更加复杂,因为它最终需要维护庞大的代码库。人们最终为不同的用户类型或实体类型的组合创建了不同的存储库。不仅如此,我们还最终获得了许多DTO。

下面的答案是筛选存储库的示例实现,而不创建完整的类和方法的集合。它可能不会直接回答问题,但在推导问题时会很有用。

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

http://entityrestsdk.codeplex.com

牢记这一点,我们开发了一个SDK,该SDK基于SecurityContext创建过滤视图的存储库,其中包含CRUD操作的过滤器。而且只有两种规则可以简化任何复杂的操作。首先是对实体的访问,其次是属性的读/写规则。

好处是,您无需为不同的用户类型重写业务逻辑或存储库,只需阻止或授予他们访问权限即可。

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);
  }
}

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


3
针对数据库的单元测试意味着您对测试有额外的外部要求。如果该数据库关闭或数据被清除,或者该数据库发生任何事情,则测试将失败。这是不希望的。暴露IQueryable的存储库大约需要2分钟的设置时间。这里没有浪费时间。DI为什么要花很长时间?所有这些都需要几分钟。我要说的是,这对于在服务层中对复杂查询进行单元测试非常有用。不需要数据库连接真是太好了。从nuget获取模拟框架大约需要一分钟。这些东西不需要任何时间。
user441521 2014年

@ user441521具有IQueryable 2分钟设置的存储库?您生活在哪个世界中,我们的实时站点上的每个asp.net请求都将在几毫秒内得到响应。模拟和伪造等增加了代码的复杂性,浪费了全部时间。如果未将单元定义为业务逻辑单元,则单元测试无用。
Akash Kava 2014年

7

关于哪种方法正确,存在很多争论,因此我认为这两种方法都是可以接受的,因此我会使用最喜欢的方法(没有存储库,UoW)。

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

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

我相信Ayende也有一些帖子关于如何抽象化CUD操作是多么糟糕。

我总是创建一个接口并从其继承上下文,因此可以将IoC容器用于DI。


那么扩展方法有多广泛?假设我需要获取扩展中另一个实体的状态?这是我目前最大的担忧。您介意展示一些扩展方法示例吗?
Dejan.S 2013年

ayende.com/blog/153473/…ayende.com/blog/153569/…。(这些是对称为s#arp lite的体系结构(框架?)的评论。通常不错,但他不同意存储库和CUD抽象)。
2013年

其基于NHibernate。您没有使用EF的任何示例吗?再一次,当我需要调用另一个实体时,如何在静态扩展方法中做到最好?
Dejan.S 2013年

3
一切都很好,直到域对象的属性需要被未存储在数据库中的数据合并为止。或者您需要使用比膨胀的ORM更高性能的技术。糟糕!ORM根本不能替代存储库,它是一个存储库的实现细节。
cdaq 2013年

2

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

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

而且,大多数EF上的“存储库”甚至都不是很好的Facade,因为它们只是很直接地映射到EF中的单个方法,甚至具有相同的签名。

因此,在EF上应用这种所谓的“存储库”模式的两个原因是允许进行更轻松的测试并为其建立“固定”调用的子集。本身还不错,但显然不是存储库。


1

Linq是当今的“存储库”。

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

这是我们使用的存储库-它使我们与直接使用nhibernate脱钩,但是提供了linq接口(在特殊情况下为ISession访问,最终可能会进行重构)。

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

您为服务层做什么?
Dejan.S 2013年

控制器查询仓库中的只读数据,为什么要添加一些额外的层?另一种可能性是使用“ ContentService”,它越来越倾向于是一个服务级别的存储库:GetXByY等。对于修改操作-应用服务是一种通过使用案例只是抽象-他们使用BL和自由回购..
米卡莱

我曾经为业务逻辑做服务层。我不太确定我对ContentService的关注是什么,请详细说明。将助手类作为“服务层”会是不好的做法吗?
Dejan.S 2013年

“服务层”是指“应用程序服务”。他们可以使用存储库和域层的任何其他公共部分。“服务层”不是一个坏习惯,但我会避免使XService类仅用于提供List <X>结果。抱歉,评论字段似乎太短,无法详细描述服务。
mikalai

假设购物车计算并且您需要获取应用程序设置参数和特定的客户参数来进行计算,然后在应用程序中的多个位置重复使用该计算,该怎么办?您如何处理这种情况?助手类或应用程序服务?
Dejan.S 2013年

1

信息库对于我来说,这次(或者有人选择调用它)主要是关于抽象持久层。

我将它与查询对象结合使用,因此我的应用程序中没有与任何特定技术的结合。而且,它大大简化了测试。

所以,我倾向于

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 (i.e. not need additional configuration - think session), there is no need to include this method in the repository.
    TResult ExecuteQuery<TResult>(IQueryObject<TResult> query);
}

可能添加带有回调作为委托的异步方法。回购协议是容易实现一般,所以我可以不碰线实施的从应用到应用。好吧,至少在使用NH时,这是正确的,我也使用EF做到了,但我讨厌EF。4.对话是交易的开始。如果几个类共享存储库实例,则非常酷。另外,对于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中使用以传递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查询,您必须将上下文包含在Abstract库中,而不是会话中。但是,当然,ifc将是相同的。

这样,查询本身就被封装并且易于测试。最好的是,我的代码仅依赖于接口。一切都很干净。域(业务)对象就是这样,例如,没有职责的混合,例如使用难以测试的活动记录模式,并且在域对象中混合了数据访问(查询)代码,并且这样做是在混合关注点(获取对象)本身??)。每个人仍然可以自由创建用于数据传输的POCO。

总而言之,这种方法提供了很多代码重用和简单性,而这丝毫没有我能想象的。有任何想法吗?

非常感谢Ayende的出色工作和持续的奉献精神。它是他的想法(查询对象),不是我的。


1
持久性实体(您的POCO)不是业务/域实体。存储库的目的是使业务(任意)层与持久性脱钩。
MikeSW 2013年

我看不到耦合。在POCO部分上有些同意,但不在乎。没有什么可以阻止您拥有“真正的” POCO,并且仍然使用此方法。
h.alex 2013年

1
实体完全不必是愚蠢的POCO。实际上,将业务逻辑建模到实体是DDD人群一直在做的事情。这种开发风格与NH或EF很好地融合在一起。
克里斯

1

对我来说,这是一个简单的决定,只有很少的因素。这些因素是:

  1. 存储库用于域类。
  2. 在我的某些应用程序中,域类与我的持久性(DAL)类相同,在其他应用程序中则不同。
  3. 当它们相同时,EF已经为我提供了存储库。
  4. EF提供延迟加载和IQueryable。我喜欢这些。
  5. 在EF上抽象/“存储” /重新实现存储库通常意味着丢失惰性和IQueryable

因此,如果我的应用程序无法证明#2的合理性(将域模型和数据模型分开),那么我通常不会为#5烦恼。

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.