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

时间:2013-08-21 19:14:09

标签: c# .net linq entity-framework iqueryable

更新2013-08-22:

看了一下'建立一个IQueryable提供者系列'(感谢链接!)我得到了更多。我相应地更新了代码。它仍然没有完全工作。如果我正确理解了教程,则在请求多个元素的情况下调用GetEnumerator(例如,通过ToList()调用queryable或任何聚合函数)。因此,包装器的所有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实现提供{{1}并从我的TranslatingEnumerator方法返回此枚举。为了获得调查员Execute调用GetEnumerator(见下文)。请求枚举器的LINQ代码似乎确保表达式实际返回Execute

关于代码的一些附注:

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

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

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

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

  • 我担心整个实现不正确 - 代码中的TODO只是我自己的笔记。

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

感谢您的帮助!

IQueryable implementation

IEnumerable

IQueryProvider实施

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

IEnumerator实现

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

2 个答案:

答案 0 :(得分:3)

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

  

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

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

  

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

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

What is the difference between IQueryable<T> and IEnumerable<T>?

  

由于在每种情况下都没有使用枚举器,因此正确的位置   指向调用翻译功能?执行方法   QueryProvider似乎是正确的地方。但是我还需要吗?   在某些情况下,在枚举器中进行翻译调用?

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

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

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

答案 1 :(得分:2)

到目前为止,我发现每次枚举查询时都会收到异常的原因:实体框架的IQueryable基础结构的实现方式与构建IQueryable提供程序系列,pt中描述的模式有很大不同。 1

  • 博文建议通过致电提供商的GetEnumerator() 来实施Execute()

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

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