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

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

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

к сожалению, теперь я получаю InvalidOperationException говоря 'последовательность содержит более одного элемента' при вызове Execute в исходном поставщике запросов. Что это значит? Я просто передаю выражение без какого-либо перевода, поскольку задействованы те же типы, что и упомянутые выше. В translatation бит с IEnumerable в коде, вероятно, неполный, но пока я даже не добираюсь до этого момента.


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

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

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

вот мои вопросы и мысли о это:

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

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

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

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

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

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

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

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

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

Справочная информация: я как бы реализую собственное отсоединение объектов, полученных из 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;
        }
    }
}

2 ответов


хорошо, вот мой лучший ответ на это

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

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

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

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

в чем разница между IQueryable и IEnumerable?

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

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

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

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


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

  • сообщение в блоге предлагает реализовать GetEnumerator() по телефону Execute() поставщиком.

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

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