草庐IT

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

coder 2024-05-20 原文

更新2013-08-22:

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

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

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

我认为这将是相对琐碎的,因为包装程序唯一要做的就是翻译。但是我无法实现我的实现。

到目前为止,请参阅下文。对于某些查询,它可以正常工作,但有时会收到StackOverflowException InvalidOperationException。我猜这是由于我的可查询对象和我的查询提供者之间的周期性关联而发生的。但是我不明白如何正确实现这一点。

这里是我的问题和想法:

1.为什么IQueryable具有提供者,而该提供者又又返回IQueryable?这不是要求无限递归吗?

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

3.由于不是在每种情况下都使用枚举器,因此调用翻译函数的正确点在哪里? QueryProvider的Execute方法似乎是正确的地方。但是在某些情况下,我仍然需要Enumerator中的转换调用吗?更新:我知道我需要提供自己的IEnumerable实现并提供TranslatingEnumerator,并从Execute方法返回此可枚举的值。为了获得枚举器,GetEnumerator调用Execute(请参见下文)。请求枚举数的LINQ代码似乎确保该表达式实际上返回IEnumerable

代码的一些说明:

  • 转换源类型名为TDatabaseEntity,转换目标类型名为TBusinessEntity。
  • 我实质上是在提供一个IQueryable,该IQueryable将从底层IQueryable检索到的结果对象转换为TBusinessEntity类型的对象。
  • 我知道表达式也需要翻译。但是我将其搁置一旁,因为在我的实际应用程序中,我对TBusinessEntity和TDatabaseEntity使用相同的类型,因此可以直接传递Expression。
  • 尽管结果对象的类型相同,但仍然需要将其转换为其他实例。更新:我的翻译层已经在我的应用程序中正常工作,并且还负责相关实体。这只是我所坚持的“实现一个IQueryable包装器”。
  • 恐怕整个实现都不正确-代码中的TODO只是我自己的注释。

  • 背景:我正在实现自己从数据访问层中从DbContext接收到的实体的分离,以防止我的业务层与实际实体联系-由于EF的一些错误和我可以做到的其他要求, t直接使用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;
            }
        }
    }
    

    最佳答案

    好吧,这是我最好的回答

    Why does the IQueryable have a Provider which in turn returns an IQueryable again? Doesn't this call for endless recursion? You want to return an IQueryable for this instance


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

    Why is it not enough to implement IEnumerator? Why does FirstOrDefault for instance not use the enumerator to get the element? When I debugged the application GetEnumerator() was not called by FirstOrDefault() on my queryable.



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

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

    Since the enumerator is not used in every case, where is the correct point to call the translation function? The Execute-methods of the QueryProvider seemed to be the right place. But do I still need the translation call in the Enumerator for some cases?



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

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

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

    关于c# - 实现IQueryable包装器来转换结果对象,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/18365764/

    有关c# - 实现IQueryable包装器来转换结果对象的更多相关文章

    1. ruby - 如何从 ruby​​ 中的字符串运行任意对象方法? - 2

      总的来说,我对ruby​​还比较陌生,我正在为我正在创建的对象编写一些rspec测试用例。许多测试用例都非常基础,我只是想确保正确填充和返回值。我想知道是否有办法使用循环结构来执行此操作。不必为我要测试的每个方法都设置一个assertEquals。例如:describeitem,"TestingtheItem"doit"willhaveanullvaluetostart"doitem=Item.new#HereIcoulddotheitem.name.shouldbe_nil#thenIcoulddoitem.category.shouldbe_nilendend但我想要一些方法来使用

    2. ruby-on-rails - 在 Rails 中将文件大小字符串转换为等效千字节 - 2

      我的目标是转换表单输入,例如“100兆字节”或“1GB”,并将其转换为我可以存储在数据库中的文件大小(以千字节为单位)。目前,我有这个:defquota_convert@regex=/([0-9]+)(.*)s/@sizes=%w{kilobytemegabytegigabyte}m=self.quota.match(@regex)if@sizes.include?m[2]eval("self.quota=#{m[1]}.#{m[2]}")endend这有效,但前提是输入是倍数(“gigabytes”,而不是“gigabyte”)并且由于使用了eval看起来疯狂不安全。所以,功能正常,

    3. ruby-on-rails - 按天对 Mongoid 对象进行分组 - 2

      在控制台中反复尝试之后,我想到了这种方法,可以按发生日期对类似activerecord的(Mongoid)对象进行分组。我不确定这是完成此任务的最佳方法,但它确实有效。有没有人有更好的建议,或者这是一个很好的方法?#eventsisanarrayofactiverecord-likeobjectsthatincludeatimeattributeevents.map{|event|#converteventsarrayintoanarrayofhasheswiththedayofthemonthandtheevent{:number=>event.time.day,:event=>ev

    4. ruby - 使用 ruby​​ 将 HTML 转换为纯文本并维护结构/格式 - 2

      我想将html转换为纯文本。不过,我不想只删除标签,我想智能地保留尽可能多的格式。为插入换行符标签,检测段落并格式化它们等。输入非常简单,通常是格式良好的html(不是整个文档,只是一堆内容,通常没有anchor或图像)。我可以将几个正则表达式放在一起,让我达到80%,但我认为可能有一些现有的解决方案更智能。 最佳答案 首先,不要尝试为此使用正则表达式。很有可能你会想出一个脆弱/脆弱的解决方案,它会随着HTML的变化而崩溃,或者很难管理和维护。您可以使用Nokogiri快速解析HTML并提取文本:require'nokogiri'h

    5. ruby - 将数组的内容转换为 int - 2

      我需要读入一个包含数字列表的文件。此代码读取文件并将其放入二维数组中。现在我需要获取数组中所有数字的平均值,但我需要将数组的内容更改为int。有什么想法可以将to_i方法放在哪里吗?ClassTerraindefinitializefile_name@input=IO.readlines(file_name)#readinfile@size=@input[0].to_i@land=[@size]x=1whilex 最佳答案 只需将数组映射为整数:@land边注如果你想得到一条线的平均值,你可以这样做:values=@input[x]

    6. ruby - 将散列转换为嵌套散列 - 2

      这道题是thisquestion的逆题.给定一个散列,每个键都有一个数组,例如{[:a,:b,:c]=>1,[:a,:b,:d]=>2,[:a,:e]=>3,[:f]=>4,}将其转换为嵌套哈希的最佳方法是什么{:a=>{:b=>{:c=>1,:d=>2},:e=>3,},:f=>4,} 最佳答案 这是一个迭代的解决方案,递归的解决方案留给读者作为练习:defconvert(h={})ret={}h.eachdo|k,v|node=retk[0..-2].each{|x|node[x]||={};node=node[x]}node[

    7. ruby-on-rails - 如何验证非模型(甚至非对象)字段 - 2

      我有一个表单,其中有很多字段取自数组(而不是模型或对象)。我如何验证这些字段的存在?solve_problem_pathdo|f|%>... 最佳答案 创建一个简单的类来包装请求参数并使用ActiveModel::Validations。#definedsomewhere,atthesimplest:require'ostruct'classSolvetrue#youcouldevencheckthesolutionwithavalidatorvalidatedoerrors.add(:base,"WRONG!!!")unlesss

    8. Ruby 写入和读取对象到文件 - 2

      好的,所以我的目标是轻松地将一些数据保存到磁盘以备后用。您如何简单地写入然后读取一个对象?所以如果我有一个简单的类classCattr_accessor:a,:bdefinitialize(a,b)@a,@b=a,bendend所以如果我从中非常快地制作一个objobj=C.new("foo","bar")#justgaveitsomerandomvalues然后我可以把它变成一个kindaidstring=obj.to_s#whichreturns""我终于可以将此字符串打印到文件或其他内容中。我的问题是,我该如何再次将这个id变回一个对象?我知道我可以自己挑选信息并制作一个接受该信

    9. ruby-on-rails - 如果 Object::try 被发送到一个 nil 对象,为什么它会起作用? - 2

      如果您尝试在Ruby中的nil对象上调用方法,则会出现NoMethodError异常并显示消息:"undefinedmethod‘...’fornil:NilClass"然而,有一个tryRails中的方法,如果它被发送到一个nil对象,它只返回nil:require'rubygems'require'active_support/all'nil.try(:nonexisting_method)#noNoMethodErrorexceptionanymore那么try如何在内部工作以防止该异常? 最佳答案 像Ruby中的所有其他对象

    10. ruby-on-rails - 未在 Ruby 中初始化的对象 - 2

      我在Rails工作并有以下类(class):classPlayer当我运行时bundleexecrailsconsole然后尝试:a=Player.new("me",5.0,"UCLA")我回来了:=>#我不知道为什么Player对象不会在这里初始化。关于可能导致此问题的操作/解释的任何建议?谢谢,马里奥格 最佳答案 havenoideawhythePlayerobjectwouldn'tbeinitializedhere它没有初始化很简单,因为你还没有初始化它!您已经覆盖了ActiveRecord::Base初始化方法,但您没有调

    随机推荐