改进多对多关系的LINQ查询

问题描述

我有一个具有以下架构的数据库

enter image description here

现在,我正尝试提取某个域的所有登录页面,并按与某个组匹配的第一个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。通常,我建议您学习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();
    
,

缩短执行时间的一种方法是查看SSMS(SQL Server Management Studio)中的执行计划。

查看执行计划后,您可以设计一些索引,或者如果您没有相关经验,则可以查看SSMS是否建议一些索引。

接下来尝试创建索引并再次执行查询,看看执行时间是否得到了改善。

注意:这只是缩短执行时间的众多可能方式之一...