Repository 返回 IQueryable?仍是 IEnumerable?(转)

做者:田园里的蟋蟀
本文版权归做者和博客园共有,欢迎转载,但未经做者赞成必须保留此段声明,且在文章页面明显位置给出原文连接,不然保留追究法律责任的权利。
 

这是一个颇有意思的问题,咱们一步一步来探讨,首先须要明确两个概念(来自 MSDN):html

  • IQueryable:提供对未指定数据类型的特定数据源的查询进行计算的功能。
  • IEnumerable:公开枚举数,该枚举数支持在非泛型集合上进行简单迭代。

IQueryable 继承自 IEnumerable,它们俩最大的区别是,IQueryable 是表达式树处理,能够延迟查询,而 IEnumerable 只能查询在本地内存中,Repository 的概念就很少说了,在“伪 DDD”设计中,你能够把它看做是数据访问层。数据库

下面咱们先实现 Repository 返回 IEnumerable:api

public interface IBookRepository  
{
    Book GetById();
    IEnumerable<Book> GetAllBooks();
    IEnumerable<Book> GetBy....();
    void Add(Book book);
    void Delete(Book book);
    void SaveChanges();
}

上面是咱们的通常接口设计,包含查询、增长、删除操做,你发现并无修改,其实咱们能够先经过 GetById 操做,而后取得 Book 对象,进行修改,最后执行 SaveChanges 就能够了,在持久化数据库的时候,会判断实体状态值的概念,最后进行应用改变。ide

GetBy....() 表明了一类查询方法,由于咱们的业务比较复杂,对 Book 的查询会千奇百怪,因此,没有办法,咱们只能增长各种查询方法来知足需求,最后可能致使的结果是,一个 Where 对应一个查询方法,IBookRepository 会充斥着各种查询方法,而且这些查询方法通常只会被一个 Application 方法调用,若是你查看下 GetBy....() 方法实现,会发现其实都大同小异,不一样的只是 Where 条件,这样的结果就会致使代码变得很是的臃肿。post

针对上面的问题,怎么办呢?由于 IEnumerable 是查询在本地内存中,因此没有办法,咱们只能这样处理,那如何使用 IQueryable 会是怎样的呢?咱们看下代码:性能

public interface IBookRepository  
{
    IQueryable<Book> GetBooks();
    void Add(Book book);
    void Delete(Book book);
    void SaveChanges();
}

只有一个 GetBooks 查询,那它能知足各种查询需求吗?咱们看下 Application 中调用的代码:单元测试

public class BookApplication : IBookApplication  
{
    private IBookRepository _bookRepository;

    public BookApplication(IBookRepository bookRepository)
    {
        _bookRepository = bookRepository;
    }

    public IEnumerable<Book> GetAllBooks()
    {
        return _bookRepository.GetBooks().AsEnumerable();
    }

    public IEnumerable<Book> GetBooksByUser(int userId)
    {
        return _bookRepository.GetBooks().Where(b => b.UserId == userId).AsEnumerable();
    }

    //....
}

由于 IQueryable 是延迟查询,只有在执行 AsEnumerable 的时候,才会真正去查询,也能够这么说,BookApplication 能够根据需求任意构建查询表达式树,就像咱们在 SQL Server 中写查询 SQL, SELECT * FORM Books  在 BookRepository 中进行构建,WHERE ... 操做在 BookApplication 中进行构建,最后的 F5 执行也在 BookApplication 中。测试

从上面的代码中,咱们能够看到,IQueryable 很好的解决了使用 IEnumerable 所出现的问题,一个查询能够应对变幻无穷的应用查询,IQueryable 看起来好像是那么的强大,其实 IQueryable 的强大并不限于此,上面说的是查询表达式,那添加、修改和删除操做,可使用它进行完成吗?修改和删除是能够的,添加并不能,具体能够参考 dudu 的这篇博文:开发笔记:基于EntityFramework.Extended用EF实现指定字段的更新ui

关于 EntityFramework.Extended 的扩展,须要记录下,由于这个东西确实很是好,改变了咱们以前的不少写法和问题,好比,在以前使用 EF 进行修改和删除实体,咱们通常会这些写:idea

public class BookApplication : IBookApplication  
{
    private IBookRepository _bookRepository;

    public BookApplication(IBookRepository bookRepository)
    {
        _bookRepository = bookRepository;
    }

    public void UpdateNameById(int bookId, string bookName)
    {
        var book = _bookRepository.GetById(bookId);
        book.BookName = bookName;
        _bookRepository.SaveChanges();
    }

    public void UpdateNameByIds(int[] bookIds, string bookName)
    {
        var books = _bookRepository.GetBooksByIds(bookIds);
        foreach (var book in books)
        {
            book.BookName = bookName;
        }
        _bookRepository.SaveChanges();
    }

    public void Delete(int id)
    {
        var book = _bookRepository.GetById(id);
        _bookRepository.Delete(book);//context.Books.Remove(book);
        _bookRepository.SaveChanges();
    }
}

上面的写法有什么问题呢?其实最大的问题就是,咱们要进行修改和删除,必须先获取这个实体,也就是先查询再进行修改和删除,这个就有点多余了,尤为是 UpdateNameByIds 中的批量修改,先获取 Book 对象列表,而后再遍历修改,最后保存,是否是有点 XXX 的感受呢,仔细想一想,还不如不用 EF 来的简单,由于一个 Update SQL 就能够搞定,简单而且性能又高,为何还要使用 EF 呢?这是一个坑?其实使用 EF 也能够执行 SQL,但这就像换了个马甲,没有什么卵用。

针对上面的问题,该如何解决呢?很简单,使用 EntityFramework.Extended 和 IQueryable 就能够,咱们改造下上面的代码:

using EntityFramework.Extensions;

public class BookApplication : IBookApplication  
{
    private IBookRepository _bookRepository;

    public BookApplication(IBookRepository bookRepository)
    {
        _bookRepository = bookRepository;
    }

    public void UpdateNameById(int bookId, string bookName)
    {
        IQueryable<Book> books = _bookRepository.GetBooks();
        books = books.Where(b => b.bookId == bookId);
        books.Update<Book>(b => new Book { BookName = bookName });
    }

    public void UpdateNameByIds(int[] bookIds, string bookName)
    {
        IQueryable<Book> books = _bookRepository.GetBooks();
        books = books.Where(b => bookIds.Contains(bookIds));
        books.Update<Book>(b => new Book { BookName = bookName });
    }

    public void Delete(int id)
    {
        IQueryable<Book> books = _bookRepository.GetBooks();
        books = books.Where(b => b.bookId == id);
        books.Delete<Book>();
    }
}

有没有发现什么不一样呢?原来 IQueryable 还能够这样写?这货竟然不仅是用于查询,也能够用于删除和修改,另外,经过追踪生成的 SQL 代码,你会发现,没有了 SELECT,和咱们直接写 SQL 是同样的效果,在执行修改和删除以前,咱们须要对查询表达树进行过滤,也就是说的,当咱们最后应用修改的时候,会是在这个过滤的查询表达树基础上的,好比上面的 Delete 操做,咱们先经过 bookId 进行过滤,而后直接进行 Delete 就能够了,哇塞,原来是这样的简单。

当 BookApplication 操做变的简单的时候,BookRepository 也会相应变的简单:

public interface IBookRepository  
{
    IQueryable<Book> GetBooks();
    void SaveChanges();//只用于Books.Add(book);
}

一个 IQueryable 表达树,一个 SaveChanges 操做,就能够知足 BookApplication 中的全部操做。


既然 IQueryable 是这么的强大,那用它就行了,为何还要讨论呢?若是你 Google 搜索“Repository IQueryable”关键词,会发现大量的相关文章,我先贴出几个很是赞的讨论:

上面只是部分,关于这类的文章,老外写的很是多,并且评论中的讨论也很是激烈,由于英语实在差,我大概看了一些,出乎我意料以外的是,不少人都不同意 Repository 返回 IQueryable,但讨论的却很是有意思,好比有个老外这样感叹:I'm still not convinced that returning IQueryable is a bad idea, but at least I'm far more aware of the arguments against it. 大体意思是:我仍然不相信返回 IQueryable 是一个坏主意,但至少我更了解他们的反对理由,是否是颇有意思呢?

关于 Repository 返回 IQueryable 的讨论,我大体总结下:

好处:

  1. 延迟执行。
  2. 减小 Repository 重复代码(GetBy...)。
  3. IQueryable 提供更好的灵活性。
  4. ...

坏处:

  1. 隔离单元测试。
  2. 数据访问在 Repository 以外完成。
  3. 数据访问异常在 Repository 以外抛出。
  4. 该领域层将充斥着这些至关详细查询。
  5. ...

好处就很少说了,由于咱们上面已经实践过了,关于坏处,“隔离单元测试”是什么意思呢?也就是说咱们不能很好的对 Repository 进行单元测试,一方面是由于 IRepository 是那么的简单(就两个方法),另外一方面 IQueryable 是查询表达树,它并非完成时,只有在具体调用的时候才会查询完成,因此,对于 Repository 的单元测试,显然是没有任何意义的。

关于 Repository Pattern and IQueryable 这篇博文,我想再说一下,由于这个老外的观点很是赞,首先,它是基于 Repository 模式概念基础上说的,因此,咱们一开始说:在“伪 DDD”设计中,你能够把 Repository 看做是数据访问层。这是两个不一样的前提,我再大体总结下这个老外的观点:

  • However the mistake is not the IQueryable itself, but its purpose.(不是 IQueryable 自己的错误,而是它的目的。)
  • The point is that using IQueryable, you're asking for a query builder and not for a model.(问题的关键是,使用 IQueryable 是一个查询生成器,而不是一个模型。)
  • we want to specify what to get, not how to get it.(咱们想经过规约获得它,而不是怎样去获得。)
  • tell it the what, not the how.

看了上面,是否是有点豁然开朗的感受呢,其实从 Repository 的模式概念方面考虑,使用 IQueryable 确实不是很恰当,但不能否认的是,IQueryable 又这么强大和便利,怎么办呢?就像博文一开始强调的那样:Repository 的概念就很少说了,在“伪 DDD”设计中,你能够把它看做是数据访问层。

因此呢,若是你的项目是“伪 DDD”,而且 Repository 是被你看做“数据访问层”,那么使用 IQueryable 就没啥问题了。

相关文章
相关标签/搜索