public class Repository { private AppDbContext context; public Repository() { context = new AppDbContext(); } public IQueryable<Post> GetPosts() { return context.Posts; } }
然后,我的服务层可以根据需要执行其他方法(其中,分页等)的LINQ
现在我的服务层设置为返回IEnumerable:
public IEnumerable<Post> GetPageOfPosts(int pageNumber,int pageSize) { Repository postRepo = new Repository(); var posts = (from p in postRepo.GetPosts() //this is IQueryable orderby p.PostDate descending select p) .Skip((pageNumber - 1) * pageSize) .Take(pageSize); return posts; }
这意味着在我的代码中,如果我想绑定到中继器或其他控件,我必须做一个ToList().
这是处理返回类型的最佳方式还是在从服务层方法返回之前需要转换为列表?
解决方法
一旦你使用IQueryable,你可以在大多数情况下使用简单的存储库,但是由于在IQueryable上定义的查询是linq-to-entities如果您模拟存储库,则它们是单元测试中的linq对对象=您不会测试实际的实现.您需要集成测试来测试查询逻辑.
一旦您使用IEnumerable,您将拥有非常复杂的存储库的公共接口 – 对于需要在存储库中公开的特殊查询的每个实体,您将需要特殊的存储库类型.这种存储库在存储过程中更常见 – 存储库中的每个方法都映射到单个存储过程.这种类型的存储库提供了更好的分离关注点和更少的泄漏抽象,但同时它消除了大量的ORM和Linq灵活性.
对于最后一个,您可以使用组合方法,您可以使用返回IEnumerable的方法来进行大多数常见的场景(查询使用次数更多),另外一种方法可以将IQueryable用于罕见或复杂的动态构建查询.
编辑:
如在使用IQueryable的评论中所指出的,有一些副作用.当您公开IQueryable时,您必须保持上下文生效,直到执行查询为止 – IQueryable以与IEnumerable相同的方式使用延迟执行,除非您调用ToList,执行查询的第一个或其他函数仍然需要您的上下文.
实现这一点的最简单的方法是在存储库中使用一次性模式 – 在其构造函数中创建上下文,并在存储库处理时进行处理.然后可以使用块并在其中执行查询.这种方法适用于非常简单的场景,您对每个存储库的单个上下文感到满意.更复杂(和常见的)情景需要在多个存储库之间共享上下文.在这种情况下,您可以使用像上下文提供程序/工厂(一次性)这样的东西,并将工厂注入到存储库构造器(或允许提供者创建存储库).这导致DAL层工厂和定制工作单位.