C# 将复杂LINQ平坦化为SQL

C# 将复杂LINQ平坦化为SQL,c#,linq,C#,Linq,我有一个有点复杂的LINQ到SQL查询,我正在尝试优化它(不,不是过早地,事情很慢),它有点像这样 IQueryable<SearchListItem> query = DbContext.EquipmentLives .Where(...) .Select(e => new SearchListItem { EquipmentStatusId = e.EquipmentStatuses.FirstOrDefault(s => s.Date

我有一个有点复杂的LINQ到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,
        ...
    });
IQueryable query=DbContext.equipmentLifes
.其中(…)
.选择(e=>new SearchListItem{
EquipmentStatusId=e.EquipmentStatuses.FirstOrDefault(s=>s.DateTo==null).Id,
StatusStartDate=e.EquipmentStatuses.FirstOrDefault(s=>s.DateTo==null).DateFrom,
...
});
where子句并不重要,它们不过滤
设备状态
,如果有人认为需要,它们很乐意包含在内

这是在相当大的一组表上,并返回一个相当详细的对象,有更多的引用到
equipmentstatus
,但我相信您已经明白了。问题是很明显有两个子查询,我确信(除其他外)并不理想,特别是因为它们每次都是完全相同的子查询


有没有可能把这件事平淡一点?也许对数据库进行一些较小的查询并在
foreach
循环中创建
SearchListItem
更容易些?

以下是我对您的评论的看法,以及我所做的一些假设

  • 它可能看起来很吓人,但是试一下,在GroupBy()之前有没有
    ToList()
  • 如果您有LinqPad,请检查生成的SQL和查询数,或者只插入SQL Server探查器
  • 使用LinqPad,您甚至可以放置一个
    秒表
    ,以精确测量事物
享受;)


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

  • 对于每个设备,总是有一个状态为
    DateTo==null
    ,或者
  • 您只需要查看具有这种状态的设备
为此,您需要将
equipmentlifes
equipmentstatus
连接起来,以避免子查询:

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

但是,如果您确实要执行
左连接
替换
DbContext.EquipmentStatuses.Where(s=>s.DateTo==null)
DbContext.EquipmentStatuses.Where(s=>s.DateTo==null.DefaultIfEmpty()
,则无需重复
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,
        ...
    });
不过,您的查询中还有另一个问题。如果
EquipmentLive
中没有匹配的
EquipmentStatus
FirstOrDefault
将返回
null
,这将导致最后一次选择中出现异常。因此,您可能需要一个附加的
,其中

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,
        ...
    });

正如您所说,较小的查询将显著提高您的性能。您可以通过SearchListItem中需要的字段获取这些设备状态的列表,然后使用foreach。您可以填写SearchListItem。如果您有一个复杂的LINQ查询,这可能意味着您有一个设计问题。要么缺少映射,要么查询实际上应该是数据库中的一个视图。这是个好主意,但假设
EquipmentStatus
对象是我可以用来填充
SearchListItem
对象的基础。不幸的是,我需要将基础保留为
EquipmentLifes
,因为
SearchListItem
的许多其他属性需要
EquipmentLifes
对象。这没有问题。您仍然可以使用
e
访问它。尤其是在查询语法中,访问e非常简单。只需在select.Correct中使用它,但我不想将方法语法重写为查询语法,在您提供的方法语法示例中,
equipmentlifes
不可访问。我认为您需要将其设置为一个
选择多个
,就像接受的答案一样。
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,
        ...
    });
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,
        ...
    });