Выполнение части запроса IQueryable и передача остальной части Linq for Objects
-
22-08-2019 - |
Вопрос
У меня есть поставщик Linq, который успешно получает данные из выбранного мной источника данных, но теперь, когда у меня есть отфильтрованный набор результатов, я хотел бы позволить Linq to Objects обрабатывать остальную часть дерева выражений (для таких вещей, как соединения, проекция и т. д.)
Я думал, что могу просто заменить константу выражения, содержащую мой IQueryProvider, наборами результатов IEnumerable через ExpressionVisitor, а затем вернуть это новое выражение.Также верните поставщика IEnumerable из моего IQueryable... но, похоже, это не работает :-(
Есть идеи?
Редактировать:Несколько хороших ответов здесь, но учитывая форму...
var qry = from c in MyProv.Table<Customer>()
Join o in MyProv.Table<Order>() on c.OrderID equals o.ID
select new
{
CustID = c.ID,
OrderID = o.ID
}
В моем провайдере я могу легко получить два набора результатов от «Клиентов» и «Заказы». Если бы данные были из источника SQL, я бы просто создал и передал синтаксис SQL Join, но в этом случае данные не из источника SQL, поэтому я нужно выполнить объединение в коде... но, как я уже сказал, у меня есть два набора результатов, и Linq to Objects может выполнить объединение... (а позже и проекцию), было бы очень неплохо просто заменить константы выражений MyProv.Table<Customer>
и MyProv.Table<Order>
с List<Customer>
и List<Order>
и пусть List<>
провайдер обрабатывает выражение... возможно ли это?как?
Решение 2
То, что мне нужно было, это заменить константу Queryable<> в дереве выражений конкретным набором результатов IEnumerable (или IQueryable через .AsQueryable())... это сложная тема, которая, вероятно, имеет какой-либо смысл только для Linq Авторы провайдеров, которые глубоко разбираются в посетителях дерева выражений и т. д.
В пошаговом руководстве msdn я нашел фрагмент, который делает что-то вроде того, что мне нужно, это дает мне путь вперед...
using System;
using System.Linq;
using System.Linq.Expressions;
namespace LinqToTerraServerProvider
{
internal class ExpressionTreeModifier : ExpressionVisitor
{
private IQueryable<Place> queryablePlaces;
internal ExpressionTreeModifier(IQueryable<Place> places)
{
this.queryablePlaces = places;
}
internal Expression CopyAndModify(Expression expression)
{
return this.Visit(expression);
}
protected override Expression VisitConstant(ConstantExpression c)
{
// Replace the constant QueryableTerraServerData arg with the queryable Place collection.
if (c.Type == typeof(QueryableTerraServerData<Place>))
return Expression.Constant(this.queryablePlaces);
else
return c;
}
}
}
Другие советы
Оба предыдущих ответа работают, но он читается лучше, если вы используете AsEnumerable() для приведения IQueryable к IEnumerable:
// Using Bob's code...
var result = datacontext.Table
.Where(x => x.Prop == val)
.OrderBy(x => x.Prop2)
.AsEnumerable() // <---- anything after this is done by LINQ to Objects
.Select(x => new { CoolProperty = x.Prop, OtherProperty = x.Prop2 });
РЕДАКТИРОВАТЬ:
// ... or MichaelGG's
var res = dc.Foos
.Where(x => x.Bla > 0) // uses IQueryable provider
.AsEnumerable()
.Where(y => y.Snag > 0); // IEnumerable, uses LINQ to Objects
Если вы реализовали шаблон репозитория, вы могли бы просто предоставить ответ IQueryable и абстрагировать таблицу.
Пример:
var qry = from c in MyProv.Repository<Customer>()
Join o in MyProv.Repository<Order>() on c.OrderID equals o.ID
select new
{
CustID = c.ID,
OrderID = o.ID
}
а затем просто создайте свой провайдер для моделирования шаблона IQueryable в вашем методе репозитория, как Эта статья иллюстрирует.
Таким образом, вы можете написать всевозможные провайдеры и использовать их для всего, что вам нужно.Вы можете иметь поставщика LINQ 2 SQL или написать поставщика в памяти для своих модульных тестов.
Метод Repository для поставщика SQL LINQ 2 будет выглядеть примерно так:
public IQueryable<T> Repository<T>() where T : class
{
ITable table = _context.GetTable(typeof(T));
return table.Cast<T>();
}
Если я не ошибаюсь, я обычно просто добавляю .ToArray() в цепочку методов linq в тот момент, когда я хочу, чтобы провайдер linq выполнялся.
Например (вспомните Linq to SQL)
var result = datacontext.Table
.Where(x => x.Prop == val)
.OrderBy(x => x.Prop2)
.ToArray()
.Select(x => new {CoolProperty = x.Prop, OtherProperty = x.Prop2});
Таким образом, через OrderBy() преобразуется в SQL, но Select() — это LINQ to Objects.
Ответ Роба хорош, но требует полного перечисления.Вы можете выполнить преобразование, чтобы сохранить синтаксис метода расширения и ленивую оценку:
var res = ((IEnumerable<Foo>)dc.Foos
.Where(x => x.Bla > 0)) // IQueryable
.Where(y => y.Snag > 0) // IEnumerable