【问题标题】:Replacing Include() calls to Select()将 Include() 调用替换为 Select()
【发布时间】:2019-02-04 09:10:06
【问题描述】:

我试图在此 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 查询返回的表中很明显,显示了许多不必要的信息被返回。 我猜你学到的一件事。 数据库有大量数据点,其中有许多记录值。 每个 Recorded 值都映射到可能具有 Record Alias 的 Record Kind。

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

有没有人有替代解决方案可以让我开始解决这个问题。

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

【问题讨论】:

  • 您是否包含这些实体是因为您想使用where 过滤它们,还是因为您稍后需要它们的数据?无需include 对象即可在同一查询中使用where
  • 事实上@EzLo,我最初的理解(天真地承认)是,即使 wheres 正在过滤信息,我仍然需要获取信息。我在加载信息时添加了 Includes,而不是通过查询向 DB 发送垃圾邮件。我的看法是,每个 timeDomainDataPoint 都与 timeData (1 到 1)连接,然后加入 RecordKinds 映射到 RecordAliases (1 到可选 1)的 RecordValues (多到 1)。所有这些连接信息也都被使用。

标签: c# entity-framework iqueryable


【解决方案1】:

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

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

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

查询数据时,请始终使用选择并仅选择您实际计划使用的属性。如果您计划更新获取的包含项,请仅使用 Include

以下会更快:

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 的数据点。如果您仍然选择 RecordAlias,请考虑在选择后的位置:

.Select(...)
.Where(dataPoint => dataPoint
       .Where(dataPoint.RecordValues.RecordKind.RecordAlias != null)
       .Any());

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

【讨论】:

  • MS SQL Server 是 DBMS。
  • 所以我实现了另一种方式,如@Harald_Coppoolse,这确实将响应时间减少到大约 1 1/2 分钟,但有趣的是,无论时间段如何,这个响应时间都没有太大变化我在子句中指定是一小时还是 24 小时(这会改变返回对象的数量)。这归结为 RecordValue PK 上的键查找和聚集索引扫描的大量成本。但如果我不解决这个瓶颈,那是另一个问题。
【解决方案2】:

您的问题是查询中的层次结构连接。为了减少此问题,请创建其他查询以从关系表中获取结果,如下所示:

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

以及对 db 的其他请求:

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

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

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-05-05
    相关资源
    最近更新 更多