LINQ to Entities/LINQ to SQL: переход от сервера (запрашиваемый) к клиенту (перечислимый) в середине понимания запроса?

Во многих случаях я хочу сделать некоторую фильтрацию (а иногда и проекцию) на стороне сервера, а затем переключиться на клиентскую сторону для операций, которые поставщик LINQ не поддерживает.

Наивный подход (в основном это то, что я сейчас делаю) - это просто разбить его на несколько запросов, аналогично:

var fromServer = from t in context.Table
                 where t.Col1 = 123
                 where t.Col2 = "blah"
                 select t;

var clientSide = from t in fromServer.AsEnumerable()
                 where t.Col3.Split('/').Last() == "whatever"
                 select t.Col4;

Однако, есть много раз, когда это больше кода/проблемы, чем это действительно стоит. Я бы очень хотел сделать "переключатель на стороне клиента" посередине. Я пробовал различные методы использования продолжения запроса, но после выполнения "select t to foo" в конце первого запроса foo по-прежнему является отдельным элементом, а не сборкой, поэтому я не могу AsEnumerable() it.

Моя цель - написать что-то большее:

var results = from t in context.Table
              where t.Col1 = 123
              where t.Col2 = "blah"
              // Magic happens here to switch to the client side
              where t.Col3.Split('/').Last() == "whatever"
              select t.Col4;

Ответ 1

Хорошо, во-первых, вы абсолютно не должны использовать код здесь. Это было написано обученными шутниками-хомяками, которые были обучены не бросать, когда занимались этим кодом такого рода.

Вы должны выбрать один из вариантов:

  • Используйте временную переменную (если вы можете статически вводить эту переменную как IEnumerable<T>, тогда вам не нужен вызов AsEnumerable - это не сработает, если у вас есть анонимный тип в качестве элемента тип курса)
  • Используйте скобки для вызова AsEnumerable
  • Используйте синтаксис "плавного" или "точечного нотации", чтобы сделать вызов AsEnumerable подходящим.

Однако вы можете сделать немного магии, используя способ выражения запросов. Вам просто нужно сделать один из стандартных операторов запросов с представлением в выражениях запроса, имеющих другой перевод. Самый простой вариант здесь - "Где". Просто напишите свой собственный метод расширения с помощью IQueryable<T> и Func<T, SomeType>, где SomeType не bool, и вы ушли. Вот пример, первый из самого взлома, а затем образец использования его...

using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;

public static class QueryHacks
{
    public static readonly HackToken TransferToClient = HackToken.Instance;

    public static IEnumerable<T> Where<T>(
        this IQueryable<T> source,
        Func<T, HackToken> ignored)
    {
        // Just like AsEnumerable... we're just changing the compile-time
        // type, effectively.
        return source;
    }

    // This class only really exists to make sure we don't *accidentally* use
    // the hack above.
    public class HackToken
    {
        internal static readonly HackToken Instance = new HackToken();
        private HackToken() {}
    }
}

public class Test
{
    static void Main()
    {
        // Pretend this is really a db context or whatever
        IQueryable<string> source = new string[0].AsQueryable();

        var query = from x in source
                    where x.StartsWith("Foo") // Queryable.Where
                    where QueryHacks.TransferToClient
                    where x.GetHashCode() == 5 // Enumerable.Where
                    select x.Length;
    }
}

Ответ 2

Конечно, если вы использовали стандартный синтаксис метода, это не проблема:

var results = context.Table
              .Where(t => t.Col1 == 123)
              .Where(t => t.Col2 == "blah")
              .AsEnumerable()
              .Where(t => t.Col3.Split('/').Last() == "whatever")
              .Select(t => t.Col4);

Если вы настаиваете на использовании синтаксиса запроса, вы не будете использовать некоторые круглые скобки, но в противном случае вы, безусловно, можете сделать то же самое:

var results = from t in (
                  from t in context.Table
                  where t.Col1 == 123
                  where t.Col2 == "blah"
                  select t
              ).AsEnumerable()
              where t.Col3.Split('/').Last() == "whatever"
              select t.Col4;

Повторное использование имени переменной t не вызывает никаких проблем; Я протестировал его.

Ответ 3

Что значит сервер/клиент?

Я предполагаю, что вы имеете в виду, что вы получаете некоторую коллекцию с сервера, а затем выполняете дополнительную фильтрацию, которая недоступна в LINQ-to-entity. Просто попробуйте следующее:

var items =
    context.Table.Where(t => t.Col1 = 123 && t.Col2 = "blah").ToList()
    .Where(t => t.Col3.Split('/').Last() == "whatever")
    .Select(t => t.Col4).ToList();

Ответ 4

Вы хотите использовать более абстрактный синтаксис, чтобы получить более тонкий контроль над сервером и локальным исполнением? Извините - это не работает.

Подумайте о проблеме области в понимании запроса.

from c in context.Customers
from o in c.Orders
from d in o.Details
asLocal
where //c, o and d are all in scope, so they all had to be hydrated locally??