返回 IQueryable < T > 或不返回 IQueryable < T >

我有一个将 LINQ 封装为 SQL 数据上下文的存储库类。存储库类是包含所有数据层逻辑(以及缓存等)的业务行类。

这是我的回购界面的 v1。

public interface ILocationRepository
{
IList<Location> FindAll();
IList<Location> FindForState(State state);
IList<Location> FindForPostCode(string postCode);
}

但是为了处理 FindAll 的分页,我正在讨论是否要公开 IQueryable < ILocation > 而不是 IList,以简化诸如分页之类的情况的接口。

从数据回收中暴露 IQueryable 的利弊是什么?

非常感谢你的帮助。

18717 次浏览

我建议使用 IEnumerable而不是 IList,这样你会有更多的灵活性。

通过这种方式,您将只能从数据库获取您真正要使用的那部分数据,而不需要在存储库中做额外的工作。

样本:

// Repository
public interface IRepository
{
IEnumerable<Location> GetLocations();
}


// Controller
public ActionResult Locations(int? page)
{
return View(repository.GetLocations().AsPagination(page ?? 1, 10);
}

非常干净简单。

优点; 可组合性:

  • 调用者可以添加过滤器
  • 调用方可以添加分页
  • 调用方可以添加排序
  • 等等

缺点; 不可测试性:

  • 您的存储库不再是适当的单元可测试的; 您不能依赖 a: 它工作,b: 什么它工作;
    • 调用者可以添加一个不可翻译的函数(例如,没有 TSQL 映射; 运行时中断)
    • 调用者可以添加一个过滤器/排序,使其像狗一样工作
  • 因为调用者期望 IQueryable<T>是可组合的,所以它排除了不可组合的实现-或者它强迫您为它们编写自己的查询提供程序
  • 这意味着你不能优化/配置 DAL

为了稳定起见,我已经将 IQueryable<T>Expression<...>暴露在我的存储库上。这意味着我知道存储库是如何工作的,我的上层可以使用模拟,而不必担心“实际存储库是否支持这一点?”(强制集成测试)。

我仍然使用 IQueryable<T>在里面的存储库-但不超过边界。我贴了一些 更多关于这个主题的想法请点击这里。在存储库接口上放置分页参数同样容易。您甚至可以使用扩展方法(在接口上)来添加 可以选择分页参数,这样具体的类只有1个方法要实现,但可能有2或3个重载可供调用方使用。

正如前面的答案所提到的,暴露 IQueryable 可以让调用者使用 IQueryable 本身,这是非常危险的,也可能会变得非常危险。

封装业务逻辑的首要责任是维护数据库的完整性。

您可以继续暴露 IList,并可能改变您的参数如下,这是我们正在做的..。

public interface ILocationRepository
{
IList<Location> FindAll(int start, int size);
IList<Location> FindForState(State state, int start, int size);
IList<Location> FindForPostCode(string postCode, int start, int size);
}

如果 size = = -1,则返回所有..。

另一种方式。

如果仍然想返回 IQueryable,那么可以在函数中返回 List 的 IQueryable。例如..。

public class MyRepository
{
IQueryable<Location> FindAll()
{
List<Location> myLocations = ....;
return myLocations.AsQueryable<Location>;
// here Query can only be applied on this
// subset, not directly to the database
}
}

第一个方法比内存有一个优点,因为您将返回更少的数据,而不是所有的数据。