如何在条件语句中使用 Linq 的 .Count() 方法
How to use Linq's .Count() method in a conditional statement
我有一个线程定期检查我的一个 MS SQL 表中是否有任何 "Processed" 位字段设置为 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 次,然后再也不会。所以我不太担心。
根据评论编辑了原文post。
我认为部分问题在于编译器如何优化循环。
您的查询中很可能正在缓存数据。如果整个查询使用惰性求值,除了在循环中检查 Count
之外,每次您在查询上调用 Count
时都会重新求值。在您的第二个示例中,整个查询都在循环中,因此每次都必须重新评估,无论是否实际使用惰性评估。我会检查 MSDN 文档中关于您正在使用的运算符的 remarks
。
为了性能和清晰度,我还建议在这种情况下使用 Any
而不是 Count
。根据您要迭代的内容,Count
通常会遍历一个集合以查看有多少元素,但 Any
比较懒。在 LINQ to Object 中,Count()
针对实现 ICollection
的序列进行了优化,以使用 Count
属性,这比迭代快得多,并且 Any()
停止检查找到 1 个元素后。正如以下 Erik 所建议的,在 LINQ to SQL 中,SELECT
语句中可能添加了类似 TOP 1
的内容。我假设 SQL 有它自己的 COUNT
优化,但我没有做过任何研究。
在适当的时候使用 Any()
也可以通过去掉 Count() > 0
中的运算符来提高可读性,并且更清楚地表达您对 bool
而不是 [=] 感兴趣29=]。
我会这样实现你的方法:
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); }
但是,正如其他答案所提到的,有关查询构造方式的更多信息将会有所帮助。
您没有在每个循环中刷新 RecordsToProcess 变量
While(RecordsToProcess.Count() > 0)
{
System.Threading.Thread.Sleep(1000);
RecordsToProcess = MyTable.Where(i => i.Processed == 0);
}
我有一个线程定期检查我的一个 MS SQL 表中是否有任何 "Processed" 位字段设置为 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 次,然后再也不会。所以我不太担心。
根据评论编辑了原文post。
我认为部分问题在于编译器如何优化循环。
您的查询中很可能正在缓存数据。如果整个查询使用惰性求值,除了在循环中检查 Count
之外,每次您在查询上调用 Count
时都会重新求值。在您的第二个示例中,整个查询都在循环中,因此每次都必须重新评估,无论是否实际使用惰性评估。我会检查 MSDN 文档中关于您正在使用的运算符的 remarks
。
为了性能和清晰度,我还建议在这种情况下使用 Any
而不是 Count
。根据您要迭代的内容,Count
通常会遍历一个集合以查看有多少元素,但 Any
比较懒。在 LINQ to Object 中,Count()
针对实现 ICollection
的序列进行了优化,以使用 Count
属性,这比迭代快得多,并且 Any()
停止检查找到 1 个元素后。正如以下 Erik 所建议的,在 LINQ to SQL 中,SELECT
语句中可能添加了类似 TOP 1
的内容。我假设 SQL 有它自己的 COUNT
优化,但我没有做过任何研究。
在适当的时候使用 Any()
也可以通过去掉 Count() > 0
中的运算符来提高可读性,并且更清楚地表达您对 bool
而不是 [=] 感兴趣29=]。
我会这样实现你的方法:
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); }
但是,正如其他答案所提到的,有关查询构造方式的更多信息将会有所帮助。
您没有在每个循环中刷新 RecordsToProcess 变量
While(RecordsToProcess.Count() > 0)
{
System.Threading.Thread.Sleep(1000);
RecordsToProcess = MyTable.Where(i => i.Processed == 0);
}