Обновление 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;
}
}
}