【问题标题】:To return IQueryable<T> or not return IQueryable<T> [closed]返回 IQueryable<T> 或不返回 IQueryable<T> [关闭]
【发布时间】:2010-10-17 15:29:50
【问题描述】:

我有一个存储库类,用于包装我的 LINQ to SQL 数据上下文。存储库类是一个业务线类,包含所有数据层逻辑(以及缓存等)。

这是我的 repo 界面的 v1。

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

但要为 FindAll 处理分页,我正在争论是否公开 IQueryable 而不是 IList 以简化分页等情况的接口。

从数据仓库中公开 IQueryable 的优缺点是什么?

非常感谢任何帮助。

【问题讨论】:

  • 在仓库界面使用IEnumerable怎么样?
  • @KonstantinTarkus 我不赞成在存储库或 BLL 中使用 IEnumerable&lt;T&gt;,因为如果您没有注意并在这种情况下对同一个列表进行了许多 foreach 循环,它可能会影响性能您会多次致电GetEnumerator()

标签: c# .net linq-to-sql iqueryable


【解决方案1】:

优点;可组合性:

  • 来电者可以添加过滤器
  • 来电者可以添加寻呼
  • 调用者可以添加排序

缺点;不可测试性:

  • 您的存储库不再是可正确进行单元测试的;你不能依赖a:它工作,b:它做了什么
    • 调用者可以添加一个不可翻译的函数(即没有 TSQL 映射;在运行时中断)
    • 调用者可以添加过滤器/排序,使其表现得像狗一样
  • 由于调用者希望 IQueryable&lt;T&gt; 是可组合的,因此它排除了不可组合的实现 - 或者它会迫使您为他们编写自己的查询提供程序
  • 这意味着您无法优化/分析 DAL

为了稳定性,我已采取在我的存储库中公开IQueryable&lt;T&gt;Expression&lt;...&gt;。这意味着我知道存储库的行为方式,并且我的上层可以使用模拟,而不必担心“实际的存储库是否支持这个?” (强制集成测试)。

我仍然使用 IQueryable&lt;T&gt;inside 存储库 - 但不是超出边界。我发布了一些more thoughts on this theme here。将分页参数放在存储库界面上同样容易。您甚至可以使用扩展方法(在接口上)添加可选分页参数,以便具体类只有 1 个方法可以实现,但调用者可能有 2 或 3 个重载可用。

【讨论】:

  • 如果使用得当,IEnumerable&lt;T&gt; 可以——但主要用于大数据。对于常规查询,我更喜欢封闭集(数组/列表/等)。特别是,我目前正在使用 MVC,我希望 控制器 获取所有数据 - 不要将其推迟到视图 - 否则您无法完全测试...
  • ...控制器,因为您实际上还没有证明它可以获取数据(因为IEnumerable&lt;T&gt; 通常与延迟执行一起使用)。如果 repo 返回 IList (或类似的),那么你知道你已经得到了数据。
  • @Mark 和.. 在单元测试中触发执行程序有什么问题?例如:Assert.IsTrue(result.Any())
  • 使用 IQueryable,会改变实际的查询。使用 IEnumerable,就少了一些 - 但仍然:获取数据是控制器的责任,而不是视图。
  • 关于可选分页参数以及调用者可用的 2 或 3 个重载的最后陈述,您能否为我们指出一篇关于此的文章?
【解决方案2】:

正如前面的回答所提到的,暴露 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
    }
}

第一种方法比内存有优势,因为您将返回更少的数据而不是全部。

【讨论】:

  • 这不是那么优雅。这样 CVertex 必须将这些参数(开始、大小)添加到他创建的每个存储库方法中 - 非常糟糕的编程习惯。
  • 好吧,这比暴露可以修改和破坏您的数据的数据库接口更好。
  • 只需要在实际使用的地方添加分页参数即可。如果它们被使用,它不会浪费精力。
  • 哎呀,您的第二种方法将成为应用程序杀手!它将所有结果拉入内存,然后对它们执行 LINQ。老实说,我从来没有找到使用AsQueryable 的真正理由。我什至不确定它为什么存在。它不会突然将您的 List 变成 IQueryable 并允许您构建发送到数据存储的表达式树,那么有什么意义呢?
  • @AlexFord AsQueryable 在您从旧版 API 获取非泛型 IEnumerable 时很有用。
【解决方案3】:

我建议使用<strong>IEnumerable</strong> 而不是<strong>IList</strong>,这样你会有更大的灵活性。

这样,您将能够从 Db 中仅获取您真正要使用的那部分数据,而无需在存储库中完成额外的工作。

示例:

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

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

超级干净和简单。

【讨论】:

  • 为什么?有哪些取舍?
  • IList FindAll() 如果您要向用户显示分页列表(假设将从 db 中获取 1000 行,但其中只有 10 行将是如图所示)。使用 IQueryable 你不会有这样的问题。
  • 顺便说一句,如果您要实现分页功能,请查看 MvcContrib = codeplex.com/mvccontrib(MvcContrib.Pagination 命名空间)中现有的帮助程序类
  • 我喜欢创建详细的存储库方法。例如GetRecords(int page, int itemsPerPage)。而不是将分页传递给控制器​​。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2018-12-08
  • 1970-01-01
  • 1970-01-01
  • 2012-01-09
  • 2010-10-02
相关资源
最近更新 更多