5

在由 DAL Repository 构建时,我偶然发现了一个名为 Pipes and Filters 的概念。我在这里,这里读到了它,并从这里看到了一个截屏视频。我仍然不确定如何实施这种模式。理论上听起来不错,但我们如何在企业场景中真正实现这一点?

如果您在问题中提到的数据映射器/ORM 的上下文中对此模式有任何资源、提示或示例,我将不胜感激。

提前致谢!!

4

1 回答 1

11

最终,LINQ onIEnumerable<T> 一种管道和过滤器的实现。IEnumerable<T>是一个流式API - 这意味着数据会在您请求时延迟返回(通过迭代器块),而不是一次加载所有内容,并返回一个大的记录缓冲区。

这意味着您的查询:

var qry = from row in source // IEnumerable<T>
          where row.Foo == "abc"
          select new {row.ID, row.Name};

是:

var qry = source.Where(row => row.Foo == "abc")
            .Select(row = > new {row.ID, row.Name});

当您对此进行枚举时,它将懒惰地消耗数据。您可以使用 Jon Skeet 的Visual LINQ以图形方式查看这一点。唯一破坏管道的是强制缓冲的东西。OrderBy, GroupBy, 等等。对于大量工作,Jon 和我自己在Push LINQ上进行了聚合,在这种情况下没有缓冲。

IQueryable<T>(由大多数 ORM 工具公开 - LINQ-to-SQL、实体框架、LINQ-to-NHibernate)是一个略有不同的野兽;因为数据库引擎将完成大部分繁重的工作,所以大部分步骤可能已经完成 - 剩下的就是消耗一个IDataReader并将其投影到对象/值 - 但这通常仍然是一个管道(IQueryable<T>实现IEnumerable<T>) 除非你打电话.ToArray().ToList()

关于在企业中使用......我的观点IQueryable<T>在存储库中编写可组合查询很好,但它们不应该离开存储库 - 因为这会使存储库的内部操作受调用者的影响,所以你将无法正确地进行单元测试/配置文件/优化等。我已经开始在存储库中做一些聪明的事情,但返回列表/数组。这也意味着我的存储库不知道实现。

这是一种耻辱——因为从存储库方法“返回”的诱惑IQueryable<T>非常大;例如,这将允许调用者添加分页/过滤器/等 - 但请记住他们实际上还没有使用数据。这使得资源管理成为一种痛苦。此外,在 MVC 等中,您需要确保控制器调用.ToList()或类似调用,以便它不是控制数据访问的视图(否则,您不能再次对控制器进行正确的单元测试)。

DAL 中过滤器的安全(IMO​​)使用如下:

public Customer[] List(string name, string countryCode) {
     using(var ctx = new CustomerDataContext()) {
         IQueryable<Customer> qry = ctx.Customers.Where(x=>x.IsOpen);
         if(!string.IsNullOrEmpty(name)) {
             qry = qry.Where(cust => cust.Name.Contains(name));
         }
         if(!string.IsNullOrEmpty(countryCode)) {
             qry = qry.Where(cust => cust.CountryCode == countryCode);
         }
         return qry.ToArray();
     }
}

在这里,我们即时添加了过滤器,但在我们调用ToArray. 此时,数据被获取并返回(在流程中处理数据上下文)。这可以进行完全单元测试。如果我们做了类似但刚刚返回IQueryable<T>的事情,调用者可能会做类似的事情:

 var custs = customerRepository.GetCustomers()
       .Where(x=>SomeUnmappedFunction(x));

突然之间,我们的 DAL 开始失败(无法转换SomeUnmappedFunction为 TSQL 等)。不过,您仍然可以在存储库中做很多有趣的事情。

这里唯一的痛点是它可能会让你有一些重载来支持不同的调用模式(有/没有分页等)。在可选/命名参数到达之前,我发现这里最好的答案是在接口上使用扩展方法;这样,我只需要一个具体的存储库实现:

class CustomerRepository {
    public Customer[] List(
        string name, string countryCode,
        int? pageSize, int? pageNumber) {...}
}
interface ICustomerRepository {
    Customer[] List(
        string name, string countryCode,
        int? pageSize, int? pageNumber);
}
static class CustomerRepositoryExtensions {
    public static Customer[] List(
          this ICustomerRepository repo,
          string name, string countryCode) {
       return repo.List(name, countryCode, null, null); 
    }
}

现在我们开启了虚拟重载(作为扩展方法)ICustomerRepository——因此我们的调用者可以使用repo.List("abc","def")而无需指定分页。


最后 - 如果没有 LINQ,使用管道和过滤器会变得更加痛苦。您将编写某种基于文本的查询(TSQL、ESQL、HQL)。您显然可以附加字符串,但它不是非常“管道/过滤器”-ish。“Criteria API”稍微好一点——但不如 LINQ 优雅。

于 2009-03-29T07:44:55.573 回答