Выполнение части запроса IQueryable и передача остальной части Linq для объектов

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

Я думал, что могу просто заменить константу выражения, содержащую мой 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
          }

в моем провайдере я могу легко вернуть 2 результирующих набора от клиентов и заказов, если данные были из источника SQL, я бы просто построил и передал синтаксис SQL Join, но в этом случае данные не из источника SQL, поэтому мне нужно сделать соединение в коде...но, как я уже сказал, У меня есть результат 2 наборы и Linq для объектов могут выполнять соединение...(и позже проекция) было бы очень хорошо просто заменить константы выражения MyProv.Table<Customer> и MyProv.Table<Order> С List<Customer> и List<Order> и пусть а List<> провайдер обрабатывает выражение...это возможно? как?

5 ответов


оба предыдущих ответа работают, но он читается лучше, если вы используете 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 });

EDIT:

// ... 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

то, что я искал, заменяло константу Queryable в дереве выражений конкретным IEnumerable (или IQueryable via.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;
        }
    }
}

Если вы реализовали шаблон репозитория, вам может сойти с рук, просто предоставив IQueryable back и абстрактную таблицу.

пример:

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 в вашем методе репозитория так же, как в этой статье иллюстрирует.

таким образом, вы можете написать все виды поставщиков, чтобы использовать все, что вам нужно. У вас может быть поставщик SQL LINQ 2 или поставщик in memory для модульные тесты.

метод репозитория для поставщика 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