【问题标题】:Modifying an Entity Framework expression when tables have been remapped重新映射表时修改实体框架表达式
【发布时间】:2019-06-24 02:35:52
【问题描述】:

现在我有一个问题,表的 id 已被删除。

首先,我在下面进行了此查询,其中实体(表)“RecordsProduct”具有映射到被告表的“DefendnatId”。很好!

            records = records
                .Include(r => r.Employer)
                .Include(r => r.Contractor)
                .Include(r => r.RecordProducts)
                .ThenInclude(rp => rp.Defendant)
                .Where(r => EF.Functions.Like(r.Employer.DefendantCode, "%" + input.DefendantCode + "%")
                    || EF.Functions.Like(r.Contractor.DefendantCode, "%" + input.DefendantCode + "%")
                    || r.RecordProducts.Any(rp => EF.Functions.Like(rp.Defendant.DefendantCode, "%" + input.DefendantCode + "%") && rp.IsActive == true));

DefendantId 已从表中删除,并替换为来自名为 ProductDefendant 的转换表中的 DefendantProductId,如下所示

ProductDefendant表:

  • 被告产品编号
  • 被告人身份
  • 产品编号

所以我不能再这样做了:

rp.Defendant.DefendantCode

现在我必须这样做

rp.ProductDefendant.Defendant.DefendantCode

现在我的查询被炸毁了!我可以做些什么来修改它以使其更快?或者改变联接的工作方式?

           records = records
                .Include(r => r.Employer)
                .Include(r => r.Contractor)
                .Include(r => r.RecordProducts)
                .ThenInclude(rp => rp.ProductDefendant.Defendant)
                .Where(r => EF.Functions.Like(r.Employer.DefendantCode, "%" + input.DefendantCode + "%")
                    || EF.Functions.Like(r.Contractor.DefendantCode, "%" + input.DefendantCode + "%")
                    || r.RecordProducts.Any(rp => EF.Functions.Like(rp.ProductDefendant.Defendant.DefendantCode, "%" + input.DefendantCode + "%")
                    && rp.IsActive == true));

下面是生成的 SQL。我认为问题出在“Where”子句中

SELECT [t].[Id], [t].[StartDate], [t].[EndDate], [t].[WitnessName], [t].[SourceCode], [t].[JobsiteName], [t].[ShipName], [t].[EmployerCode]
FROM (
    SELECT DISTINCT [r].[RecordID] AS [Id], [r].[StartDate], [r].[EndDate], [r.Witness].[FullName] AS [WitnessName], CASE
        WHEN [r].[SourceID] IS NOT NULL
        THEN [r.Source].[SourceCode] ELSE N'zzzzz'
    END AS [SourceCode], CASE
        WHEN [r].[JobsiteID] IS NOT NULL
        THEN [r.Jobsite].[JobsiteName] ELSE N'zzzzz'
    END AS [JobsiteName], CASE
        WHEN [r].[ShipID] IS NOT NULL
        THEN [r.Ship].[ShipName] ELSE N'zzzzz'
    END AS [ShipName], CASE
        WHEN [r].[EmployerID] IS NOT NULL
        THEN [r.Employer].[DefendantCode] ELSE N'zzzzz'
    END AS [EmployerCode]
    FROM [Records] AS [r]
    LEFT JOIN [Ships] AS [r.Ship] ON [r].[ShipID] = [r.Ship].[ShipID]
    LEFT JOIN [Jobsites] AS [r.Jobsite] ON [r].[JobsiteID] = [r.Jobsite].[JobsiteID]
    LEFT JOIN [Sources] AS [r.Source] ON [r].[SourceID] = [r.Source].[SourceID]
    LEFT JOIN [Witnesses] AS [r.Witness] ON [r].[WitnessID] = [r.Witness].[WitnessID]
    LEFT JOIN [Defendants] AS [r.Contractor] ON [r].[ContractorID] = [r.Contractor].[DefendantID]
    LEFT JOIN [Defendants] AS [r.Employer] ON [r].[EmployerID] = [r.Employer].[DefendantID]
    WHERE ([r].[IsActive] = 1) AND (([r.Employer].[DefendantCode] LIKE (N'%' + @__input_DefendantCode_1) + N'%' OR [r.Contractor].[DefendantCode] LIKE (N'%' + @__input_DefendantCode_3) + N'%') OR EXISTS (
        SELECT 1
        FROM [Records_Products] AS [rp]
        INNER JOIN [Product_Defendant] AS [rp.ProductDefendant] ON [rp].[DefendantProductID] = [rp.ProductDefendant].[DefendantProductID]
        INNER JOIN [Defendants] AS [rp.ProductDefendant.Defendant] ON [rp.ProductDefendant].[DefendantID] = [rp.ProductDefendant.Defendant].[DefendantID]
        WHERE ([rp.ProductDefendant.Defendant].[DefendantCode] LIKE (N'%' + @__input_DefendantCode_5) + N'%' AND ([rp].[IsActive] = 1)) AND ([r].[RecordID] = [rp].[RecordID])))
) AS [t]
ORDER BY [t].[SourceCode]
OFFSET @__p_6 ROWS FETCH NEXT @__p_7 ROWS ONLY

【问题讨论】:

  • 这只是一个额外的连接(PK 索引搜索),通常不会显着影响查询性能。不,它无法避免,因为引入的中间翻译表需要它。
  • 是的,我同意这是无法避免的,但它确实会影响查询性能!非常...
  • 这听起来更像是一个数据库问题,而不是 EF 问题。检查该查询要使用哪些索引。
  • 我认为我需要的唯一索引是 DefendantId 上用于连接被告表的索引。那已经存在了!但我可能是错的,也许还有其他需要。我没有遵循代码优先方法或数据库优先方法。只是在前雇主的工作中工作。
  • 您可能还需要 ProductDefendant.DefendantId 上的索引(以覆盖所有外键)。

标签: c# lambda entity-framework-core


【解决方案1】:

很难给你一个好的建议,因为生成的 SQL 查询看起来很适合那个模型,现在 SQL 查询优化器 (CBO) 不应该像旧的 RBO 那样受到你编写查询的方式的影响(CBO 代表成本基于优化器,RBO - 基于规则的优化器)。他们应该能够将EXISTSIN 转换为JOIN(产生与JOIN 相同的执行计划)。当前 SQL 和原始 SQL 之间的唯一区别是增加了一个连接,使用集群 PK 索引查找应该不会显着影响性能。

但是既然你这么说,显然是一些未知的事情导致 CBO 选择了一个糟糕的计划。而且由于该计划取决于我没有的数据,所以我所能做的就是建议尝试两个功能等效的替代查询。

首先,您当前的(慢速)查询似乎是这样的:

var input = new { DefendantCode = "Abc", Skip = 4, Take = 2 };
var defendantCodePattern = "%" + input.DefendantCode + "%";

var query = db.Set<Record>()
    .Where(r => r.IsActive)
    .Where(r => EF.Functions.Like(r.Employer.DefendantCode, defendantCodePattern)
        || EF.Functions.Like(r.Contractor.DefendantCode, defendantCodePattern)
        || r.RecordProducts.Any(rp => EF.Functions.Like(rp.ProductDefendant.Defendant.DefendantCode, defendantCodePattern))
    )
    .Select(r => new
    {
        ID = r.RecordID,
        StartDate = r.StartDate,
        EndDate = r.EndDate,
        WitnessName = r.Witness.FullName,
        SourceCode = r.Source != null ? r.Source.SourceCode : "zzzzz",
        JobsiteName = r.Jobsite != null ? r.Jobsite.JobsiteName : "zzzzz",
        ShipName = r.Ship != null ? r.Ship.ShipName : "zzzzz",
        EmployeeCode = r.Employer != null ? r.Employer.DefendantCode : "zzzzz",
    })
    //.Distinct()
    .OrderBy(t => t.SourceCode)
    .Skip(input.Skip).Take(input.Take);

有些事情要提。首先,查询使用投影(Select),因此不需要Include / ThenInclude(因为它们是ignored)。其次,通用搜索模式是在查询之外创建和存储的,因此以 sing 参数而不是 3 结束。第三,此查询不需要 Distinct,因此我将其删除。

现在可能会尝试提高生成的 SQL 查询的执行速度。

(1)如果Defendant相关表不大,可以预取匹配搜索过滤器的DefendantIDs,然后使用Contains(翻译成SQLIN)进行过滤,这样有助于消除一些连接。例如

var defendantIds = db.Set<Defendant>()
    .Where(d => EF.Functions.Like(d.DefendantCode, defendantCodePattern))
    .Select(d => d.DefendantID)
    .ToList();

然后(第二个Where):

.Where(r => defendantIds.Contains(r.Employer.DefendantID)
    || defendantIds.Contains(r.Contractor.DefendantID)
    || r.RecordProducts.Any(rp => defendantIds.Contains(rp.ProductDefendant.Defendant.DefendantID))
)

(2) 以下技巧会将EXISTS 替换为LEFT JOIN。将第二个 Where 替换为:

.SelectMany(r => r.RecordProducts.DefaultIfEmpty(), (r, rp) => new { r, rp })
.Where(x => EF.Functions.Like(x.r.Employer.DefendantCode, defendantCodePattern)
    || EF.Functions.Like(x.r.Contractor.DefendantCode, defendantCodePattern)
    || EF.Functions.Like(x.rp.ProductDefendant.Defendant.DefendantCode, defendantCodePattern)
)
.Select(x => x.r)

并取消注释.Distinct()(这里需要它,因为LEFT JOIN(来自SelectMany)与源记录相乘)。本例中生成的 SQL 如下所示:

SELECT [t].[ID], [t].[StartDate], [t].[EndDate], [t].[WitnessName], [t].[SourceCode], [t].[JobsiteName], [t].[ShipName], [t].[EmployeeCode]
FROM (
    SELECT DISTINCT [r].[RecordID] AS [ID], [r].[StartDate], [r].[EndDate], [r.Witness].[FullName] AS [WitnessName], CASE
        WHEN [r].[SourceID] IS NOT NULL
        THEN [r.Source].[SourceCode] ELSE N'zzzzz'
    END AS [SourceCode], CASE
        WHEN [r].[JobsiteID] IS NOT NULL
        THEN [r.Jobsite].[JobsiteName] ELSE N'zzzzz'
    END AS [JobsiteName], CASE
        WHEN [r].[ShipID] IS NOT NULL
        THEN [r.Ship].[ShipName] ELSE N'zzzzz'
    END AS [ShipName], CASE
        WHEN [r].[EmployerID] IS NOT NULL
        THEN [r.Employer].[DefendantCode] ELSE N'zzzzz'
    END AS [EmployeeCode]
    FROM [Records] AS [r]
    LEFT JOIN [Ships] AS [r.Ship] ON [r].[ShipID] = [r.Ship].[ShipID]
    LEFT JOIN [Jobsites] AS [r.Jobsite] ON [r].[JobsiteID] = [r.Jobsite].[JobsiteID]
    LEFT JOIN [Sources] AS [r.Source] ON [r].[SourceID] = [r.Source].[SourceID]
    LEFT JOIN [Witnesses] AS [r.Witness] ON [r].[WitnessID] = [r.Witness].[WitnessID]
    LEFT JOIN [Defendants] AS [r.Contractor] ON [r].[ContractorID] = [r.Contractor].[DefendantID]
    LEFT JOIN [Defendants] AS [r.Employer] ON [r].[EmployerID] = [r.Employer].[DefendantID]
    LEFT JOIN [Records_Products] AS [r.RecordProducts] ON [r].[RecordID] = [r.RecordProducts].[RecordID]
    LEFT JOIN [Product_Defendant] AS [r.RecordProducts.ProductDefendant] ON [r.RecordProducts].[DefendantProductID] = [r.RecordProducts.ProductDefendant].[DefendantProductID]
    LEFT JOIN [Defendants] AS [r.RecordProducts.ProductDefendant.Defendant] ON [r.RecordProducts.ProductDefendant].[DefendantID] = [r.RecordProducts.ProductDefendant.Defendant].[DefendantID]
    WHERE ([r].[IsActive] = 1) AND (([r.Employer].[DefendantCode] LIKE @__defendantCodePattern_1 OR [r.Contractor].[DefendantCode] LIKE @__defendantCodePattern_1) OR [r.RecordProducts.ProductDefendant.Defendant].[DefendantCode] LIKE @__defendantCodePattern_1)
  ) AS [t]
ORDER BY [t].[SourceCode]
OFFSET @__p_2 ROWS FETCH NEXT @__p_3 ROWS ONLY

正如我一开始所说,通常这不会影响 CBO 计划。但我确实看到了与原来不同的估计执行计划,所以值得一试(虽然 LINQ 查询看起来很丑)。

【讨论】:

  • 我正在寻找但无法解释的解决方案是将查询从存在更改为连接,我只是不知道该怎么做,而使用存在正在运行查询进入 40 多秒的操作,现在下降到 ~10 秒,这对我的用户来说是可以接受的。感谢您的帮助
猜你喜欢
  • 2017-12-04
  • 2019-05-30
  • 2014-04-01
  • 1970-01-01
  • 1970-01-01
  • 2016-02-21
  • 2011-12-18
  • 2013-01-17
  • 2015-08-17
相关资源
最近更新 更多