【问题标题】:Does adding .ToArray or .ToList always make database queries faster?添加 .ToArray 或 .ToList 是否总是使数据库查询更快?
【发布时间】:2014-02-19 22:28:25
【问题描述】:

我注意到将 .ToArray() 或 .ToList() 添加到查询时,数据库查询运行得更快。这是因为数据集被加载到内存中并且所有后续查询都是在内存中完成的,而不是进行更昂贵的数据库调用吗?

数据库查询的内存存储限制应该是多少,因为我担心使用过多的内部内存会减慢整个项目的速度,因为我确信占用过多的内存存储会减慢速度急剧下降。

编辑:这适用于 Linq-SQL 查询。我正在使用 SQL Server 2008。

示例 1:在内存中执行大型数据库查询和过滤

我有一个包含 5 000 行的数据库表。我查询整个表(例如 SELECT* From Clients)。我接下来的几个查询基于上一个查询的字段:a)获取所有男性客户; b) 获取所有女性客户 c) 获取 FirstName 以 A 开头的所有客户。

示例 2:执行更频繁的数据库调用

使用同一个5000行的Client表,我需要执行3个查询a)获取所有男性客户; b) 获取所有女性客户 c) 获取 FirstName 以 A 开头的所有客户。我通过数据库调用而不是在内存中进行所有查询。

哪种方法更有效?

【问题讨论】:

  • 它几乎只是以非常简单的形式进行缓存。当您可以运行一次并记住结果时,为什么要一遍又一遍地运行一个每次需要 2 分钟的查询呢?不幸的是,这没有硬性和快速的答案。您需要了解每种情况。您不能只将ToArray 应用于所有情况,它并不总是能让事情变得更快。至于有没有限制,谁知道呢?这取决于很多事情。
  • 你需要举一个具体的例子。在某些情况下,它会使事情变得非常变慢,例如,在过滤之前拉入整个数据库表。
  • 这是用于 Linq-to-Sql 的吗?英孚?无论如何, .ToArray() 和 .ToList() 是在 IEnumerable 上运行的扩展方法,实际上是“枚举”结果。在您真正需要枚举的 IEnumerable 之前,它将推迟执行并尝试生成最有效的表达式以返回枚举结果。通过调用 .ToArray() 或 .ToList() 尽早枚举结果(急切加载)会预先获得成功,但允许您一遍又一遍地重用该查询,而无需枚举结果。
  • 同样使用ToList()ToArray() 将结果保存在内存中并不总是会给我们新的数据。您将创建一些类似捕获的东西,因此您需要不时更新数据以获取新数据。

标签: c# linq performance toarray


【解决方案1】:

注意:我在查询中使用了实体框架“上下文”。答案适用于实体框架或 LINQ-to-SQL。

ToArray() 和 ToList() 永远不会加快查询速度。但是,如果您不小心多次运行查询,它们可能会以这种方式显示。这是使用 LINQ 时的常见错误。例如:

// Construct a query to find all the cute puppies
var data = myContext.Puppies.Where(puppy => puppy.Type == PuppyTypes.Cute);

上面的行实际上并没有查询数据库中的可爱小狗。相反,它构建了一个查询,该查询将在未来某个时候查询小狗。这称为延迟执行。问答题:上例中的“数据”是什么类型?

查询实际上是在您枚举变量数据时运行的。此变量的类型为 IEnumerable。 IEnumerables 不同于列表或数组。 IEnumerable 仅仅是一个可以获取数据的承诺。这并不意味着您实际上拥有数据。

// Display the puppies
foreach (Puppy p in data) { Console.WriteLine(p.Name); }

上面的 foreach 调用将强制执行查询。小狗将进入屏幕,但它们不会被缓存在任何地方的数组或列表中。所以现在,如果你这样做:

// Display their owner's names
foreach (Puppy p in data) { Console.Writeline(p.OwnerName); }

这个 foreach 导致查询再次执行。事实上,如果在两次调用之间在数据库中添加或删除了新的小狗,您甚至可能得到不同的结果!所以假设相反,我们将 ToList() 添加到初始调用中。

// Construct a query to find all the cute puppies
var data = myContext.Puppies.Where(puppy => puppy.Type == PuppyTypes.Cute).ToList();

现在,数据是列表类型。在后台,这会创建一个 IEnumerable,然后通过它执行 foreach,并将结果放入列表中。所以现在,如果你显示小狗的名字和主人,它不会两次查询数据库。而是在列表中循环两次。

如果您搜索延迟执行,您会发现很多有趣的用途,以及我列出的警告。通常,您要确保只运行一次查询。如果这需要 ToList() 那就太好了。但不要不必要地添加 ToList() 。使用最后一个例子:

var data = myContext.Puppies.Where(puppy => puppy.Type == PuppyTypes.Cute).ToList();
foreach (Puppy p in data) { Console.WriteLine(p.Name + "," + p.OwnerName); }

这里应该有 ToList() 吗?不!因为当您确实不需要列表时,这会增加列表的开销。更糟糕的是:

var data = myContext.Puppies.Where(puppy => puppy.Type == PuppyTypes.Cute).ToList();
Console.Writeline(data.Count());

那就更糟了!它把所有的小狗都拉到内存中的一个列表中,然后对它们进行计数。但这个查询甚至根本不需要抓住小狗。这样做:

var data = myContext.Puppies.Where(puppy => puppy.Type == PuppyTypes.Cute);
Console.Writeline(data.Count());

它实际上告诉 SQL 服务器计算小狗的数量,并且从不浪费带宽或内存实际将小狗从数据库中加载出来或将其发送到 C# 或任何其他地方。

我希望这会有所帮助!

【讨论】:

  • 很好的回答我的朋友+1
  • This variable is of type IEnumerable - 你的意思是IQueryable
  • @Jamiec 是正确的。数据上下文返回一个 IQueryable,它又是一个 IEnumerable。我不想深入了解 IQueryable 的细节。
  • @Moby Disk PuppyTypes.Cute- 这很酷!))很好的答案,谢谢!
猜你喜欢
  • 2012-05-15
  • 1970-01-01
  • 1970-01-01
  • 2010-12-22
  • 2016-09-21
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-09-19
相关资源
最近更新 更多