Implement IQueryable wrapper to translate result objects

.net c# entity-framework iqueryable linq

Question

2013-08-22 Update:

I've read the "Building an IQueryable provider series" (thanks for the link!). I advanced a little bit. I made the necessary code updates. However, it is still not operating entirely. If the instruction is to be believed, theGetEnumerator is used when numerous elements are needed, such as when aToList() make use of any aggregation function or the queryable). hence, all theGetEnumerator Calling an is all that the wrapper's implementation must perform.Execute pass the queryable's expression on the provider. In the alternative, if just one element is required,Execute is immediately called. Whether the query is for one or more elements is also indicated by the queryable's phrase. Is this accurate?

Unfortunately, whenever I call, I now get an InvalidOperationException with the message Sequence includes multiple elements..Execute on the supplier of the source query. Why does this matter? Since the same kinds as those specified above are involved, I just send the expression without any translation. The translation portion ofIEnumerable in the code is undoubtedly lacking, but for the time being, I don't even touch that subject.


Using a single underlying IQueryable as the data source, I'm attempting to construct a straightforward IQueryable wrapper that runs a translation function for each return object.

Since the wrapper simply needs to translate, I believed this task to be rather simple. But I was unable to make my implementation work.

What I've so far gotten is shown below. It works for various searches, however occasionally I get a StackOverflowException InvalidOperationException. The cyclic link between my queryable and my query provider, I suppose, is to blame for this. But I'm not sure how to go about doing this properly.

Here are my queries and observations:

Why is there a Provider for the IQueryable, which then returns another IQueryable? This necessitates endless recursion, right?

2. Why isn't implementing IEnumerator sufficient? Why does FirstOrDefault, for example, not get the element using the enumerator? When I was debugging the application, FirstOrDefault() on my queryable did not call GetEnumerator().

3. When should the translation function be called as the enumerator is not always used? It looked like the Execute-methods of the QueryProvider were the best place. But in some circumstances, do I still require the translation call in the Enumerator?Update: I've come to the realization that I must supply my own.IEnumerable implementation supplyingTranslatingEnumerator as well as deliver this enumerable from myExecute method. To acquire the enumeratorGetEnumerator calls Execute (See underneath. It appears that the LINQ function that requests the enumerator ensures that the expression truly returns anIEnumerable .

Observations on the code:

  • TDatabaseEntity is the name of the translation source type, and TBusinessEntity is the name of the translation destination type.

  • In essence, I'm offering an IQueryable that converts the result objects retrieved from an underlying IQueryable to the TBusinessEntity typeobjects.

  • I understand that the Expression also need translation. The fact that I use the identical types for TBusinessEntity and TDatabaseEntity in my actual application allows the Expression to be passed through without modification, therefore I have pushed this aside.

  • Despite being of the same type, the result objects still need to be translated to other instances. Update: Within my application, my translation layer is already operational and takes care of linked entities. I'm only stuck with the "implementing an IQueryable wrapper" part.

  • 159-zzz — I just included my personal notes as TODOs in the code.

Background: Due to some EF flaws and other restrictions, I can't directly use EF detached entities, thus I'm kind of building my own detaching of entities obtained from DbContext within my data access layer to prevent my business layer from getting in touch with the actual entities.

I appreciate your support.

Implementation of 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;
        }
    }
}

Implementation of 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;
        }
    }
}

Implementation of 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
17
8/25/2013 10:42:58 PM

Accepted Answer

By this time, I had figured out why I consistently got an exception when the query was enumerated: The Entity Framework's IQueryable infrastructure is implemented quite differently from the design mentioned in Part 1 of the series "Building an IQueryable provider".

  • The blog post recommends usingGetEnumerator() through telephoneExecute() about the provider.

  • ObjectQueryProvider's in the EF infrastructure, in contrast,Execute() Rather than an enumerable collection of result objects, the method only accepts expressions that yield a single result object (this is even documented in the source code). Therefore, ObjectQuery'sGetEnumerator() approach doesn't callExecute() yet there's also obtaining the information directly from the database.

As a result, any translating IQueryable implementation that retrieves the objects from a database using an underlying query must follow the same pattern.GetEnumerator() merely callingGetEnumerator() underlying database query and incorporates it into a new query.TranslatingEnumerator .

2
6/5/2015 1:37:42 PM

Popular Answer

ZZZ_tmp


Related Questions





Related

Licensed under: CC-BY-SA with attribution
Not affiliated with Stack Overflow
Licensed under: CC-BY-SA with attribution
Not affiliated with Stack Overflow