InvalidOperationException

InvalidOperationException

本文介绍了实现IQueryable的包装翻译结果对象的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

更新2013年8月22日:

在有一个看建立一个IQueryable提供系列'(感谢您的联系!),我有点进一步。因此,我更新了code。这还没有完全虽然工作。如果我理解正确的教程中,的GetEnumerator 被调用的情况下的多个元素请求(例如,通过了ToList()呼吁可查询,或任何聚合函数)。因此,所有的的GetEnumerator 实施包装的所要做的就是叫执行上的供应商,并通过可查询的前pression。在其他情况下,如果只有一个元素被请求时,执行被直接调用。在可查询的前pression也反映出无论是单个或多个元素。它是否正确?

不幸的是,现在我得到一个InvalidOperationException说的序列包含多个元素的要求时,执行在源查询供应商。这是什么意思?我只是通过前pression无需任何转换,因为同类型涉及如上所述。该translatation位与的IEnumerable 在code可能是不完整的,但现在我甚至不到这一点。


我想实现使用一个底层的IQueryable因为它调用一个函数转换为每个结果对象的数据源的一个简单的IQueryable包装。

我想这是因为包装所要做的就是翻译的唯一的事情比较琐碎。不过,我不能让我的执行工作。

请参阅下面的是我得到了这么远。对于某些查询是工作,但我收到 StackOverflowException 出现InvalidOperationException在一些点。

在这里,我的问题和想法就可以了:

1。为何IQueryable的有供应商反过来再次返回一个IQueryable?未在本征集无限递归?

2。为什么是不够的,实现IEnumerator?为什么FirstOrDefault比如不使用枚举来获取元素?当我调试的应用程序的GetEnumerator()对我可查询。没有称为FirstOrDefault()。

3。由于枚举不能在任何情况下使用的,哪里是正确的点来调用翻译功能?该QueryProvider的执行,方法似乎是正确的地方。但我还需要翻译电话中枚举某些情况下更新:的我知道意识到,我需要提供自己的的IEnumerable 的实施提供了 TranslatingEnumerator 键,返回此枚举从我的执行方法。为了让普查员的GetEnumerator 电话执行(见下文)。在LINQ code,要求普查员似乎确保前pression实际上返回一个的IEnumerable

在code端的一些注意事项:

  • 翻译源型被命名为 TDatabaseEntity 的,翻译的目标类型被命名为 TBusinessEntity 的。

  • 我基本上是提供一个IQueryable这需要从底层的IQueryable检索的结果对象,并将它们转换为TBusinessEntity 键入对象的。

  • 我知道,防爆pression也需要翻译。但是我设置预留,因为在我的实际应用中我使用的是同一个类型TBusinessEntity和TDatabaseEntity,所以防爆pression可以直接经之地。

  • 的结果对象仍然需要被翻译成虽然其它情况下,尽管是同一类型的。 更新:的我的翻译层工作已经在我的应用程序,也需要相关实体的照顾。这只是实现​​一个IQueryable包装'的事,我只能坚持。

  • 恐怕整个实现是不正确 - 在待办事项中的code只是我自己的笔记

背景:我是那种实现我自己的实体拆卸从的DbContext收到我的数据访问层内,以prevent我的业务层,从得到的与实际的实体触摸 - 因一些错误与英法等要求,我不能直接使用EF分离的实体。

感谢您的帮助!

IQueryable的实施

 内部类TranslatingQueryable< TDatabaseEntity,TBusinessEntity> :IQueryable的< TBusinessEntity>
{
    私人只读IQueryProvider _provider;
    私人只读的IQueryable< TDatabaseEntity> _资源;

    内部TranslatingQueryable(TranslatingQueryProvider供应商的IQueryable< TDatabaseEntity>源)
    {
        Guard.ThrowIfArgumentNull(供应商,提供者);
        Guard.ThrowIfArgumentNull(源,源);

        _provider =提供商;
        _source =来源;
    }

    内部TranslatingQueryable(Func键<对象,对象> translateFunc,IQueryable的< TDatabaseEntity> databaseQueryable)
        :这个(新TranslatingQueryProvider(translateFunc,databaseQueryable.Provider),databaseQueryable)
    {
    }

    公众的IEnumerator< TBusinessEntity>的GetEnumerator()
    {
        返回((IEnumerable的< TBusinessEntity>)Provider.Execute(防爆pression))的GetEnumerator()。
    }

    IEnumerator的IEnumerable.GetEnumerator()
    {
        返回((IEnumerable的)Provider.Execute(防爆pression))的GetEnumerator()。
    }

    公共防爆pression前pression
    {
        得到
        {
            返回_source.Ex pression;
        }
    }

    公共类型元素类型
    {
        得到
        {
            返回的typeof(TBusinessEntity);
        }
    }

    公共IQueryProvider提供商
    {
        得到
        {
            返回_provider;
        }
    }
}
 

IQueryProvider实施

 公共类TranslatingQueryProvider:IQueryProvider
{
    私人只读Func键<对象,对象> _translateFunc;
    私人只读IQueryProvider _databaseQueryProvider;

    公共TranslatingQueryProvider(Func键<对象,对象> translateFunc,IQueryProvider databaseQueryProvider)
    {
        _translateFunc = translateFunc;
        _databaseQueryProvider = databaseQueryProvider;
    }

    公众的IQueryable的createQuery(出pression EX pression)
    {
        VAR databaseQueryable = _databaseQueryProvider.CreateQuery<对象>(EX pression);

        返回新TranslatingQueryable<对象,对象>(本,databaseQueryable);
    }

    公众的IQueryable< TElement>的createQuery< TElement>(出pression EX pression)
    {
        VAR databaseQueryable = _databaseQueryProvider.CreateQuery<对象>(EX pression);

        返回新TranslatingQueryable<对象,TElement>(这一点,databaseQueryable);
    }

    公共对象Execute(前pression EX pression)
    {
        返回执行<对象>(EX pression);
    }

    公共TResult执行< TResult>(出pression EX pression)
    {
        // TODO此调用引发InvalidOperationException如果枚举请求
        VAR databaseResult = _databaseQueryProvider.Execute< TResult>(EX pression);

        VAR databaseEnumerable = databaseResult为IEnumerable;
        如果(databaseEnumerable!= NULL)
        {
            如果(typeof运算(TResult).IsAssignableFrom(typeof运算(IEnumerable的)))
            {
                抛出新的InvalidOperationException异常();
            }

            返回(TResult)(对象)新TranslatingEnumerable(databaseEnumerable,_translateFunc);
        }
        其他
        {
            返回(TResult)_translateFunc(databaseResult);
        }
    }

    私有类TranslatingEnumerable:IEnumerable的
    {
        私人只读TranslatingEnumerator _enumerator;

        公共TranslatingEnumerable(IEnumerable的databaseEnumerable,Func键<对象,对象> translateFunc)
        {
            _enumerator =新TranslatingEnumerator(translateFunc,databaseEnumerable.GetEnumerator());
        }

        公众的IEnumerator的GetEnumerator()
        {
            返回_enumerator;
        }
    }
}
 

IEnumerator的实施

 内部类TranslatingEnumerator:IEnumerator的
{
    私人只读Func键<对象,对象> _translateFunc;
    私人只读的IEnumerator _databaseEnumerator;

    内部TranslatingEnumerator(Func键<对象,对象> translateFunc,IEnumerator的databaseEnumerator)
    {
        _translateFunc = translateFunc;
        _databaseEnumerator = databaseEnumerator;
    }

    公共BOOL的MoveNext()
    {
        返回_databaseEnumerator.MoveNext();
    }

    公共无效复位()
    {
        _databaseEnumerator.Reset();
    }

    公共对象当前
    {
        得到
        {
            返回_translateFunc(_databaseEnumerator.Current);
        }
    }

    对象IEnumerator.Current
    {
        得到
        {
            回到当前的;
        }
    }
}
 

解决方案

现在我发现了,为什么我每次查询枚举时间接收到异常:实体框架的IQueryable的基础设施,从描述的模式实现非常不同在建立一个IQueryable提供商系​​列,PT。 1 的。

  • 博客文章建议实施的GetEnumerator() 致电执行()上供应商

  • 在此相反,在EF基础设施,ObjectQueryProvider的执行()方法只接受前pressions它返回一个结果对象 - 但不是一个结果对象的枚举集合(这甚至记录在源$ C ​​$ C)。因此,的ObjectQuery的的GetEnumerator()方法不叫执行()但另一种方法得到的结果直接从数据库

因此​​,任何翻译IQueryable的实现,它使用一个底层数据库查询来获取对象必须使用相同的模式 - 的GetEnumerator()方法只是调用翻译的GetEnumerator()对底层数据库查询并注入到这一个新的 TranslatingEnumerator

Update 2013-08-22:

After having a look at the 'Building an IQueryable provider series' (thanks for the link!) I got a bit further. I updated the code accordingly. It is still not fully working though. If I understand the tutorial correctly, the GetEnumerator is invoked in case multiple elements are requested (e.g. by a ToList() call on the queryable, or any aggregation function). So all the GetEnumerator implementation of the wrapper has to do is call an Execute on the provider and pass the queryable's expression. In the other case, if only a single element is requested, Execute is called directly. The queryable's expression also reflects whether it is for a single or multiple elements. Is this correct?

Unfortunately now I get an InvalidOperationException saying 'Sequence contains more than one element' when calling Execute on the source query provider. What does this mean? I just pass the expression without any translation since the same types are involved as mentioned above. The translatation bit with IEnumerable in the code is probably incomplete, but for now I don't even get to that point.


I'm trying to implement a simple IQueryable wrapper using a single underlying IQueryable as the data source which calls a translation function for each result object.

I thought this would be relatively trivial since the only thing the wrapper has to do is translating. However I couldn't get my implementation to work.

See below for what I got so far. For some queries it is working but I receive a InvalidOperationException at some point.

Here my questions and thoughts on it:

Update: I know realized that I need to provide my own IEnumerable implementation providing the TranslatingEnumerator and return this enumerable from my Execute method. In order to get the enumerator GetEnumerator calls Execute (see below). The LINQ code requesting the enumerator seems to make sure that the expression actually returns an IEnumerable.

Some side notes on the code:

  • The translation source type is named TDatabaseEntity, the translation target type is named TBusinessEntity.

  • I'm essentially providing an IQueryable which takes the result objects retrieved from an underlying IQueryable and translates them to the TBusinessEntity objects.

  • I'm aware that the Expression also needs to be translated. However I set this aside since in my actual application I'm using the same types for TBusinessEntity and TDatabaseEntity, so the Expression can be passed straight through.

  • The result objects still need to be translated to other instances though, despite being of the same type. Update: My translation layer is working already within my application and also takes care of related entities. It's just the 'implementing an IQueryable wrapper' thing I'm stuck with.

  • -- the TODOs in the code are just my own notes.

Background: I'm kind of implementing my own detaching of entities received from DbContext within my data access layer to prevent my business layer from getting in touch with the actual entities -- due to some bugs with EF and other requirements I can't directly use EF detached entities.

Thanks 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;
        }
    }
}
解决方案

By now I found out why I received an exception every time the query has been enumerated: The IQueryable infrastructure of the Entity Framework is implemented very differently from the pattern described in Building an IQueryable provider series, pt. 1.

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

  • In contrast, in the EF infrastructure, ObjectQueryProvider's Execute() method only accepts expressions which return a single result object -- but not an enumerable collection of result objects (this is even documented in the source code). Accordingly, ObjectQuery's GetEnumerator() method does not call Execute() but another method getting the result right from the database.

Thus, any translating IQueryable implementation which uses an underlying database query to get the objects must use the same pattern -- the translating GetEnumerator() method just calls GetEnumerator() on the underlying database query and injects this into a new TranslatingEnumerator.

这篇关于实现IQueryable的包装翻译结果对象的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!

08-30 06:10