Изменение выражения Entity Framework, когда таблицы были переназначены - PullRequest
2 голосов
/ 18 июня 2019

У меня проблема с удалением идентификатора таблицы.

Сначала у меня был этот запрос ниже, где у объекта (таблицы) '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 было удалено из таблицы и заменено на DefendantProductId из таблицы перевода с именем ProductDefendant, как это

ProductDefendant таблица:

  • DefendantProductId
  • DefendantId
  • PRODUCTID

Так что я не могу больше это делать:

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 ниже. Я думаю, что проблема в предложении "Где"

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

1 Ответ

3 голосов
/ 23 июня 2019

Трудно дать вам хороший совет, потому что сгенерированный SQL-запрос выглядит хорошо для этой модели, и в настоящее время на оптимизаторы SQL-запросов (CBO) не должно влиять то, как вы пишете запрос, как старые RBO (CBO означает Cost Оптимизатор на основе, RBO - Оптимизатор на основе правил). Они должны иметь возможность превратить EXISTS или IN в JOIN (создать тот же план выполнения, что и JOIN). Единственное различие между текущим SQL и оригиналом состоит в одном дополнительном соединении, которое при поиске кластерного индекса PK не должно существенно влиять на производительность.

Но поскольку вы говорите, что, по-видимому, что-то неизвестное заставляет ОСО выбирать плохой план. А поскольку план зависит от данных, которых у меня нет, все, что я могу сделать, - это предложить два альтернативных функционально эквивалентных запроса.

Во-первых, ваш текущий (медленный) запрос выглядит так:

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 не нужны (потому что они игнорируются ). Во-вторых, общий шаблон поиска создается и сохраняется вне запроса, в результате чего вместо параметра 3. в качестве параметра sing используется третий. В-третьих, Distinct не требуется для этого запроса, поэтому я удалил его.

Теперь потенциальные попытки улучшить скорость выполнения сгенерированного SQL-запроса.

(1) Если связанная таблица Defendant невелика, вы можете предварительно выбрать DefendantID s, соответствующие поисковому фильтру, а затем использовать Contains (в переводе на SQL IN) для фильтрации. Это поможет устранить некоторые из соединений. например,

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 выглядит некрасиво).

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...