Embed an IQueryable Shell to Convert Result Objects

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) { // 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 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; } } } 
+16
c # linq entity-framework iqueryable
source share
2 answers

So far, I have found out why I received an exception every time a query was listed: The IQueryable infrastructure for the Entity Framework is implemented in a completely different way than the template described in "Creating the IQueryable Provider Series", page 1.

  • The blog post suggests implementing GetEnumerator() by calling Execute() on the provider .

  • In contrast, in the EF framework, the ObjectQueryProvider Execute() method accepts only expressions that return a single result object, but not an enumerable set of result objects (this is even documented in the source code). Accordingly, the ObjectQuery GetEnumerator() method does not call Execute() , and the other method gets the result directly from the database .

Thus, any IQueryable translation implementation that uses a basic database query to retrieve objects must use the same template - the GetEnumerator() translation method simply calls GetEnumerator() in the base database query and enters it into the new TranslatingEnumerator .

+2
source share

Ok i can answer this question

Why does IQueryable have a Provider that in turn returns IQueryable again? Doesn't that require infinite recursion? You want to return IQueryable for this instance

SomeEnumerable.Where(x=>x.Field == something).Select(x=>x.SomeOtherField) Think JQuery if you are familiar with the chain

Why is this not enough to implement IEnumerator? Why does FirstOrDefault for example not use an enumerator to get the item? When I debug the application, GetEnumerator () was not called FirstOrDefault () in my request.

Since IQueryable has 2 special query provider queries and a query expression:

What is the difference between IQueryable <T> and IEnumerable <T>?

Since the enumerator is not used in each case, where is the call to the translation function correctly indicated? The run-methods QueryProvider seemed to be the right place. But do I still need to transfer the call to Enumerator for some cases?

Execution is the right place, you need to parse the expression tree because the provider does not know what to do when you execute.

I also added this link, which helped me a lot when I implemented my own request provider http://blogs.msdn.com/b/mattwar/archive/2008/11/18/linq-links.aspx

What you could avoid is using the same way that the author in this post takes dbReader and converts it to real objects, but instead of reading your DBEntity and converting it to BusinessEntity, but I'm not sure if this is possible. Since every time you add a linq clause (Select, Where ...), it creates a new query of this return type, therefore, if you have a DBEntity "entity" entity and you made entities. Select (x => x.someField) and some field says type int it now IQueryable, now your model does not work, because it expects an int and gets DBEntitity

+3
source share

All Articles