将 Include() 调用替换为 Select()

Replacing Include() calls to Select()

我正在尝试消除此 IQueryable 定义中对 Include() 调用的使用:

return ctx.timeDomainDataPoints.AsNoTracking()
   .Include(dp => dp.timeData)
   .Include(dp => dp.RecordValues.Select(rv => rv.RecordKind).Select(rk => rk.RecordAlias).Select(fma => fma.RecordAliasGroup))
   .Include(dp => dp.RecordValues.Select(rv => rv.RecordKind).Select(rk => rk.RecordAlias).Select(fma => fma.RecordAliasUnit))
   .Where(dp => dp.RecordValues.Any(rv => rv.RecordKind.RecordAlias != null))
   .Where(dp => dp.Source == 235235)
   .Where(dp => dp.timeData.time >= start && cd.timeData.time <= end)
   .OrderByDescending(cd => cd.timeData.time);

我一直遇到数据库问题,其中 运行 时间太长,主要原因是 Include() 调用正在提取所有内容。 从由此生成的结果 SQL 查询返回的 table 中可以明显看出这一点,显示返回了许多不必要的信息。 我猜你学到的其中一件事。 数据库有大量数据点,其中有许多记录值。 每个 Recorded 值都映射到一个 Record Kind,它可能有一个 Record Alias。

我曾尝试创建一个 Select() 作为替代方法,但我只是想不出如何构建正确的 Select 并保持正确加载实体层次结构。 IE。相关实体加载了对数据库的不必要调用。

有没有人有替代解决方案可以帮助我解决这个问题。

如果需要,我会添加更多细节。

你的问题是你的 query.In 中的层次结构连接为了减少这个问题创建其他查询以从关系 table 中获取结果,如下所示:

var items= ctx.timeDomainDataPoints.AsNoTracking().Include(dp =>dp.timeData).Include(dp => dp.RecordValues);
var ids=items.selectMany(item=>item.RecordValues).Select(i=>i.Id);

以及对数据库的其他请求:

  var otherItems= ctx.RecordAlias.AsNoTracking().select(dp =>dp.RecordAlias).where(s=>ids.Contains(s.RecordKindId)).selectMany(s=>s.RecordAliasGroup)

对于这种方法,您的查询没有内部联接。

你是对的。数据库查询中速度较慢的部分之一是将 selected 数据从 DBMS 传输到本地进程。因此,限制这一点是明智的。

每个TimeDomainDataPoint都有一个主键。此 TimeDomainDataPoint 的所有 RecordValues 都有一个外键 TimeDomainDataPointId,其值等于此主键。

所以如果 Id 为 4 的 TimeDomainDataPoint 有一千个 RecordValue,那么每个 RecordValue 都会有一个值为 4 的外键。将这个值 4 传输 1001 次是一种浪费,而你只需要它一次。

When querying data, always use Select and select only the properties you actually plan to use. Only use Include if you plan to update the fetched included items.

下面会快很多:

var result = dbContext.timeDomainDataPoints
    // first limit the datapoints you want to select
    .Where(datapoint => d.RecordValues.Any(rv => rv.RecordKind.RecordAlias != null))
    .Where(datapoint => datapoint.Source == 235235)
    .Where(datapoint => datapoint.timeData.time >= start
                     && datapoint.timeData.time <= end)
    .OrderByDescending(datapoint => datapoint.timeData.time)

    // then select only the properties you actually plan to use
    Select(dataPoint => new
    {
        Id = dataPoint.Id,
        RecordValues = dataPoint.RecordValues
            .Where(recordValues => ...)           // if you don't want all RecordValues
            .Select(recordValue => new
            {
                // again: select only the properties you actually plan to use:
                Id = recordValue.Id,
                // not needed, you know the value: DataPointId = recordValue.DataPointId,
                RecordKinds = recordValues.RecordKinds
                    .Where(recordKind => ...) // if you don't want all recordKinds
                    .Select(recordKind => new
                    {
                         ... // only the properties you really need!
                    })
                    .ToList(),
                 ...
            })
            .ToList(),

        TimeData = dataPoint.TimeData.Select(...),
        ...
    });

可能的改进

部分:

.Where(datapoint => d.RecordValues.Any(rv => rv.RecordKind.RecordAlias != null))

用于仅获取具有非空 RecordAlias 的 recordValues 的数据点。如果您仍然 selecting RecordAlias,请考虑在 select:

之后执行此操作
.Select(...)
.Where(dataPoint => dataPoint
       .Where(dataPoint.RecordValues.RecordKind.RecordAlias != null)
       .Any());

我不确定这是否更快。如果您的数据库管理系统首先在内部创建一个完整的 table,其中包含所有连接的 table 的所有列,然后丢弃未 selected 的列,那么它不会生成区别。但是,如果它仅使用实际使用的列创建 table,则内部 table 会更小。这可能会更快。