改进多对多关系的 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&lt;DomainLandingPages&gt;.AsNoTracking().Where(x =&gt; 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 查询多对多关系。代码优先

ASP.NET MVC 4、EF 和 LINQ:为多对多关系创建特定的 LINQ 查询

使用 SQL 和 Linq 的多对多关系(实体框架/实体)

LINQ to Entities 查询多对多表

用于连接多对多表的 Linq 查询

使用非关联数据 (LINQ) 从多对多关系中获取值