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

更新2013-08-22:

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

不幸的是,现在我在源查询提供程序上调用Execute时得到一个InvalidOperationException,说‘Sequence包含多个元素’ 。 这是什么意思? 我只是传递表达式而没有任何翻译,因为如上所述涉及相同的类型。 代码中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 : IQueryable { private readonly IQueryProvider _provider; private readonly IQueryable _source; internal TranslatingQueryable(TranslatingQueryProvider provider, IQueryable source) { Guard.ThrowIfArgumentNull(provider, "provider"); Guard.ThrowIfArgumentNull(source, "source"); _provider = provider; _source = source; } internal TranslatingQueryable(Func translateFunc, IQueryable databaseQueryable) : this(new TranslatingQueryProvider(translateFunc, databaseQueryable.Provider), databaseQueryable) { } public IEnumerator GetEnumerator() { return ((IEnumerable)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 _translateFunc; private readonly IQueryProvider _databaseQueryProvider; public TranslatingQueryProvider(Func translateFunc, IQueryProvider databaseQueryProvider) { _translateFunc = translateFunc; _databaseQueryProvider = databaseQueryProvider; } public IQueryable CreateQuery(Expression expression) { var databaseQueryable = _databaseQueryProvider.CreateQuery(expression); return new TranslatingQueryable(this, databaseQueryable); } public IQueryable CreateQuery(Expression expression) { var databaseQueryable = _databaseQueryProvider.CreateQuery(expression); return new TranslatingQueryable(this, databaseQueryable); } public object Execute(Expression expression) { return Execute(expression); } public TResult Execute(Expression expression) { // TODO This call throws an InvalidOperationException if an enumeration is requested var databaseResult = _databaseQueryProvider.Execute(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 translateFunc) { _enumerator = new TranslatingEnumerator(translateFunc, databaseEnumerable.GetEnumerator()); } public IEnumerator GetEnumerator() { return _enumerator; } } } 

IEnumerator实现

 internal class TranslatingEnumerator : IEnumerator { private readonly Func _translateFunc; private readonly IEnumerator _databaseEnumerator; internal TranslatingEnumerator(Func 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; } } } 

好的,我最好回答这个问题

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

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

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

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

IQueryable 和IEnumerable 有什么区别?

由于在每种情况下都没有使用枚举器,因此调用翻译函数的正确位置在哪里? QueryProvider的Execute-methods似乎是正确的地方。 但是在某些情况下,我仍然需要在枚举器中进行翻译调用吗?

Execute是正确的位置,您必须解析表达式树,因为提供程序在执行时不知道该怎么做。

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

您可以使用的方法是使用与此文章中的作者相同的方式获取dbReader并将其转换为实际对象,但不是读取者将您的DBEntity转换为您的BusinessEntity,但我不确定是否这个有可能。 因为每次你添加一个linq子句(Select,Where …)时它会创建一个返回类型的新查询,所以如果你有一个DBEntity实体“实体”并且你做了实体。选择(x => x.someField)并且某些字段是int类型,现在它是IQueryable,现在你的模型不起作用,因为它期望int并且它正在获得DBEntitity

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

  • 博客文章建议通过在提供程序上调用Execute()来实现GetEnumerator()

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

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