改进多对多关系的 LINQ 查询
Posted
技术标签:
【中文标题】改进多对多关系的 LINQ 查询【英文标题】:Improving LINQ query for many-to-many relation 【发布时间】:2021-01-30 16:56:42 【问题描述】:我有一个具有以下架构的数据库:
现在,我正在尝试提取域的所有登录页面,并按与某个组匹配的第一个 UrlFilter 的 FilterType 对它们进行排序。这是我迄今为止提出的 LINQ:
var baseQuery = DbSet.AsNoTracking()
.Where(e => EF.Functions.Contains(EF.Property<string>(e, "Url"), $"\"searchTerm*\""))
.Where(e => e.DomainLandingPages.Select(lp => lp.DomainId).Contains(domainId));
var count = baseQuery.Count();
var page = baseQuery
.Select(e => new
LandingPage = e,
UrlFilter = e.LandingPageUrlFilters.FirstOrDefault(f => f.UrlFilter.GroupId == groupId)
)
.Select(e => new
e.LandingPage,
FilterType = e.UrlFilter == null ? UrlFilterType.NotCovered : e.UrlFilter.UrlFilter.UrlFilterType
)
.OrderBy(e => e.FilterType)
.Skip(10).Take(75).ToList();
现在,虽然这在技术上可行,但执行时间从 10 到 30 秒不等,速度很慢,这对于用例来说还不够好。 LINQ 被转换为以下 SQL:
SELECT [l1].[Id], [l1].[LastUpdated], [l1].[Url], CASE
WHEN (
SELECT TOP(1) [l].[LandingPageId]
FROM [LandingPageUrlFilters] AS [l]
INNER JOIN [UrlFilters] AS [u] ON [l].[UrlFilterId] = [u].[Id]
WHERE ([l1].[Id] = [l].[LandingPageId]) AND ([u].[GroupId] = @__groupId_3)) IS NULL THEN 4
ELSE (
SELECT TOP(1) [u0].[UrlFilterType]
FROM [LandingPageUrlFilters] AS [l0]
INNER JOIN [UrlFilters] AS [u0] ON [l0].[UrlFilterId] = [u0].[Id]
WHERE ([l1].[Id] = [l0].[LandingPageId]) AND ([u0].[GroupId] = @__groupId_3))
END AS [FilterType]
FROM [LandingPages] AS [l1]
WHERE CONTAINS([l1].[Url], @__Format_1) AND @__domainId_2 IN (
SELECT [d].[DomainId]
FROM [DomainLandingPages] AS [d]
WHERE [l1].[Id] = [d].[LandingPageId]
)
ORDER BY CASE
WHEN (
SELECT TOP(1) [l2].[LandingPageId]
FROM [LandingPageUrlFilters] AS [l2]
INNER JOIN [UrlFilters] AS [u1] ON [l2].[UrlFilterId] = [u1].[Id]
WHERE ([l1].[Id] = [l2].[LandingPageId]) AND ([u1].[GroupId] = @__groupId_3)) IS NULL THEN 4
ELSE (
SELECT TOP(1) [u2].[UrlFilterType]
FROM [LandingPageUrlFilters] AS [l3]
INNER JOIN [UrlFilters] AS [u2] ON [l3].[UrlFilterId] = [u2].[Id]
WHERE ([l1].[Id] = [l3].[LandingPageId]) AND ([u2].[GroupId] = @__groupId_3))
END
OFFSET @__p_4 ROWS FETCH NEXT @__p_5 ROWS ONLY
现在我的问题是,我怎样才能提高它的执行时间?通过 SQL 或 LINQ
编辑:所以我一直在修改一些原始 SQL,这就是我想出的:
with matched_urls as (
select l.id, min(f.urlfiltertype) as Filter
from landingpages l
join landingpageurlfilters lpf on lpf.landingpageid = l.id
join urlfilters f on lpf.urlfilterid = f.id
where f.groupid = @groupId
and contains(Url, '"barz*"')
group by l.id
) select l.id, 5 as Filter
from landingpages l
where @domainId in (
select domainid
from domainlandingpages dlp
where l.id = dlp.landingpageid
) and l.id not in (select id from matched_urls ) and contains(Url, '"barz*"')
union select * from matched_urls
order by Filter
offset 10 rows fetch next 30 rows only
这执行得还不错,将执行时间缩短到约 5 秒。由于这将用于表搜索,但我想进一步降低它。有什么方法可以改进这个 SQL 吗?
【问题讨论】:
为此使用原始 SQL。容易得多。并非所有事情都必须通过 LINQ。 @jeroenh 您能否大致介绍一下查询的外观?我对 SQL 不太熟悉,尤其是优化。 你的basequery
够好吗?尝试只运行它。实际上,我更愿意从 Join
表开始查询 - DbSet<DomainLandingPages>.AsNoTracking().Where(x => x.domainId == domainId)
... - 它生成 Inner Join
语句而不是 In
【参考方案1】:
看看生成的 SQL 是对的。一般来说,我建议学习 SQL,编写一个执行 SQL 查询,然后按照自己的方式工作(使用存储过程或原始 SQL,或者使用相同的理念设计您的 LINQ 查询。
我怀疑这会更好(未测试):
var page = (
from e in baseQuery
let urlFilter = e.LandingPageUrlFilters.OrderBy(f => f.UrlFilterType).FirstOrDefault(f => f.UrlFilter.GroupId == groupId)
let filterType = urlFilter == null ? UrlFilterType.NotCovered : e.UrlFilter.UrlFilter.UrlFilterType
select new
LandingPage = e,
FilterType = filterType
).Skip(10).Take(75).ToList();
【讨论】:
不幸的是,这相当于大致相同的 SQL,因为我仍然必须在 Skip 和 Take 之前按 FilterType 排序。 我已经编辑了帖子以包含一些原始 SQL。你能看看我是否在正确的轨道上?【参考方案2】:提高执行时间的方法之一是在 SSMS (SQL Server Management Studio) 中查看执行计划。
查看执行计划后可以设计一些索引,或者如果你没有这方面的经验,你可以看看SSMS是否推荐了一些索引。
接下来尝试创建索引并再次执行查询,看看执行时间是否有所改善。
注意:这只是缩短执行时间的众多可能方法之一...
【讨论】:
感谢马丁的回复。据我在执行计划中看到的,一切都是索引搜索(集群和非集群)和连接。没有建议的索引。我认为生成的查询可能太无效了。以上是关于改进多对多关系的 LINQ 查询的主要内容,如果未能解决你的问题,请参考以下文章
使用 linq/Entity Framework 查询多对多关系。代码优先