AsEnumerable及其如何影响收益率和SqlDataReader

本文关键字:影响 收益率 SqlDataReader 何影响 AsEnumerable | 更新日期: 2023-09-27 18:26:47

我试图了解AsEnumerable()在迭代数据时对数据的影响。我有一个内存中的mock列表。如果我在第一次调用ToList()foreach覆盖它,这将强制评估,我的打印输出如下所示(请参阅本文底部的代码来解释输出):

entering da
yield return
yield return
yield return
exiting da
doing something to aaron
doing something to jeremy
doing something to brendan

一切都有道理。ToList()强制存储库中的yield首先执行到列表中,然后我们得到foreach迭代。到目前为止一切都很好。

当我除了使用AsEnumerable()之外做同样的事情时,根据我读到的关于IQueryable的内容(我知道这不是IQueryable),我本以为这也会强制评估,但事实并非如此。它看起来像这样:

entering da
yield return
doing something to aaron
yield return
doing something to jeremy
yield return
doing something to brendan
exiting da

如果我从来没有打过AsEnumerable(),那么我的问题是:

  1. 为什么AsEnumerable在内存中集合中的行为与linq-to-sql及其IQueryable返回类型不同?

  2. 当我的存储库被更改为使用SqlDataReader并在读取器内部执行yield return时(同时调用Read()方法),所有这些会发生什么变化。在执行foreach之前,是否会对来自SqlServer的缓存在客户端网络缓冲区中的行进行全面评估(通常,这里的yield会导致回购中的"暂停",而每一行都由foreach块处理。我知道如果在这种情况下我先调用ToList(),我可以强制评估SqlDataReader,那么AsEnumerable在这里也会这样做吗?

注意:我对将yield放入SqlDataReader是否是个好主意不感兴趣,因为它可能会打开连接,我已经把这个话题打得落花流水了:)

这是我的测试代码:

    public class TestClient
    {
        public void Execute()
        {
            var data = MockRepo.GetData();
            foreach (var p in data.AsEnumerable()) //or .ToList()
            {
                Console.WriteLine("doing something to {0}", p.Name);
            }
            Console.ReadKey();
        }
    }
    public class Person
    {
        public Person(string name)
        {
            Name = name;
        }
        public string Name { get; set; }
    }
    public class MockRepo
    {
        private static readonly List<Person> items = new List<Person>(3)
                        {
                            new Person("aaron"),
                            new Person("jeremy"), 
                            new Person("brendan")
                        };
        public static IEnumerable<Person> GetData()
        {
            Console.WriteLine("entering da");
            var enumerator = items.GetEnumerator();
            while (enumerator.MoveNext())
            {
                Console.WriteLine("yield return");
                yield return enumerator.Current;
            }
            Console.WriteLine("exiting da");
        }
    }

AsEnumerable及其如何影响收益率和SqlDataReader

AsEnumerable除了将表达式类型更改为IEnumerable<T>之外,什么都不做。当它用于这样的查询时:

var query = db.Customers
              .Where(x => x.Foo)
              .AsEnumerable()
              .Where(x => x.Bar);

这意味着您将使用Queryable.Where作为第一个谓词(因此转换为SQL),使用Enumerable.Where作为第二个谓词(从而在.NET代码中执行)。

它不会强制评估。它对没有任何作用。它甚至不检查是否在null上调用了它。

有关更多信息,请参阅我在AsEnumerable上的Edulinq博客文章。

@Jon Skeet已经发布了AsEnumerable()的功能——它只是更改了编译时类型。但你为什么要用它呢?

从本质上讲,通过将表达式从IQueryable更改为IEnumerable,您现在可以在没有任何限制的情况下使用Linq-to-Objects(而不是数据库提供程序的IQueryable实现)-数据库端不必有等效的方法,因此您可以自由地执行对象转换、远程调用(如果需要)或任何类型的字符串操作。

也就是说,当你还在处理数据库(IQueryable)时,你会想做所有你能做的过滤——否则你会把所有这些行都带到内存中,这会让你付出代价——然后再使用AsEnumerable()进行最后的转换。

根据MSDN文档:

AsEnumerable(Of-TSource)(IEnumerable(Of-TSource))方法没有除了从类型,将IEnumerable(Of T)实现为IEnumeraable(Of T)本身。

它不应该引起任何评估,只是提示您希望使用IEnumerable方法与其他实现(IQueryable等)。