如何维护LINQ延迟执行?

时间:2009-08-20 18:52:43

标签: c# linq iqueryable deferred-execution

假设我有一个IQueryable<T>表达式,我想要封装它的定义,存储并重用它,或者稍后将其嵌入到更大的查询中。例如:

IQueryable<Foo> myQuery =
    from foo in blah.Foos
    where foo.Bar == bar
    select foo;

现在我相信我可以保留myQuery对象并像我描述的那样使用它。但有些事情我不确定:

  1. 如何最好地参数化?最初我在方法中定义了这个,然后返回IQueryable<T>作为方法的结果。这样我就可以将blahbar定义为方法参数,我想每次只创建一个新的IQueryable<T>。这是封装IQueryable<T>逻辑的最佳方法吗?还有其他方法吗?

  2. 如果我的查询解析为标量而不是IQueryable怎么办?例如,如果我希望此查询完全如图所示但附加.Any(),该怎么办?只是让我知道是否有任何匹配的结果?如果我添加(...).Any(),那么结果为bool并立即执行,对吧?有没有办法在不立即执行的情况下使用这些Queryable运算符(AnySindleOrDefault等)? LINQ-to-SQL如何处理这个问题?

  3. 修改:第2部分更多的是尝试了解IQueryable<T>.Where(Expression<Func<T, bool>>)IQueryable<T>.Any(Expression<Func<T, bool>>)之间的限制差异。在创建要延迟执行的大型查询时,似乎后者并不灵活。可以附加Where(),然后可以添加其他构造,然后最终执行。由于Any()返回标量值,因此它可能会在构建其余查询之前立即执行。

4 个答案:

答案 0 :(得分:5)

  1. 当你使用DataContext时,你必须非常小心地传递IQueryables,因为一旦上下文被处理掉,你就不能再在那个IQueryable上执行了。如果你没有使用上下文,那么你可能没问题,但要注意这一点。

  2. .Any()和.FirstOrDefault()延迟。当你打电话给他们时,导致执行。但是,这可能不符合您的想法。例如,在LINQ to SQL中,如果在IQueryable上执行.Any(),它基本上充当IF EXISTS(SQL HERE)。

  3. 如果您愿意,可以像这样链接IQueryable:

    var firstQuery = from f in context.Foos
                        where f.Bar == bar
                        select f;
    
    var secondQuery = from f in firstQuery
                        where f.Bar == anotherBar
                        orderby f.SomeDate
                        select f;
    
    if (secondQuery.Any())  //immediately executes IF EXISTS( second query in SQL )
    {
        //causes execution on second query 
        //and allows you to enumerate through the results
        foreach (var foo in secondQuery)  
        {
            //do something
        }
    
        //or
    
        //immediately executes second query in SQL with a TOP 1 
        //or something like that
        var foo = secondQuery.FirstOrDefault(); 
    }
    

答案 1 :(得分:2)

比缓存IQueryable对象更好的选择是缓存表达式树。所有IQueryable对象都有一个名为Expression(我相信)的属性,它表示该查询的当前表达式树。

稍后,您可以通过调用queryable.Provider.CreateQuery(表达式)重新创建查询,或者直接在提供者身上重新创建查询(在您的情况下是Linq2Sql数据上下文)。

然而,参数化这些表达式树稍微有些困难,因为它们使用ConstantExpressions来构建值。为了参数化这些查询,您每次需要不同的参数时都必须重建查询。

答案 2 :(得分:1)

使用这种方式的

Any()是延迟的。

var q = dc.Customers.Where(c => c.Orders.Any());
使用这种方式的

Any()不是延迟的,但仍然被转换为SQL(整个customers表没有加载到内存中)。

bool result = dc.Customers.Any();

如果你想要一个延迟的Any(),那就这样做:

public static class QueryableExtensions
{
  public static Func<bool> DeferredAny<T>(this IQueryable<T> source)
  {
    return () => source.Any();
  }
}

这就是这样称呼的:

Func<bool> f = dc.Customers.DeferredAny();
bool result = f();

缺点是这种技术不允许进行子查询。

答案 3 :(得分:0)

在表达式

中创建查询的部分应用程序
Func[Bar,IQueryable[Blah],IQueryable[Foo]] queryMaker = 
(criteria, queryable) => from foo in queryable.Foos
        where foo.Bar == criteria
        select foo;

然后你可以用它来......

IQueryable[Blah] blah = context.Blah;
Bar someCriteria = new Bar();
IQueryable[Foo] someFoosQuery = queryMaker(blah, someCriteria);

如果要使查询更具可移植性/可重用性,可以将查询封装在类中。

public class FooBarQuery
{
  public Bar Criteria { get; set; }

  public IQueryable[Foo] GetQuery( IQueryable[Blah] queryable )
  {
     return from foo in queryable.Foos
        where foo.Bar == Criteria
        select foo;
  } 
}