【问题标题】:How to use Linq's .Count() method in a conditional statement如何在条件语句中使用 Linq 的 .Count() 方法
【发布时间】:2016-10-27 12:52:58
【问题描述】:

我有一个线程定期检查我的一个 MS SQL 表中是否有任何将“已处理”位字段设置为 0 的记录。该线程然后使用这些记录执行一些代码,然后将它们的已处理位设置为 1;基本上将其用作队列。我用来检索这些记录的 Linq 查询跨越多行并且非常复杂(原因与问题无关),所以这里是一个非常简化的版本:

var RecordsToProcess = MyTable.Where(i => i.Processed == 0); // Very simplified

我需要等到所有记录都处理完才能继续,所以我想使用这样的东西:

while (RecordsToProcess.Count() > 0)
{
    System.Threading.Thread.Sleep(1000);
}

问题在于,虽然线程实际上确实处理了记录并将它们的已处理位设置为 1,但条件语句中的 RecordsToProcess.Count() 的值从未减少,因此我们得到了一个无限循环。我的猜测是调用 .Count() 将该整数存储在内存中,然后循环的每次迭代都会查看该值,而不是查询数据库以获取当前计数。我可以通过将查询移动到条件语句中来获得我想要的行为,如下所示:

while (true)
{
    if (MyTable.Where(i => i.Processed == 0).Count() > 0)
        System.Threading.Thread.Sleep(1000);
    else
        break;
}

由于我实际使用的查询比本示例中的查询复杂得多,因此以这种方式进行操作会导致难以阅读。我可以使用类似于 RecordsToProcess.Count() > 0 的东西,但是每次迭代都会查询该数据库,而不是使用存储在内存中的初始计数(假设我是正确的)?

注意:我通常不会使用这样的潜在危险 while 循环,但我只需要运行此页面最多 4 或 5 次,然后再也不会。所以我并不太担心。

【问题讨论】:

  • “我的猜测是调用 .Count() 会将该整数存储在内存中”——IIRC 第一次调用RecordsToProcess.Count() 时,会评估RecordsToProcess,并缓存该评估的结果.所以实际上,你是对的。我的解决方案是var count = MyTable.Where().Blah().Blah().Long().Query(),带有适当的换行符等以提高可读性,然后是if (count > 0) { ... }——或者在GetProcessedRecords()方法中隐藏LINQ。
  • 一切最终都取决于您的RecordsToProcess 查询。您应该向我们展示该查询是什么。您可能以已经评估过的方式编写它。
  • 当然我看不到你的其余代码,但你不能等待线程完成吗?或者甚至不在另一个线程中这样做?

标签: c# multithreading linq linq-to-sql deferred-execution


【解决方案1】:

根据 cmets 编辑原始帖子。

我相信部分问题在于编译器如何优化循环。

您的查询中可能存在缓存数据的内容。如果整个查询使用惰性求值,除了在循环中检查Count,每次您在查询上调用Count 时,都会重新求值。在您的第二个示例中,整个查询都在循环中,因此每次都必须重新评估,无论是否实际使用惰性评估。我会检查 MSDN 文档中关于您正在使用的运算符的 remarks。

我还建议在这种情况下使用Any 而不是Count 以提高性能和清晰度。根据您要迭代的内容,Count 通常会遍历一个集合以查看有多少元素,但 Any 更懒惰。在 LINQ to Object 中,Count() 针对实现ICollection 的序列进行了优化,以使用Count 属性,这比迭代快得多,并且Any() 在找到1 个元素后停止检查。正如下面 Erik 所建议的那样,在 LINQ to SQL 中,TOP 1 可能会添加到 SELECT 语句中。我会假设 SQL 有它自己的 COUNT 优化,但我还没有做任何研究。

在适当的时候使用Any() 还可以通过去掉Count() > 0 中的运算符来提高可读性,并且更清楚地表明您对bool 而不是int 感兴趣。

我会这样实现你的方法:

var query = MyTable.Where(i => i.Processed == 0);
while(true) {
    if (!query.Any()) break;
    Thread.Sleep(1000);
}

或者更好的是,如果你可以让它懒惰地执行:

var query = MyTable.Where(i => i.Processed == 0);
while(query.Any()) { Thread.Sleep(1000); }

但是,正如其他答案所述,有关如何构建查询的更多信息会有所帮助。

【讨论】:

  • Count 必须迭代一个集合以查看有多少记录并不总是正确的,it depends on the collection。就 Linq2Sql 和 EF 而言,我非常有信心 Any() 将选择更改为 top 1 然后检查是否有记录(甚至可能只是做 select 1 from.. 以最小化网络交通。
  • 我怀疑编译器会做出这样的优化。那将是一个不安全的改变。 while 循环的条件是 always 评估的,它不会像你建议的那样缓存它。
【解决方案2】:

您没有在每个循环上刷新 RecordsToProcess 变量

While(RecordsToProcess.Count() > 0)
{
  System.Threading.Thread.Sleep(1000);
  RecordsToProcess = MyTable.Where(i => i.Processed == 0);
}

【讨论】:

  • 这与原始帖子中的第二个示例没有任何不同。
猜你喜欢
  • 1970-01-01
  • 2020-11-29
  • 1970-01-01
  • 1970-01-01
  • 2014-08-24
  • 1970-01-01
  • 2011-06-17
  • 2020-04-27
  • 2020-02-08
相关资源
最近更新 更多