21

2013-08-22 更新:

在查看了“构建 IQueryable 提供程序系列”之后(感谢您的链接!),我进一步了解了一些。我相应地更新了代码。它仍然没有完全工作。如果我正确理解了本教程,GetEnumerator则会在请求多个元素的情况下调用 (例如,通过ToList()调用可查询或任何聚合函数)。因此GetEnumerator,包装器的所有实现所要做的就是Execute在提供者上调用 an 并传递可查询的表达式。在另一种情况下,如果只请求单个元素,Execute则直接调用。可查询的表达式还反映了它是针对单个元素还是多个元素。这个对吗?

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


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

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

到目前为止,请参阅下面的内容。对于某些查询,它正在工作,但我在某些时候收到了StackOverflowException InvalidOperationException。我猜这是由于我的可查询对象和我的查询提供者之间的循环关联而发生的。但我不明白如何正确实现这一点。

这是我的问题和想法:

1. 为什么IQueryable 有一个Provider 又返回一个IQueryable?这不是需要无限递归吗?

2、为什么实现IEnumerator还不够?为什么 FirstOrDefault 例如不使用枚举器来获取元素?当我在我的可查询对象上调试应用程序 GetEnumerator() 时,FirstOrDefault() 没有调用它。

3. 由于枚举器不是在所有情况下都使用,那么调用翻译函数的正确点在哪里?QueryProvider 的执行方法似乎是正确的地方。但是在某些情况下,我是否仍然需要枚举器中的翻译调用? 更新:我知道我需要提供我自己的IEnumerable实现来提供并从我的方法中TranslatingEnumerator返回这个可枚举。Execute为了获得枚举器GetEnumerator调用Execute(见下文)。请求枚举器的 LINQ 代码似乎确保表达式实际上返回一个IEnumerable.

关于代码的一些旁注:

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

  • 我本质上提供了一个 IQueryable,它获取从底层 IQueryable 检索的结果对象并将它们转换为 TBusinessEntity类型的 对象

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

  • 尽管结果对象属于同一类型,但仍需要将其转换为其他实例。更新:我的翻译层已经在我的应用程序中工作,并且还负责相关实体。这只是我坚持的“实现 IQueryable 包装器”的事情。

  • 恐怕整个实现都不正确——代码中的 TODO 只是我自己的笔记。

背景:我有点在我的数据访问层中实现我自己的从 DbContext 接收的实体的分离,以防止我的业务层与实际实体取得联系——由于 EF 和其他要求的一些错误,我不能直接使用 EF 分离实体。

谢谢你的帮助!

可查询的实现

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 实现

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 实现

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;
        }
    }
}
4

2 回答 2

3

好吧,这是我最好的回答

为什么 IQueryable 有一个 Provider 又返回一个 IQueryable ?这不是需要无限递归吗?您想为此实例返回一个 IQueryable

SomeEnumerable.Where(x=>x.Field == something).Select(x=>x.SomeOtherField) 如果您熟悉链接,请考虑 JQuery

为什么只实现 IEnumerator 还不够?为什么 FirstOrDefault 例如不使用枚举器来获取元素?当我在我的可查询对象上调试应用程序 GetEnumerator() 时,FirstOrDefault() 没有调用它。

由于 IQueryable 有 2 个特殊属性查询提供程序和查询表达式:

IQueryable<T> 和 IEnumerable<T> 有什么区别?

由于不是在每种情况下都使用枚举器,所以调用翻译函数的正确点在哪里?QueryProvider 的执行方法似乎是正确的地方。但是在某些情况下,我是否仍然需要枚举器中的翻译调用?

执行是正确的地方,您将不得不解析表达式树,因为提供者不知道您执行时要做什么。

我还添加了这个链接,当我实现自己的查询提供程序 http://blogs.msdn.com/b/mattwar/archive/2008/11/18/linq-links.aspx时,它对我有很大帮助

您可能能够摆脱的是使用与本文作者相同的方式获取 dbReader 并将其转换为实际对象,而不是读者获取您的 DBEntity 并将其转换为您的 BusinessEntity,但我不确定是否这个有可能。因为每次您添加一个 linq 子句(Select,Where...)它都会创建一个该返回类型的新查询,所以如果您有一个 DBEntity“实体”实体并且您执行了 entity.Select(x=>x.someField)并且某些字段是 int 类型的,它现在是 IQueryable,现在您的模型不起作用,因为它需要 int 并且它正在获取 DBEntity

于 2013-08-21T21:49:35.127 回答
2

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

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

  • 相比之下,在 EF 基础结构中,ObjectQueryProvider 的Execute()方法只接受返回单个结果对象的表达式,而不接受结果对象的可枚举集合(这甚至在源代码中都有说明)。因此,ObjectQuery 的GetEnumerator()方法不调用Execute()而是另一种方法从数据库中获取结果

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

于 2013-08-25T22:44:34.023 回答