LINQ to Entities / LINQ to SQL: переключение с сервера (с запросом) на клиент (с перечислением) в середине понимания запроса? - PullRequest
10 голосов
/ 28 января 2011

Во многих случаях я хочу выполнить некоторую фильтрацию (а иногда и проекцию) на стороне сервера, а затем переключиться на сторону клиента для операций, которые поставщик 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 into foo' в конце первого запроса foo остается отдельным элементом, а не коллекцией, поэтому я не могу AsEnumerable () .

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

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;

Ответы [ 4 ]

20 голосов
/ 28 января 2011

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

Вам следует абсолютно выбрать один из вариантов, о которых вы знаете:

  • Используйте «временную» переменную (если вы можете статически напечатать эту переменную как 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;
    }
}
8 голосов
/ 28 января 2011

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

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 не вызывает никаких проблем;Я проверял это.

0 голосов
/ 28 января 2011

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

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

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??
0 голосов
/ 28 января 2011

Что вы подразумеваете под стороной сервер / клиент?

Полагаю, вы имеете в виду, что вы получаете некоторую коллекцию с сервера и затем выполняете дополнительную фильтрацию, которая недоступна в 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();
...