实现IQueryable包装器来转换结果对象

cya*_*902 16 .net c# linq entity-framework iqueryable

更新2013-08-22:

看了"建立一个IQueryable提供者系列"(感谢链接!)后,我进一步了解了一下.我相应地更新了代码.它仍然没有完全工作.如果我正确理解了教程,GetEnumerator则在请求多个元素的情况下调用(例如,通过ToList()调用queryable或任何聚合函数).所以GetEnumerator包装器的所有实现都要Execute在提供者上调用一个并传递可查询的表达式.在另一种情况下,如果只请求一个元素,Execute则直接调用.queryable的表达式还反映了它是单个元素还是多个元素.它是否正确?

不幸的是,现在我在调用源查询提供程序时得到一个InvalidOperationException,说"Sequence包含多个元素"Execute.这是什么意思?我只是传递表达式而没有任何翻译,因为如上所述涉及相同的类型.IEnumerable代码中的翻译位可能不完整,但是现在我甚至没有达到这一点.


我正在尝试使用单个底层IQueryable作为数据源来实现一个简单的IQueryable包装器,该数据源为每个结果对象调用转换函数.

我认为这将是相对微不足道的,因为包装器唯一要做的就是翻译.但是我无法让我的实现工作.

请看下面我到目前为止所得到的内容.对于某些查询,它正在工作,但我在某些时候收到StackOverflowException InvalidOperationException.我想这是因为我的queryable和我的查询提供程序之间的循环关联.但我不明白如何正确实现这一点.

在这里我的问题和想法:

1.为什么IQueryable有一个Provider又可以再次返回IQueryable?这不是要求无休止的递归吗?

2.为什么不能实现IEnumerator?为什么FirstOrDefault不使用枚举器来获取元素?当我调试应用程序时GetEnumerator()没有被我的可查询器上的FirstOrDefault()调用.

3.由于在每种情况下都没有使用枚举器,因此调用翻译函数的正确位置在哪里?QueryProvider的Execute-methods似乎是正确的地方.但在某些情况下,我仍然需要在枚举器中进行翻译调用吗? 更新:我知道我意识到我需要提供自己的IEnumerable实现,TranslatingEnumerator并从我的Execute方法返回这个枚举.为了获得枚举器GetEnumerator调用Execute(见下文).请求枚举器的LINQ代码似乎确保表达式实际返回一个IEnumerable.

关于代码的一些附注:

  • 翻译源类型名为TDatabaseEntity,翻译目标类型名为TBusinessEntity.

  • 我基本上提供了一个IQueryable,它从基础IQueryable中获取结果对象,并将它们转换为TBusinessEntity 类型 对象.

  • 我知道表达式也需要翻译.但是我将它放在一边,因为在我的实际应用程序中,我使用相同类型的TBusinessEntity和TDatabaseEntity,因此Expression可以直接传递.

  • 尽管属性相同,但结果对象仍然需要转换为其他实例.更新:我的翻译层已经在我的应用程序中工作,并且还负责相关实体.它只是'实现一个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;
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

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;
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

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;
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

cya*_*902 2

现在我明白了为什么每次枚举查询时都会收到异常:实体框架的 IQueryable 基础结构的实现方式与构建 IQueryable 提供程序系列 pt. 中描述的模式非常不同。1 .

  • GetEnumerator() 该博客文章建议通过调用Execute()提供商来实施。

  • 相反,在 EF 基础结构中,ObjectQueryProvider 的Execute()方法仅接受返回单个结果对象的表达式,而不接受返回结果对象的可枚举集合(这甚至记录在源代码中)。因此,ObjectQuery 的GetEnumerator()方法不会调用,而是调用另一个直接从数据库获取结果的Execute()方法。

因此,任何使用底层数据库查询来获取对象的转换 IQueryable 实现都必须使用相同的模式——转换GetEnumerator()方法只是调用GetEnumerator()底层数据库查询并将其注入到新的TranslatingEnumerator.