Выполнение части запроса IQueryable и передача остальной части Linq for Objects

StackOverflow https://stackoverflow.com/questions/695678

Вопрос

У меня есть поставщик 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
Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top