【问题标题】:Linq to SQL/Entities: Greatest N-Per group problem/performance increaseLinq to SQL/Entities:最大的 N-Per 组问题/性能提升
【发布时间】:2021-07-20 06:34:00
【问题描述】:

好吧,所以我也遇到了我认为是 Greatest-N-Per 问题,而这个问题在我认为 Linq 还没有很好地解决之前已经得到了回答。我有一个包含几百万个条目的表,因此查询需要很多时间。我希望这些查询花费不到一秒,而目前它们花费大约 10 秒才能到达无穷大。

var query = 
        from MD in _context.MeasureDevice
        where MD.DbdistributorMap.DbcustomerId == 6 // Filter the devices based on customer
        select new
        {
            DbMeasureDeviceId = MD.DbMeasureDeviceId,
            // includes measurements and alarms which have 1-m and 1-m relations
            Measure = _context.Measures.Include(e=> e.MeasureAlarms)
                .FirstOrDefault(e => e.DbMeasureDeviceId == MD.DbMeasureDeviceId && e.MeasureTimeStamp == _context.Measures
                    .Where(x => x.DbMeasureDeviceId == MD.DbMeasureDeviceId)
                    .Max(e=> e.MeasureTimeStamp)),
            Address = MD.Dbaddress // includes address 1-1 relation
        };

在此查询中,我从 4 个不同的表中选择数据。首先是 MeasureDevice 表,它是我之后的主要实体。其次,我想要测量表中的最新测量值,如果存在的话,它还应该包括来自另一个表的警报。最后我需要设备的地址,它位于它自己的表中。

有几千台设备,但它们之间有几千个测量值,在测量表中达到几百万行。

我想知道是否有人知道如何使用 EF5 提高 Linq 查询的性能,或者有任何更好的方法来解决 Greatest-N-Per 问题。我使用 Microsoft SQL Server Manager 分析了查询,大部分时间都花在了获取测量值上。

根据要求生成查询:

SELECT [w].[DBMeasureDeviceID], [t].[DBMeasureID], [t].[AlarmDBAlarmID], [t].[batteryValue], [t].[DBMeasureDeviceID], [t].[MeasureTimeStamp], [t].[Stand], [t].[Temperature], [t].[c], [a].[DBAddressID], [a].[AmountAvtalenummere],
[a].[DBOwnerID], [a].[Gate], [a].[HouseCharacter], [a].[HouseNumber], [a].[Latitude], [a].[Longitude], [d].[DBDistributorMapID], [m1].[DBMeasureID], [m1].[DBAlarmID], [m1].[AlarmDBAlarmID], [m1].[MeasureDBMeasureID]
      FROM [MeasureDevice] AS [w]
      INNER JOIN [DistribrutorMap] AS [d] ON [w].[DBDistributorMapID] = [d].[DBDistributorMapID]
      LEFT JOIN [Address] AS [a] ON [w].[DBAddressID] = [a].[DBAddressID]
      OUTER APPLY (
          SELECT TOP(1) [m].[DBMeasureID], [m].[AlarmDBAlarmID], [m].[batteryValue], [m].[DBMeasureDeviceID], [m].[MeasureTimeStamp], [m].[Stand], [m].[Temperature], 1 AS [c]
          FROM [Measure] AS [m]
          WHERE ([m].[MeasureTimeStamp] = (
              SELECT MAX([m0].[MeasureTimeStamp])
              FROM [Measure] AS [m0]
              WHERE [m0].[DBMeasureDeviceID] = [w].[DBMeasureDeviceID])) AND ([w].[DBMeasureDeviceID] = [m].[DBMeasureDeviceID])
      ) AS [t]
      LEFT JOIN [MeasureAlarm] AS [m1] ON [t].[DBMeasureID] = [m1].[MeasureDBMeasureID]
      WHERE [d].[DBCustomerID] = 6
      ORDER BY [w].[DBMeasureDeviceID], [d].[DBDistributorMapID], [a].[DBAddressID], [t].[DBMeasureID], [m1].[DBMeasureID], [m1].[DBAlarmID]

实体关系

【问题讨论】:

  • 恕我直言,使用导航属性构建这种类型的查询更容易。 _context.Measure.Where(m => m.MeasureTimeStamp == m.Device.Measure.Max(m2 => m2.MeasureTimeStamp))。但是性能会更好吗?
  • @mjwills 添加到编辑中
  • @JeremyLakeman 似乎对性能几乎没有影响。运行大约需要 10 秒
  • 一些备注: 1. 更常见的顺序是MeasureTimeStamp 降序取第一条记录。 2.查看类模型(+导航属性)会有所帮助。 3. 您显示的查询看起来不像 EF6 会生成的内容。这是正确的版本吗?
  • @GertArnold 使用的是 EF5 而不是 EF6,是一个错字。在编辑中修复。将尝试生成描述关系的图表

标签: c# .net entity-framework linq


【解决方案1】:

您已经定义了导航属性,因此 MeasureDevice 应该引用它的 Measures:

var query = _context.MeasureDevice
    .Include(md => md.Measures.Select(m => m.MeasureAlarms)
    .Where(md => md.DbDistributorMap.DbCustomerId == 6)
    .Select(md => new 
    {
        DbMeasureDeviceId = md.DbMeasureDeviceId,
        Measure = md.Measures.OrderByDescending(m => m.MeasureTimeStamp).FirstOrDefault(),
        Address = md.Address
    });

此处可能存在的问题是将 MeasureAlarms 包含在所需的 Measure 中。 AFAIK 你不能把.Include() 放在.Select() 中(我们可能已经尝试过Measure = md.Measures.Include(m => m.MeasureAlarms)...

警告:自从我使用 EF 5 以来已经有一段时间了(除非您指的是 EF Core 5)如果您在项目中使用(非常旧的)EF5,我建议您考虑升级到 EF6 EF6 确实为 EF5 带来了许多性能和功能改进。如果您改用 EF Core 5,则上面的 Include 语句会略有不同:

.Include(md => md.Measures).ThenInclude(m => m.MeasureAlarms)

我的首选建议是使用 Projection 来精确选择我们需要的数据,而不是返回实体。这样我们就不需要担心急切或延迟加载。如果有我们需要的 Measure 和 MeasureAlarms 的详细信息:

var query = _context.MeasureDevice
    .Where(md => md.DbDistributorMap.DbCustomerId == 6)
    .Select(md => new 
    {
        md.DbMeasureDeviceId,
        Measure = md.Measures
            .Select(m => new 
            {
                m.MeasureId,
                m.MeasureTimestamp,

                // any additional needed fields from Measure

                Address = m.Address.Select(a => new 
                {
                   // Assuming Address is an entity, any needed fields from Address.
                }),
                Alarms = m.MeasureAlarms.Select(ma => new 
                {
                    ma.MeasureAlarmId,
                    ma.Label // etc. Whatever fields needed from Alarm...
                }).ToList()
            }).OrderByDescending(m => m.MeasureTimestamp)
            .FirstOrDefault()
    });

此示例选择匿名类型,或者您可以定义 DTO/ViewModel,并可以利用 Automapper 之类的库将字段映射到相应的实体值,以将所有内容替换为 ProjectTo<LatestMeasureSummaryDTO> 之类的东西,其中 Automapper 具有映射 MeasureDevice 的规则解析最新的Measure并提取所需的字段。

投影的好处是处理其他复杂/笨拙的急切加载,仅使用消费者需要的字段构建优化的有效负载,以及在新关系不会意外引入延迟加载性能问题的不断变化的系统中的弹性。例如,如果 Measure 当前只有 MeasureAlarm 来预加载,那么一切正常。但是,如果将新关系添加到 Measure 或 MeasureAlarm 并且包含这些实体的有效负载已序列化,则序列化调用现在将“触发”新关系上的延迟加载,除非您重新访问检索这些实体的所有查询并添加更多急切加载,或者开始担心完全禁用延迟加载。只有当它们需要返回的字段实际需要更改时,投影才会保持不变。

除此之外,您可以调查的下一件事是通过分析器(例如在 SQL Management Studio 中)运行生成的查询,以返回执行计划并确定查询是否可以从索引更改中受益。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2011-03-10
    • 1970-01-01
    • 2019-02-04
    • 2012-01-02
    • 2010-11-02
    • 2017-03-31
    • 2022-01-08
    相关资源
    最近更新 更多