且构网

分享程序员开发的那些事...
且构网 - 分享程序员编程开发的那些事

如何使用LinqToSQL / Entity Framework / NHibernate实现管道和过滤器模式?

更新时间:2023-02-13 20:40:37

最终,LINQ on IEnumerable< T> 是管道和过滤器的实现。 IEnumerable< T> 是一种流式传输API,这意味着数据在您要求的时候(通过迭代器块)懒惰地返回,而不是加载所有内容一次,并返回一大笔记录。

Ultimately, LINQ on IEnumerable<T> is a pipes and filters implementation. IEnumerable<T> is a streaming API - meaning that data is lazily returns as you ask for it (via iterator blocks), rather than loading everything at once, and returning a big buffer of records.

这意味着您的查询:

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和我自己在推送LINQ 用于在这种情况下进行汇总而不缓冲。

as you enumerate over this, it will consume the data lazily. You can see this graphically with Jon Skeet's Visual LINQ. The only things that break the pipe are things that force buffering; OrderBy, GroupBy, etc. For high volume work, Jon and myself worked on Push LINQ for doing aggregates without buffering in such scenarios.

IQueryable< T> (大多数ORM工具暴露 - LINQ到SQL,实体框架,LINQ到NHibernate)是一个略有不同的野兽;因为数据库引擎要做大部分的工作,所以很可能已经完成了大部分步骤 - 剩下的就是使用$ code> IDataReader 和项目这个对象/值 - 但是通常仍然是一个管道( IQueryable< T> 实现 IEnumerable< T> )除非你调用 .ToArray() .ToList()等。

IQueryable<T> (exposed by most ORM tools - LINQ-to-SQL, Entity Framework, LINQ-to-NHibernate) is a slightly different beast; because the database engine is going to do most of the heavy lifting, the chances are that most of the steps are already done - all that is left is to consume an IDataReader and project this to objects/values - but that is still typically a pipe (IQueryable<T> implements IEnumerable<T>) unless you call .ToArray(), .ToList() etc.

关于在企业中的使用... 我的观点使用 IQueryable 可以在存储库中编写可组合的查询是很好的,但是他们不应该将存放在存储库中 - as这将使存储库的内部操作受调用者的影响,因此您将无法正确单元测试/配置文件/优化等。我已经在存储库中执行了巧妙的操作,但返回列表/数组。这也意味着我的存储库保持不了解实现。

With regard to use in enterprise... my view is that it is fine to use IQueryable<T> to write composable queries inside the repository, but they shouldn't leave the repository - as that would make the internal operation of the repository subject to the caller, so you would be unable to properly unit test / profile / optimize / etc. I've taken to doing clever things in the repository, but return lists/arrays. This also means my repository stays unaware of the implementation.

这是一个耻辱 - 作为返回的诱惑 IQueryable< T& code>从存储库方法是相当大的;例如,这将允许调用者添加分页/过滤器/ etc - 但请记住,他们还没有实际使用数据。这使资源管理变得痛苦。另外,在MVC等你需要确保控制器调用 .ToList()或类似的,所以它不是视图控制数据访问(否则,您再也不能单独测试控制器)。

This is a shame - as the temptation to "return" IQueryable<T> from a repository method is quite large; for example, this would allow the caller to add paging/filters/etc - but remember that they haven't actually consumed the data yet. This makes resource management a pain. Also, in MVC etc you'd need to ensure that the controller calls .ToList() or similar, so that it isn't the view that is controlling data access (otherwise, again, you can't unit test the controller properly).

安全(IMO)使用DAL中的过滤器将是这样的:

A safe (IMO) use of filters in the DAL would be things like:

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> ,调用者可能会执行以下操作:

Here we've added filters on-the-fly, but nothing happens until we call ToArray. At this point, the data is obtained and returned (disposing the data-context in the process). This can be fully unit tested. If we did something similar but just returned IQueryable<T>, the caller might do something like:

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

突然之间我们的DAL开始失败(不能转换 SomeUnmappedFunction 到TSQL等)。您仍然可以在存储库中进行一些有趣的事情。

And all of a sudden our DAL starts failing (cannot translate SomeUnmappedFunction to TSQL, etc). You can still do a lot of interesting things in the repository, though.

这里唯一的难点在于它可能会推动您拥有一些重载来支持不同的呼叫模式(带/不带分页等)。直到可选/命名参数到达,我找到***的这里的答案是在界面上使用扩展方法;这样,我只需要一个具体的存储库实现:

The only pain point here is that it might push you to have a few overloads to support different calling patterns (with/without paging, etc). Until optional/named parameters arrives, I find the best answer here is to use extension methods on the interface; that way, I only need one concrete repository implementation:

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)而不必指定分页。

Now we have virtual overloads (as extension methods) on ICustomerRepository - so our caller can use repo.List("abc","def") without having to specify the paging.

最后 - 没有LINQ,使用管道和过滤器变得更加痛苦。你会写一些基于文本的查询(TSQL,ESQL,HQL)。你可以显然附加字符串,但它不是很管道/过滤器。 标准API有点好 - 但不如LINQ那么优雅。

Finally - without LINQ, using pipes and filters becomes a lot more painful. You'll be writing some kind of text based query (TSQL, ESQL, HQL). You can obviously append strings, but it isn't very "pipe/filter"-ish. The "Criteria API" is a bit better - but not as elegant as LINQ.