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

Обновление 2013-08-22:

Посмотрев на "Создание серии поставщиков IQueryable" (спасибо за ссылку!), я получил немного больше. Я обновил код соответствующим образом. Тем не менее, он все еще не работает. Если я правильно понимаю учебник, GetEnumerator вызывается в случае запроса нескольких элементов (например, при вызове ToList() на запросе или любой функции агрегации). Таким образом, вся реализация оболочки GetEnumerator должна состоять в вызове Execute на провайдере и передаче запроса. В другом случае, если запрашивается только один элемент, Execute вызывается напрямую. Вызываемое выражение также отражает, является ли оно для одного или нескольких элементов. Это верно?

К сожалению, теперь я получаю InvalidOperationException, говорящий: "Последовательность содержит более одного элемента" при вызове Execute у поставщика исходного запроса. Что это значит? Я просто передаю выражение без перевода, поскольку такие же типы задействованы, как упоминалось выше. Бит трансляции с IEnumerable в коде, вероятно, неполный, но на данный момент я даже не добираюсь до этой точки.


Я пытаюсь реализовать простую оболочку IQueryable, используя один базовый IQueryable в качестве источника данных, который вызывает функцию перевода для каждого объекта результата.

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

См. ниже, что я получил до сих пор. Для некоторых запросов он работает, но в какой-то момент я получаю исключение StackOverflowException InvalidOperationException. Я предполагаю, что это происходит из-за циклической связи между моим запросом и поставщиком запросов. Но я не понимаю, как правильно это реализовать.

Здесь мои вопросы и мысли об этом:

1. Почему у IQueryable есть Провайдер, который в свою очередь снова возвращает IQueryable? Разве это не требует бесконечной рекурсии?

2. Почему этого недостаточно для реализации IEnumerator? Почему FirstOrDefault, например, не использует перечислитель для получения элемента? Когда я отлаживал приложение, GetEnumerator() не вызывался FirstOrDefault() в моем запросе.

3. Поскольку перечислитель не используется в каждом случае, где правильная точка для вызова функции перевода? Эффективные методы QueryProvider оказались подходящим местом. Но мне еще нужен перевод в Enumerator для некоторых случаев? Обновление: я знаю, что мне нужно предоставить свою собственную реализацию IEnumerable, предоставляющую TranslatingEnumerator, и вернуть это перечисление из моего метода Execute, Чтобы получить перечислитель GetEnumerator вызывает Execute (см. Ниже). Код LINQ, запрашивающий перечислитель, должен убедиться, что выражение действительно возвращает IEnumerable.

Некоторые примечания по коду:

  • Тип источника перевода называется TDatabaseEntity, тип назначения перевода называется TBusinessEntity.

  • Я по сути предоставляю IQueryable, который берет объекты результата, полученные из базового IQueryable, и переводит их в объекты типа

    .
  • Я знаю, что выражение также нужно перевести. Однако я отложил это в сторону, так как в моем фактическом приложении я использую те же типы для TBusinessEntity и TDatabaseEntity, поэтому выражение можно передать прямо.

  • Объекты результата все равно должны быть переведены в другие экземпляры, хотя, несмотря на то, что они одного типа. Обновление: мой уровень перевода работает уже в моем приложении, а также заботится о связанных объектах. Это просто "реализация обертки IQueryable", за которой я застрял.

  • Я боюсь, что вся реализация неверна - TODO в коде - это только мои собственные заметки.

Фон: Я как бы осуществляю собственное разделение сущностей, полученных от DbContext, на моем уровне доступа к данным, чтобы мой бизнес-уровень не общался с реальными объектами - из-за некоторых ошибок с EF и другими требованиями я не могу напрямую использовать отдельные элементы EF.

Спасибо за вашу помощь!

Реализация IQueryable

internal class TranslatingQueryable<TDatabaseEntity, TBusinessEntity> : IQueryable<TBusinessEntity>
{
    private readonly IQueryProvider _provider;
    private readonly IQueryable<TDatabaseEntity> _source;

    internal TranslatingQueryable(TranslatingQueryProvider provider, IQueryable<TDatabaseEntity> source)
    {
        Guard.ThrowIfArgumentNull(provider, "provider");
        Guard.ThrowIfArgumentNull(source, "source");

        _provider = provider;
        _source = source;
    }

    internal TranslatingQueryable(Func<object, object> translateFunc, IQueryable<TDatabaseEntity> databaseQueryable)
        : this(new TranslatingQueryProvider(translateFunc, databaseQueryable.Provider), databaseQueryable)
    {
    }

    public IEnumerator<TBusinessEntity> GetEnumerator()
    {
        return ((IEnumerable<TBusinessEntity>)Provider.Execute(Expression)).GetEnumerator();
    }

    IEnumerator IEnumerable.GetEnumerator()
    {
        return ((IEnumerable)Provider.Execute(Expression)).GetEnumerator();
    }

    public Expression Expression
    {
        get
        {
            return _source.Expression;
        }
    }

    public Type ElementType
    {
        get
        {
            return typeof(TBusinessEntity);
        }
    }

    public IQueryProvider Provider
    {
        get
        {
            return _provider;
        }
    }
}

Реализация IQueryProvider

public class TranslatingQueryProvider : IQueryProvider
{
    private readonly Func<object, object> _translateFunc;
    private readonly IQueryProvider _databaseQueryProvider;

    public TranslatingQueryProvider(Func<object, object> translateFunc, IQueryProvider databaseQueryProvider)
    {
        _translateFunc = translateFunc;
        _databaseQueryProvider = databaseQueryProvider;
    }

    public IQueryable CreateQuery(Expression expression)
    {
        var databaseQueryable = _databaseQueryProvider.CreateQuery<object>(expression);

        return new TranslatingQueryable<object, object>(this, databaseQueryable);
    }

    public IQueryable<TElement> CreateQuery<TElement>(Expression expression)
    {
        var databaseQueryable = _databaseQueryProvider.CreateQuery<object>(expression);

        return new TranslatingQueryable<object, TElement>(this, databaseQueryable);
    }

    public object Execute(Expression expression)
    {
        return Execute<object>(expression);
    }

    public TResult Execute<TResult>(Expression expression)
    {
        // TODO This call throws an InvalidOperationException if an enumeration is requested
        var databaseResult = _databaseQueryProvider.Execute<TResult>(expression);

        var databaseEnumerable = databaseResult as IEnumerable;
        if (databaseEnumerable != null)
        {
            if (typeof(TResult).IsAssignableFrom(typeof(IEnumerable)))
            {
                throw new InvalidOperationException();
            }

            return (TResult)(object)new TranslatingEnumerable(databaseEnumerable, _translateFunc);
        }
        else
        {
            return (TResult)_translateFunc(databaseResult);
        }
    }

    private class TranslatingEnumerable : IEnumerable
    {
        private readonly TranslatingEnumerator _enumerator;

        public TranslatingEnumerable(IEnumerable databaseEnumerable, Func<object, object> translateFunc)
        {
            _enumerator = new TranslatingEnumerator(translateFunc, databaseEnumerable.GetEnumerator());
        }

        public IEnumerator GetEnumerator()
        {
            return _enumerator;
        }
    }
}

Реализация IEnumerator

internal class TranslatingEnumerator : IEnumerator
{
    private readonly Func<object, object> _translateFunc;
    private readonly IEnumerator _databaseEnumerator;

    internal TranslatingEnumerator(Func<object, object> translateFunc, IEnumerator databaseEnumerator)
    {
        _translateFunc = translateFunc;
        _databaseEnumerator = databaseEnumerator;
    }

    public bool MoveNext()
    {
        return _databaseEnumerator.MoveNext();
    }

    public void Reset()
    {
        _databaseEnumerator.Reset();
    }

    public object Current
    {
        get
        {
            return _translateFunc(_databaseEnumerator.Current);
        }
    }

    object IEnumerator.Current
    {
        get
        {
            return Current;
        }
    }
}

Ответ 1

К настоящему моменту я узнал, почему я получил исключение каждый раз, когда запрос был перечислен: Инфраструктура IQueryable для платформы Entity Framework реализована совсем иначе, чем шаблон, описанный в разделе "Создание серии поставщиков IQueryable", стр. 1.

  • В блоге предлагается реализовать GetEnumerator() , вызывая Execute() у поставщика.

  • Напротив, в инфраструктуре EF метод ObjectQueryProvider Execute() принимает только выражения, которые возвращают единственный объект результата, но не перечислимый набор объектов результатов (это даже документировано в исходном коде). Соответственно, метод ObjectQuery GetEnumerator() не вызывает Execute(), а другой метод получает результат прямо из базы данных.

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

Ответ 2

Хорошо, я могу ответить на этот вопрос

Почему у IQueryable есть Провайдер, который, в свою очередь, возвращает     IQueryable снова? Разве это не требует бесконечной рекурсии?  Вы хотите вернуть IQueryable для этого экземпляра

SomeEnumerable.Where(x=>x.Field == something).Select(x=>x.SomeOtherField)   Подумайте, JQuery, если вы знакомы с цепочкой

Почему этого недостаточно для реализации IEnumerator? Почему FirstOrDefault например, не использовать перечислитель для получения элемента? Когда я отладка приложения GetEnumerator() не вызывалась FirstOrDefault() в моем запросе.

Так как IQueryable имеет 2 специальных запроса поставщика запросов и выражение запроса:

В чем разница между IQueryable <T> и IEnumerable <T> ?

Поскольку перечислитель не используется в каждом случае, где верно указать вызов функции перевода? Выполнить-методы QueryProvider, казалось, был правильным местом. Но мне все еще нужно перевод вызова в Enumerator для некоторых случаев?

Выполнение - это правильное место, вам нужно проанализировать дерево выражений, потому что поставщик не знает, что делать, когда вы выполняете.

Я также добавил эту ссылку, которая очень помогла мне, когда я реализовал свой собственный поставщик запросов http://blogs.msdn.com/b/mattwar/archive/2008/11/18/linq-links.aspx

То, что вы могли бы избежать, использует тот же способ, которым автор в этом сообщении принимает dbReader и преобразует его в реальные объекты, но вместо чтения вашего DBEntity и преобразования его в BusinessEntity, но я не уверен, что это возможно. Поскольку каждый раз, когда вы добавляете предложение linq (Select, Where...), он создает новый запрос этого типа возврата, поэтому, если у вас есть сущность "сущностей" DBEntity и вы сделали сущности. Выберите (x = > x.someField) и некоторое поле говорит о типе int it now IQueryable, теперь ваша модель не работает, потому что она ожидает int и получает DBEntitity