如何维护LINQ延迟执行?

9

假设我有一个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 如何处理这个问题?

编辑:第二部分实际上更多地是试图了解 IQueryable<T>.Where(Expression<Func<T, bool>>)IQueryable<T>.Any(Expression<Func<T, bool>>) 之间的限制差异。似乎后者在创建需要延迟执行的更大查询时不太灵活。可以添加 Where(),然后可以稍后添加其他结构,最后再执行。由于 Any() 返回一个标量值,因此它听起来会立即执行,而无法构建查询的其余部分。

4个回答

5
  1. 使用DataContext时,传递IQueryable时要非常小心,因为一旦上下文被释放,就无法再在该IQueryable上执行操作。如果您不使用上下文,则可能还好,但请注意。

  2. .Any()和.FirstOrDefault()不是延迟的。当您调用它们时,它们将导致执行发生。但是,这可能不会做您认为它会做的事情。例如,在LINQ to SQL中,如果在IQueryable上执行.Any(),它基本上是IF EXISTS(SQL HERE)。

如果您想这样链接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中,拥有一个每次本质上构造新的IQueryable的方法听起来是一件好事,因为这样我就不会遇到处理问题的麻烦。在#2中,我对LINQ-to-SQL如何转换Any运算符感到困惑,但我不能推迟。如果我在较大的查询中使用Any运算符,它是否也会立即执行,还是它是较大查询执行的一部分? - mckamey
好的,我觉得我快完成了。如果我将 .Any() 嵌入到 where 子句中,那么它不会在循环中执行,对吗?它会编译为适当的 SQL 表达式并发送下去。因此,实际上,不是 .Any() 阻止了延迟执行,而是它的使用方式。基本上,如果整个查询的结果是标量,则编译器会认为您现在需要结果,而不是继续构建一个 IQueryable<T> - mckamey
1
@McKAMEY 正确,只要在不可延迟的上下文中使用 .Any(),它就会执行。在 .Where() 的情况下,它正在寻找一个表达式,这是可延迟的,所以没问题。在 var 或 foreach 循环的情况下,它们会导致执行,因为它们是不可延迟的。 - Joseph

2

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();

这种技术的缺点是,它不支持子查询。

当你说“deferred”时,听起来像是我可以定义一个lambda表达式(或委托),但不立即执行它。我认为LINQ的“延迟执行”概念有微妙之处,它允许操作成为更大表达树的一部分,这可以由提供程序按其意愿进行解释。我的问题更多地是想了解IQueryable<T>.Where(Expression<Func<T, bool>>)IQueryable<T>.Any(Expression<Func<T, bool>>)之间的限制差异,因为似乎后者不太灵活。 - mckamey
1
这种不灵活性源于返回类型的差异。名为'bool'的类型不能有延迟行为。 - Amy B
我认为这个建议在多线程环境下不安全(请考虑DataContext)。 - Matt Kocaj
@cottsak - 所有扩展方法(包括用于linq的方法)都是静态的,例如http://msdn.microsoft.com/en-us/library/bb535040.aspx。只要没有静态状态(如静态属性或字段),静态方法就是线程安全的。 - Amy B

2
比缓存IQueryable对象更好的选择是缓存表达式树。所有IQueryable对象都有一个名为Expression的属性(我相信),它表示该查询的当前表达式树。
稍后,您可以通过调用queryable.Provider.CreateQuery(expression)或直接在提供程序上(在您的情况下是Linq2Sql数据上下文)来重新创建查询。
然而,将这些表达式树参数化略微困难,因为它们使用ConstantExpressions来构建值。为了参数化这些查询,每次想要不同参数时都必须重新构建查询。

1
我认为参数化(或更重要地,封装单个逻辑单元)才是真正的目标,而不是缓存。考虑到C#编译器已经将其转换,我认为运行时等效物并没有太大用处/性能好处(这就是缓存所暗示的)。 - mckamey
你能解释一下为什么这个选项更好吗?据我所知,您只是将“Expression”解包以后再重新包装:为什么不保持包装器不变呢? - PPC

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;
  } 
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接