【问题标题】:Linq query and Foreach on large number of records from SQL database来自 SQL 数据库的大量记录的 Linq 查询和 Foreach
【发布时间】: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。

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

【问题讨论】:

    标签: c# entity-framework linq


    【解决方案1】:

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

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

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

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2015-11-20
        • 1970-01-01
        • 2021-09-27
        • 2011-05-07
        • 2019-08-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多