展平复杂LINQ to SQL

时间:2016-12-16 07:16:08

标签: c# linq

我有一个有点复杂的LINQ to SQL查询,我正在尝试优化(不,不是过早,事情很慢),这有点像这样;

IQueryable<SearchListItem> query = DbContext.EquipmentLives
    .Where(...)
    .Select(e => new SearchListItem {
        EquipmentStatusId = e.EquipmentStatuses.FirstOrDefault(s => s.DateTo == null).Id,
        StatusStartDate = e.EquipmentStatuses.FirstOrDefault(s => s.DateTo == null).DateFrom,
        ...
    });

where子句并不重要,他们不会过滤EquipmentStatuses,如果有人认为他们是必需的,我们很乐意加入。

这是在一大堆表上并返回一个相当详细的对象,有更多对EquipmentStatuses的引用,但我相信你明白了。问题是很明显有两个子查询,我确信(在其他一些事情中)并不理想,特别是因为它们每次都是完全相同的子查询。

是否有可能将其扁平化?也许更容易对数据库进行一些较小的查询并在SearchListItem循环中创建foreach

3 个答案:

答案 0 :(得分:2)

以下是我的评论,以及我做出的一些假设

  • 可能看起来很可怕,但在GroupBy()
  • 之前有和没有ToList()的情况下试一试
  • 如果你有LinqPad,请检查生成的SQL,查询次数,或者只是插入SQL Server Profiler
  • 使用LinqPad,您甚至可以使用Stopwatch来精确测量事物

享受;)

var query = DbContext.EquipmentLives
    .AsNoTracking() // Notice this!!!
    .Where(...)

    // WARNING: SelectMany is an INNER JOIN
    // You won't get EquipmentLive records that don't have EquipmentStatuses
    // But your original code would break if such a case existed
    .SelectMany(e => e.EquipmentStatuses, (live, status) => new
    {
        EquipmentLiveId = live.Id, // We'll need this one for grouping
        EquipmentStatusId = status.Id,
        EquipmentStatusDateTo = status.DateTo,
        StatusStartDate = status.DateFrom
        //...
    })

    // WARNING: Again, you won't get EquipmentLive records for which none of their EquipmentStatuses have a DateTo == null
    // But your original code would break if such a case existed
    .Where(x => x.EquipmentStatusDateTo == null)

    // Now You can do a ToList() before the following GroupBy(). It depends on a lot of factors...
    // If you only expect one or two EquipmentStatus.DateTo == null per EquipmentLive, doing ToList() before GroupBy may give you a performance boost
    // Why? GroupBy sometimes confuses the EF SQL generator and the SQL Optimizer
    .GroupBy(x => x.EquipmentLiveId, x => new SearchListItem
    {
        EquipmentLiveId = x.EquipmentLiveId, // You may or may not need this?
        EquipmentStatusId = x.EquipmentStatusId,
        StatusStartDate = x.StatusStartDate,
        //...
    })

    // Now you have one group of SearchListItem per EquipmentLive
    // Each group has a list of EquipmenStatuses with DateTo == null
    // Just select the first one (you could do g.OrderBy... as well)
    .Select(g => g.FirstOrDefault())

    // Materialize
    .ToList();

答案 1 :(得分:1)

您不需要重复FirstOrDefault。您可以添加一个中间Select来选择它,然后重复使用它:

IQueryable<SearchListItem> query = DbContext.EquipmentLives
    .Where(...)
    .Select(e => e.EquipmentStatuses.FirstOrDefault(s => s.DateTo == null))
    .Select(s => new SearchListItem {
        EquipmentStatusId = s.Id,
        StatusStartDate = s.DateFrom,
        ...
    });

在查询语法中(我发现它更具可读性),它看起来像这样:

var query =
    from e in DbContext.EquipmentLives
    where ...
    let s = e.EquipmentStatuses.FirstOrDefault(s => s.DateTo == null)
    select new SearchListItem {
        EquipmentStatusId = s.Id,
        StatusStartDate = s.DateFrom,
        ...
    });

但您的查询中还有另一个问题。如果EquipmentStatus中没有匹配的EquipmentLiveFirstOrDefault将返回null,这将导致最后一次选择中出现异常。因此,您可能需要额外的Where

IQueryable<SearchListItem> query = DbContext.EquipmentLives
    .Where(...)
    .Select(e => e.EquipmentStatuses.FirstOrDefault(s => s.DateTo == null))
    .Where(s => s != null)
    .Select(s => new SearchListItem {
        EquipmentStatusId = s.Id,
        StatusStartDate = s.DateFrom,
        ...
    });

var query =
    from e in DbContext.EquipmentLives
    where ...
    let s = e.EquipmentStatuses.FirstOrDefault(s => s.DateTo == null)
    where s != null
    select new SearchListItem {
        EquipmentStatusId = s.Id,
        StatusStartDate = s.DateFrom,
        ...
    });

答案 2 :(得分:0)

假设您在调用FirstOrDefault(s => s.DateTo == null)后没有测试null,我认为:

  • 每个设备的始终显示DateTo == null 状态或
  • 您需要查看仅具有此类状态的设备

为此,您需要将EquipmentLives加入EquipmentStatuses以避免子查询:

var query = DbContext.EquipmentLives
    .Where(l => true)
    .Join(DbContext.EquipmentStatuses.Where(s => s.DateTo == null),
        eq => eq.Id,
        status => status.EquipmentId,
        (eq, status) => new SelectListItem
        {
            EquipmentStatusId = status.Id,
            StatusStartDate = status.DateFrom
        });

但是,如果您确实希望使用left join执行DbContext.EquipmentStatuses.Where(s => s.DateTo == null)替换DbContext.EquipmentStatuses.Where(s => s.DateTo == null).DefaultIfEmpty()