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

时间:2019-02-04 09:10:07

标签: c# entity-framework iqueryable

我正在尝试消除在此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并保持正确加载实体层次结构。即相关实体会加载对数据库的不必要的调用。

有人有替代解决方案,可能会促使我解决这个问题。

如有需要,请添加更多详细信息。

2 个答案:

答案 0 :(得分:2)

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

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

因此,如果具有ID 4的TimeDomainDataPoint具有一千个RecordValues,则每个RecordValue将具有一个值为4的外键。将这个值4传输1001次是浪费的,而您只需要一次。

  

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

以下内容会更快:

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());

我不确定这是否更快。如果您的数据库管理系统在内部首先创建一个包含所有联接表的所有列的完整表,然后丢弃未选择的列,则不会有什么不同。但是,如果仅使用实际使用的列创建表,则内部表将较小。这样可能更快。

答案 1 :(得分:0)

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

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)

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