来自 SQL 数据库的大量记录的 Linq 查询和 Foreach

Posted

技术标签:

【中文标题】来自 SQL 数据库的大量记录的 Linq 查询和 Foreach【英文标题】:Linq query and Foreach on large number of records from SQL database 【发布时间】:2016-08-12 10:17:08 【问题描述】:

我正在使用实体框架和 Linq。我需要对我的对象的 2 个属性进行查询。

我在数据库中有这个对象(大约 200.000 条记录):

public class DeviceState

    public int ID  get; set; 
    public DateTime TimeStamp  get; set; 
    public string StatusCode  get; set;       
    public int Device_ID  get; set; 

我需要这样查询:

List<DeviceState> listState = systemDB.DeviceStates.Where(s => s.Device_ID == DeviceID).Where(l => l.TimeStamp > startDate).Where(l => l.TimeStamp < endDate).Where(s => s.StatusCode == "xx").ToList();
foreach (DeviceState status in listState)
  
   // here I need to save in an object the status code and the time stamp:
   object.StatusCode= status.StatusCode;
   object.TimeStamp = status.TimeStamp; 

此查询需要很长时间(大约 15 分钟)。我认为这是由于创建了列表。所以我尝试了这个:

foreach (DeviceState status in systemDB.DeviceStates.Where(s => s.Device_ID == DeviceID).Where(l => l.TimeStamp > startDate).Where(l => l.TimeStamp < endDate).Where(s => s.StatusCode == "xx"))
  
   // here I need to save in an object the status code and the time stamp:
   object.StatusCode= status.StatusCode;
   object.TimeStamp = status.TimeStamp; 

创建列表的速度要快得多。但是由于 foreach 循环,我仍然存在性能问题。每个元素需要 5ms。

我需要找到一个需要几秒钟来执行的解决方案。

【问题讨论】:

【参考方案1】:

您可以做这些事情来帮助生成查询。

    只返回您需要的内容。现在您要返回所有内容,但您只使用过 TimeStamp,所以只需返回即可。您在下方设置了 StatusCode,但是您已经在 Where 子句中对其进行了过滤,因此您知道所有返回的项目都有一个“xx”的StatusCode,所以也不需要检索它。通过网络返回的数据更少,将数据映射到对象所需的周期更少,数据占用的内存也更少。 您应该查看 EF 生成的查询。您可以使用 sql profiler 工具来执行此操作(Sql Server 有一个称为 Sql Profiler)。然后查看查询计划,看看是否有任何可以使您受益的内容,例如添加缺少的索引。此分析应在数据库服务器上完成,而不是在 C# 中。 合并 where 子句,因为它更易于阅读。

代码

// list of timestamps from database
var timeStamps = systemDB.DeviceStates.Where(s => s.Device_ID == DeviceID && 
                                                  s.TimeStamp > startDate && 
                                                  s.TimeStamp < endDate &&
                                                  s.StatusCode == "xx")
                                      .Select(x => x.TimeStamp).ToList();

如果你仍然想要你的状态码,因为你删除了过滤器,你可以这样做

var timeStamps = systemDB.DeviceStates.Where(s => s.Device_ID == DeviceID &&
                                                  s.TimeStamp > startDate && 
                                                  s.TimeStamp < endDate && 
                                                  s.StatusCode == "xx")
                                      .Select(x => new x.TimeStamp, x.StatusCode)
                                      .ToList();

【讨论】:

【参考方案2】:

你可以做一个瘦的where子句

List<DeviceState> listState = systemDB.DeviceStates.Where(
                                  l => l.Device_ID == DeviceID 
                               && l.TimeStamp > startDate
                               && l.TimeStamp < endDate 
                               && l.StatusCode == "xx" 
                               ).ToList();

然后使用for而不是foreach,因为foreach在大数据量的情况下会变慢

for (int i = 0; i< listState.Count; i++ )
  
   object.StatusCode= listState[i].StatusCode;
   object.TimeStamp = listState[i].TimeStamp; 

【讨论】:

你是通过 where 子句多次过滤,所以它多次做同样的事情肯定有性能问题 数据量也是个大问题,在某些时候您必须将其转换为列表、json 或类似的东西,所以在那个地方需要更多时间来处理大量数据 是的,它似乎更快。 foreach 会不会导致列表中大量数据出现问题? 是的 foreach 对大量数据的性能也有影响 linq,在这种情况下 for 比其他更快,我正在用 for 更新答案 您可能还需要考虑并行 for 以利用多线程(前提是循环内操作是线程安全的):msdn.microsoft.com/en-us/library/…【参考方案3】:

Igor 的回答已经表明了无论如何你都应该做的最明显的改进。

现在,如果您处理大量数据,那么您可能希望在与System.Threading.Tasks.Parallel.ForEach 并行的多个线程中进行处理。代码(有其他改进):

var deviceStateTime = systemDB.DeviceStates.Where(s => s.Device_ID == DeviceID
                        && s.TimeStamp > startDate)
                        && s.TimeStamp < endDate
                        && s.StatusCode == "xx");
Parallel.ForEach(deviceStateTime, (time) =>
                 
                    object.StatusCode = "xx";
                    object.Timestamp = time;
                 );

请注意,我没有测试它(我是凭记忆写下来的),所以我可能在某个地方打错了。

【讨论】:

以上是关于来自 SQL 数据库的大量记录的 Linq 查询和 Foreach的主要内容,如果未能解决你的问题,请参考以下文章

使用LINQ2SQL插入大量记录

来自 SQL 查询的 Linq Pivot

大量数据的linq查询延迟结果

LINQ to SQL:对来自订购系统的多个表的报告的聚合数据进行复杂查询

如何在 LINQ SQL C# 中返回动态周数?

如何在查询中加入 MS-SQL 和 MySQL 表?