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??