Как сохранить отложенное выполнение LINQ? - PullRequest
8 голосов
/ 20 августа 2009

Предположим, у меня есть выражение IQueryable<T>, в которое я хотел бы инкапсулировать определение, сохранить его и повторно использовать или встроить в более крупный запрос позже. Например:

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

Теперь я считаю, что могу просто сохранить этот объект myQuery и использовать его, как я описал. Но в некоторых вещах я не уверен:

  1. Как лучше всего его параметризовать? Сначала я определил это в методе, а затем возвратил IQueryable<T> в качестве результата метода. Таким образом, я могу определить blah и bar как аргументы метода, и я предполагаю, что он просто создает новый IQueryable<T> каждый раз. Это лучший способ инкапсулировать логику IQueryable<T>? Есть ли другие способы?

  2. Что если мой запрос будет преобразован в скаляр, а не IQueryable? Например, что если я хочу, чтобы этот запрос был точно таким, как показано, но добавьте .Any(), чтобы просто сообщить мне если были какие-либо результаты, которые соответствовали? Если я добавлю (...).Any(), то результат будет bool и сразу будет выполнен, верно? Есть ли способ использовать эти Queryable операторы (Any, SindleOrDefault и т. Д.) Без немедленного выполнения? Как LINQ-to-SQL справляется с этим?

Редактировать: Часть 2 на самом деле больше о попытках понять, в чем различия между IQueryable<T>.Where(Expression<Func<T, bool>>) и IQueryable<T>.Any(Expression<Func<T, bool>>). Кажется, что последний не так гибок при создании больших запросов, где выполнение должно быть отложено. Where() может быть добавлено, а затем могут быть добавлены другие конструкции и, наконец, выполнены. Поскольку Any() возвращает скалярное значение, звучит так, как будто оно будет немедленно выполнено до того, как будет построен остальной запрос.

Ответы [ 4 ]

5 голосов
/ 20 августа 2009
  1. Вы должны быть очень осторожны при передаче IQueryables, когда используете DataContext, потому что как только контекст получен, вы больше не сможете выполнять этот IQueryable. Если вы не используете контекст, тогда вы можете быть в порядке, но помните об этом.

  2. .Any () и .FirstOrDefault () не отложены. Когда вы вызываете их, они будут вызывать исполнение. Тем не менее, это может не делать то, что вы думаете, что делает. Например, в LINQ to SQL, если вы выполняете .Any () для IQueryable, он действует как 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(); 
}
2 голосов
/ 20 августа 2009

Гораздо лучший вариант, чем кэширование объектов IQueryable, - это кэширование деревьев выражений. Все объекты IQueryable имеют свойство Expression (я полагаю), которое представляет текущее дерево выражений для этого запроса.

В более поздний момент времени вы можете заново создать запрос, вызвав queryable.Provider.CreateQuery (expression), или напрямую к тому, кем является поставщик (в вашем случае контекст данных Linq2Sql).

Параметризация этих деревьев выражений, однако, немного сложнее, поскольку они используют ConstantExpressions для создания значения. Для параметризации этих запросов вам потребуется перестраивать запрос каждый раз, когда вам нужны другие параметры.

1 голос
/ 20 августа 2009

Any() используется таким образом, отсрочено.

var q = dc.Customers.Where(c => c.Orders.Any());

Any() используемый таким образом не откладывается, но все равно переводится в SQL (вся таблица клиентов не загружается в память).

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

Недостатком является то, что этот метод не позволяет выполнять подзапросы.

0 голосов
/ 17 сентября 2009

Создайте частичное применение вашего запроса внутри выражения

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