Update 2013-08-22:
Looking at the “Creating an IQueryable Provider Series” (thanks for the link!), I got a bit more. I updated the code accordingly. However, it still does not work. If I understand the tutorial correctly, GetEnumerator is called when several elements are requested (for example, when ToList() called on a request or any aggregation function). Thus, the entire implementation of the GetEnumerator shell should consist of calling Execute on the provider and passing the request. Otherwise, if only one item is requested, Execute is called directly. The expression being invoked also reflects whether it is for one or more elements. It's right?
Unfortunately, now I get an InvalidOperationException saying: "The sequence contains more than one element" when calling Execute from the source request provider. What does it mean? I just pass the expression without translation, since the same types are involved, as mentioned above. The translation bit with IEnumerable in the code is probably incomplete, but at the moment I'm not even getting to this point.
I am trying to implement a simple IQueryable shell using one basic IQueryable as a data source that calls a translation function for each result object.
I thought it would be relatively trivial, because the only thing the shell should do is translate. However, I could not get my implementation to work.
See below what I have received so far. It works for some queries, but at some point I get a StackOverflowException InvalidOperationException. I assume this is due to the cyclical relationship between my request and the request provider. But I do not understand how to implement this correctly.
Here are my questions and thoughts about this:
1. Why does IQueryable have a Provider that in turn returns IQueryable again? Doesn't that require infinite recursion?
2. Why is this not enough to implement IEnumerator? Why does FirstOrDefault, for example, not use an enumerator to get the item? When I debugged the application, GetEnumerator () was not called FirstOrDefault () in my request.
3. Since the enumerator is not used in each case, where is the correct point for calling the translation function? Effective QueryProvider methods have proven to be the right place. But do I still need a translation in Enumerator for some cases? Update: I know that I need to provide my own IEnumerable implementation that provides the TranslatingEnumerator and return this enumeration from my Execute method. To get the GetEnumerator enumerator, calls Execute (see below). The LINQ code requesting the enumerator must ensure that the expression does return IEnumerable .
Some notes on the code:
The type of translation source is called TDatabaseEntity, the type of translation destination is called TBusinessEntity.
I essentially provide an IQueryable that takes the result objects derived from the underlying IQueryable and translates them into objects of type
.I know that the expression also needs to be translated. However, I put this aside, since in my actual application I use the same types for TBusinessEntity and TDatabaseEntity, so the expression can be passed directly.
Result objects must still be transferred to other instances, although, despite the fact that they are of the same type. Update: my translation level already works in my application, and also takes care of related objects. This is just the "implementation of the IQueryable wrapper" I am stuck with.
I am afraid that the whole implementation is wrong - TODO in the code is just my own notes.
Background: It’s as if I am performing my own separation of entities received from DbContext at my data access level so that my business layer does not communicate with real objects - due to some errors with EF and other requirements I cannot directly use separate EF elements .
Thank you for your help!
IQueryable implementation
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 Implementation
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) {
IEnumerator implementation
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; } } }