我有一个有点复杂的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
?
答案 0 :(得分:2)
以下是我的评论,以及我做出的一些假设
ToList()
的情况下试一试
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
中没有匹配的EquipmentLive
,FirstOrDefault
将返回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()
。