我們使用的是實體框架,我們希望執行簡單的LEFT JOIN。如何使用EF編寫此LEFT JOIN查詢
其實,這是我們想在LINQ(可查詢)的SQL:
SELECT
cbl.ID as ClaimBatchLine_ID
,cbl.PurchasePrice
,c.*
,ic.DueDate
,ic.Reference
FROM ClaimBatchLine cbl
INNER JOIN Claim c ON c.ID = cbl.CLaim_ID
LEFT JOIN InvoiceClaim ic ON ic.ID = c.ID
WHERE cbl.ClaimBatch_ID = @claimBatchId
ORDER BY cbl.ID
OFFSET (@recordsPerPage*@page) ROWS
FETCH NEXT @recordsPerPage ROWS ONLY
我們想出了是這樣的:
from cbl in ClaimBatchLines where cbl.ClaimBatch_ID == 1
from c in Claims where c.ID == cbl.Claim_ID
from ic in InvoiceClaims.DefaultIfEmpty() where ic.ID == c.ID
select new {cbl, c, ic.Reference}
和產生以下SQL。
SELECT [t0].[ID],
[t0].[ClaimBatch_ID],
[t0].[Claim_ID],
[t0].[PurchasePrice],
[t1].[ID] AS [ID2],
[t1].[ClaimType_ID],
[t1].[Debtor_ID],
[t1].[CustomerContractRevision_ID],
[t1].[Date],
[t1].[CreatedOn],
[t1].[GrossAmount],
[t1].[OpenAmount],
[t1].[IsProcessedByOpenAmountCalculator],
[t1].[RowVersion],
[t2].[Reference] AS [Reference]
FROM [ClaimBatchLine] AS [t0]
CROSS JOIN [Claim] AS [t1]
LEFT OUTER JOIN [InvoiceClaim] AS [t2] ON 1 = 1
WHERE([t2].[ID] = [t1].[ID])
AND ([t1].[ID] = [t0].[Claim_ID])
AND ([t0].[ClaimBatch_ID] = @p0);
它產生相同的結果集。這很好。但是,您可以看到LEFT OUTER JOIN [InvoiceClaim] AS [t2] ON 1 = 1
不是我們想要的。我希望它會把它翻譯成LEFT JOIN InvoiceClaim ic ON ic.ID = c.ID
。
我們做錯了什麼?或者,LINQ to SQL只是次優的(就性能而言),而不能理解我們想要的。
編輯: 在LINQPad這導致一些不錯的查詢
from cbl in ClaimBatchLines
join c in Claims on cbl.Claim_ID equals c.ID
join ic in InvoiceClaims on c.ID equals ic.ID into g
from e in g.DefaultIfEmpty()
where cbl.ClaimBatch_ID == 1
select new {cbl, c, e.Reference}
即
-- Region Parameters
DECLARE @p0 INT= 1;
-- EndRegion
SELECT [t0].[ID],
[Columns left out for brevity]
[t2].[Reference] AS [Reference]
FROM [ClaimBatchLine] AS [t0]
INNER JOIN [Claim] AS [t1] ON [t0].[Claim_ID] = [t1].[ID]
LEFT OUTER JOIN [InvoiceClaim] AS [t2] ON [t1].[ID] = [t2].[ID]
WHERE [t0].[ClaimBatch_ID] = @p0;
但加入像這樣的分頁功能時:
(from cbl in ClaimBatchLines
join c in Claims on cbl.Claim_ID equals c.ID
join ic in InvoiceClaims on c.ID equals ic.ID into g
from e in g.DefaultIfEmpty()
where cbl.ClaimBatch_ID == 1
select new {cbl, c, e.Reference})
.OrderBy(a => a.cbl.ID)
.Skip(0 * 15000)
.Take(15000)
它產生這個'怪物':
-- Region Parameters
DECLARE @p0 INT= 1;
DECLARE @p1 INT= 0;
DECLARE @p2 INT= 15000;
-- EndRegion
SELECT [t4].[ID],
[Columsn left out for brevity...]
FROM
(
SELECT ROW_NUMBER() OVER(ORDER BY [t3].[ID]) AS [ROW_NUMBER],
[Columsn left out for brevity...]
FROM
(
SELECT [t0].[ID],
[Columsn left out for brevity...]
FROM [ClaimBatchLine] AS [t0]
INNER JOIN [Claim] AS [t1] ON [t0].[Claim_ID] = [t1].[ID]
LEFT OUTER JOIN [InvoiceClaim] AS [t2] ON [t1].[ID] = [t2].[ID]
) AS [t3]
WHERE [t3].[ClaimBatch_ID] = @p0
) AS [t4]
WHERE [t4].[ROW_NUMBER] BETWEEN @p1 + 1 AND @p1 + @p2
ORDER BY [t4].[ROW_NUMBER];
甚至更糟。當我執行同樣的LINT-TO-EF不是通過LINQpad但在使用EF庫的代碼,我得到這個更大的怪物:
SELECT [Project5].[ID] AS [ID],
[Columns left out for brevity...]
[Project5].[Reference] AS [Reference]
FROM
(
SELECT [Extent1].[ID] AS [ID],
[Extent1].[Claim_ID] AS [Claim_ID],
[Extent1].[ClaimBatch_ID] AS [ClaimBatch_ID],
[Extent1].[PurchasePrice] AS [PurchasePrice],
[Join4].[Id1] AS [ID1],
[Join4].[ClaimType_ID] AS [ClaimType_ID],
[Join4].[Debtor_ID] AS [Debtor_ID],
[Join4].[CustomerContractRevision_ID] AS [CustomerContractRevision_ID],
[Join4].[Date] AS [Date],
[Join4].[GrossAmount] AS [GrossAmount],
[Join4].[OpenAmount] AS [OpenAmount],
[Join4].[CreatedOn] AS [CreatedOn],
[Join4].[IsProcessedByOpenAmountCalculator] AS [IsProcessedByOpenAmountCalculator],
CASE
WHEN((NOT(([Join4].[C11] = 1)
AND ([Join4].[C11] IS NOT NULL)))
AND (NOT(([Join4].[C12] = 1)
AND ([Join4].[C12] IS NOT NULL)))
AND (NOT(([Join4].[C13] = 1)
AND ([Join4].[C13] IS NOT NULL)))
AND (NOT(([Join4].[C14] = 1)
AND ([Join4].[C14] IS NOT NULL))))
THEN '2X'
WHEN(([Join4].[C14] = 1)
AND ([Join4].[C14] IS NOT NULL))
THEN '2X0X'
WHEN(([Join4].[C12] = 1)
AND ([Join4].[C12] IS NOT NULL))
THEN '2X1X'
WHEN(([Join4].[C11] = 1)
AND ([Join4].[C11] IS NOT NULL))
THEN '2X2X'
ELSE '2X3X'
END AS [C1],
CASE
WHEN((NOT(([Join4].[C11] = 1)
AND ([Join4].[C11] IS NOT NULL)))
AND (NOT(([Join4].[C12] = 1)
AND ([Join4].[C12] IS NOT NULL)))
AND (NOT(([Join4].[C13] = 1)
AND ([Join4].[C13] IS NOT NULL)))
AND (NOT(([Join4].[C14] = 1)
AND ([Join4].[C14] IS NOT NULL))))
THEN CAST(NULL AS BIT)
WHEN(([Join4].[C14] = 1)
AND ([Join4].[C14] IS NOT NULL))
THEN [Join4].[IsAppeared]
WHEN(([Join4].[C12] = 1)
AND ([Join4].[C12] IS NOT NULL))
THEN CAST(NULL AS BIT)
WHEN(([Join4].[C11] = 1)
AND ([Join4].[C11] IS NOT NULL))
THEN CAST(NULL AS BIT)
END AS [C2],
CASE
WHEN((NOT(([Join4].[C11] = 1)
AND ([Join4].[C11] IS NOT NULL)))
AND (NOT(([Join4].[C12] = 1)
AND ([Join4].[C12] IS NOT NULL)))
AND (NOT(([Join4].[C13] = 1)
AND ([Join4].[C13] IS NOT NULL)))
AND (NOT(([Join4].[C14] = 1)
AND ([Join4].[C14] IS NOT NULL))))
THEN CAST(NULL AS TINYINT)
WHEN(([Join4].[C14] = 1)
AND ([Join4].[C14] IS NOT NULL))
THEN CAST(NULL AS TINYINT)
WHEN(([Join4].[C12] = 1)
AND ([Join4].[C12] IS NOT NULL))
THEN [Join4].[AdjustmentClaimReason_ID]
WHEN(([Join4].[C11] = 1)
AND ([Join4].[C11] IS NOT NULL))
THEN CAST(NULL AS TINYINT)
END AS [C3],
CASE
WHEN((NOT(([Join4].[C11] = 1)
AND ([Join4].[C11] IS NOT NULL)))
AND (NOT(([Join4].[C12] = 1)
AND ([Join4].[C12] IS NOT NULL)))
AND (NOT(([Join4].[C13] = 1)
AND ([Join4].[C13] IS NOT NULL)))
AND (NOT(([Join4].[C14] = 1)
AND ([Join4].[C14] IS NOT NULL))))
THEN CAST(NULL AS INT)
WHEN(([Join4].[C14] = 1)
AND ([Join4].[C14] IS NOT NULL))
THEN CAST(NULL AS INT)
WHEN(([Join4].[C12] = 1)
AND ([Join4].[C12] IS NOT NULL))
THEN [Join4].[User_ID]
WHEN(([Join4].[C11] = 1)
AND ([Join4].[C11] IS NOT NULL))
THEN CAST(NULL AS INT)
END AS [C4],
CASE
WHEN((NOT(([Join4].[C11] = 1)
AND ([Join4].[C11] IS NOT NULL)))
AND (NOT(([Join4].[C12] = 1)
AND ([Join4].[C12] IS NOT NULL)))
AND (NOT(([Join4].[C13] = 1)
AND ([Join4].[C13] IS NOT NULL)))
AND (NOT(([Join4].[C14] = 1)
AND ([Join4].[C14] IS NOT NULL))))
THEN CAST(NULL AS INT)
WHEN(([Join4].[C14] = 1)
AND ([Join4].[C14] IS NOT NULL))
THEN CAST(NULL AS INT)
WHEN(([Join4].[C12] = 1)
AND ([Join4].[C12] IS NOT NULL))
THEN CAST(NULL AS INT)
WHEN(([Join4].[C11] = 1)
AND ([Join4].[C11] IS NOT NULL))
THEN [Join4].[CostClaimAnnouncement_ID]
END AS [C5],
CASE
WHEN((NOT(([Join4].[C11] = 1)
AND ([Join4].[C11] IS NOT NULL)))
AND (NOT(([Join4].[C12] = 1)
AND ([Join4].[C12] IS NOT NULL)))
AND (NOT(([Join4].[C13] = 1)
AND ([Join4].[C13] IS NOT NULL)))
AND (NOT(([Join4].[C14] = 1)
AND ([Join4].[C14] IS NOT NULL))))
THEN CAST(NULL AS DECIMAL(19, 4))
WHEN(([Join4].[C14] = 1)
AND ([Join4].[C14] IS NOT NULL))
THEN CAST(NULL AS DECIMAL(19, 4))
WHEN(([Join4].[C12] = 1)
AND ([Join4].[C12] IS NOT NULL))
THEN CAST(NULL AS DECIMAL(19, 4))
WHEN(([Join4].[C11] = 1)
AND ([Join4].[C11] IS NOT NULL))
THEN [Join4].[DiscountFactor]
END AS [C6],
CASE
WHEN((NOT(([Join4].[C11] = 1)
AND ([Join4].[C11] IS NOT NULL)))
AND (NOT(([Join4].[C12] = 1)
AND ([Join4].[C12] IS NOT NULL)))
AND (NOT(([Join4].[C13] = 1)
AND ([Join4].[C13] IS NOT NULL)))
AND (NOT(([Join4].[C14] = 1)
AND ([Join4].[C14] IS NOT NULL))))
THEN CAST(NULL AS DATETIME2)
WHEN(([Join4].[C14] = 1)
AND ([Join4].[C14] IS NOT NULL))
THEN CAST(NULL AS DATETIME2)
WHEN(([Join4].[C12] = 1)
AND ([Join4].[C12] IS NOT NULL))
THEN CAST(NULL AS DATETIME2)
WHEN(([Join4].[C11] = 1)
AND ([Join4].[C11] IS NOT NULL))
THEN [Join4].[DiscountValidTo]
END AS [C7],
CASE
WHEN((NOT(([Join4].[C11] = 1)
AND ([Join4].[C11] IS NOT NULL)))
AND (NOT(([Join4].[C12] = 1)
AND ([Join4].[C12] IS NOT NULL)))
AND (NOT(([Join4].[C13] = 1)
AND ([Join4].[C13] IS NOT NULL)))
AND (NOT(([Join4].[C14] = 1)
AND ([Join4].[C14] IS NOT NULL))))
THEN CAST(NULL AS INT)
WHEN(([Join4].[C14] = 1)
AND ([Join4].[C14] IS NOT NULL))
THEN CAST(NULL AS INT)
WHEN(([Join4].[C12] = 1)
AND ([Join4].[C12] IS NOT NULL))
THEN CAST(NULL AS INT)
WHEN(([Join4].[C11] = 1)
AND ([Join4].[C11] IS NOT NULL))
THEN [Join4].[AppliedDiscountAdjustmentClaim_ID]
END AS [C8],
CASE
WHEN((NOT(([Join4].[C11] = 1)
AND ([Join4].[C11] IS NOT NULL)))
AND (NOT(([Join4].[C12] = 1)
AND ([Join4].[C12] IS NOT NULL)))
AND (NOT(([Join4].[C13] = 1)
AND ([Join4].[C13] IS NOT NULL)))
AND (NOT(([Join4].[C14] = 1)
AND ([Join4].[C14] IS NOT NULL))))
THEN CAST(NULL AS INT)
WHEN(([Join4].[C14] = 1)
AND ([Join4].[C14] IS NOT NULL))
THEN CAST(NULL AS INT)
WHEN(([Join4].[C12] = 1)
AND ([Join4].[C12] IS NOT NULL))
THEN CAST(NULL AS INT)
WHEN(([Join4].[C11] = 1)
AND ([Join4].[C11] IS NOT NULL))
THEN [Join4].[ExpiredDiscountAdjustmentClaim_ID]
END AS [C9],
CASE
WHEN((NOT(([Join4].[C11] = 1)
AND ([Join4].[C11] IS NOT NULL)))
AND (NOT(([Join4].[C12] = 1)
AND ([Join4].[C12] IS NOT NULL)))
AND (NOT(([Join4].[C13] = 1)
AND ([Join4].[C13] IS NOT NULL)))
AND (NOT(([Join4].[C14] = 1)
AND ([Join4].[C14] IS NOT NULL))))
THEN CAST(NULL AS VARCHAR(1))
WHEN(([Join4].[C14] = 1)
AND ([Join4].[C14] IS NOT NULL))
THEN CAST(NULL AS VARCHAR(1))
WHEN(([Join4].[C12] = 1)
AND ([Join4].[C12] IS NOT NULL))
THEN CAST(NULL AS VARCHAR(1))
WHEN(([Join4].[C11] = 1)
AND ([Join4].[C11] IS NOT NULL))
THEN CAST(NULL AS VARCHAR(1))
ELSE [Join4].[Reference]
END AS [C10],
CASE
WHEN((NOT(([Join4].[C11] = 1)
AND ([Join4].[C11] IS NOT NULL)))
AND (NOT(([Join4].[C12] = 1)
AND ([Join4].[C12] IS NOT NULL)))
AND (NOT(([Join4].[C13] = 1)
AND ([Join4].[C13] IS NOT NULL)))
AND (NOT(([Join4].[C14] = 1)
AND ([Join4].[C14] IS NOT NULL))))
THEN CAST(NULL AS DATETIME2)
WHEN(([Join4].[C14] = 1)
AND ([Join4].[C14] IS NOT NULL))
THEN CAST(NULL AS DATETIME2)
WHEN(([Join4].[C12] = 1)
AND ([Join4].[C12] IS NOT NULL))
THEN CAST(NULL AS DATETIME2)
WHEN(([Join4].[C11] = 1)
AND ([Join4].[C11] IS NOT NULL))
THEN CAST(NULL AS DATETIME2)
ELSE [Join4].[DueDate]
END AS [C11],
[Extent7].[Reference] AS [Reference]
FROM [dbo].[ClaimBatchLine] AS [Extent1]
INNER JOIN
(
SELECT [Extent2].[Id] AS [Id1],
[Columns left out for brevity...]
FROM [dbo].[Claim] AS [Extent2]
LEFT OUTER JOIN
(
SELECT [Extent3].[Id] AS [Id],
[Extent3].[CostClaimAnnouncement_ID] AS [CostClaimAnnouncement_ID],
[Extent3].[DiscountFactor] AS [DiscountFactor],
[Extent3].[DiscountValidTo] AS [DiscountValidTo],
[Extent3].[AppliedDiscountAdjustmentClaim_ID] AS [AppliedDiscountAdjustmentClaim_ID],
[Extent3].[ExpiredDiscountAdjustmentClaim_ID] AS [ExpiredDiscountAdjustmentClaim_ID],
CAST(1 AS BIT) AS [C1]
FROM [dbo].[CostClaim] AS [Extent3]
) AS [Project1] ON [Extent2].[Id] = [Project1].[Id]
LEFT OUTER JOIN
(
SELECT [Extent4].[Id] AS [Id],
[Extent4].[IsAppeared] AS [IsAppeared],
CAST(1 AS BIT) AS [C1]
FROM [dbo].[InterestClaim] AS [Extent4]
) AS [Project2] ON [Extent2].[Id] = [Project2].[Id]
LEFT OUTER JOIN
(
SELECT [Extent5].[Id] AS [Id],
[Extent5].[AdjustmentClaimReason_ID] AS [AdjustmentClaimReason_ID],
[Extent5].[User_ID] AS [User_ID],
CAST(1 AS BIT) AS [C1]
FROM [dbo].[AdjustmentClaim] AS [Extent5]
) AS [Project3] ON [Extent2].[Id] = [Project3].[Id]
LEFT OUTER JOIN
(
SELECT [Extent6].[Id] AS [Id],
[Extent6].[Reference] AS [Reference],
[Extent6].[DueDate] AS [DueDate],
CAST(1 AS BIT) AS [C1]
FROM [dbo].[InvoiceClaim] AS [Extent6]
) AS [Project4] ON [Extent2].[Id] = [Project4].[Id]
) AS [Join4] ON [Extent1].[Claim_ID] = [Join4].[Id1]
LEFT OUTER JOIN [dbo].[InvoiceClaim] AS [Extent7] ON [Join4].[Id1] = [Extent7].[Id]
WHERE 1 = [Extent1].[ClaimBatch_ID]
) AS [Project5]
ORDER BY [Project5].[ID] ASC
OFFSET 0 ROWS FETCH NEXT 15000 ROWS ONLY;
這到底是怎麼回事呢?在LINQpad上搔抓最初看起來很好。但是生產代碼中的最後一個查詢太簡單了! 也許這些查詢適用於一些簡單的應用程序。在查詢更多記錄中的500k條記錄時,這對我來說並不好。我將堅持使用簡單的SQL表值函數,而不是使用LINQ。太遺憾了。
其實沒有必要檢查L2E查詢中的「null」,因爲SQL自然會處理這個問題。唯一需要注意的是屬性是值類型,在這種情況下,您需要將其提升(轉換)爲相應的可空類型。 –
明白了。感謝@IvanStoev,每天我都會學到新的東西;) – octavioccl
我們都這樣做,並且用EF核心應該重新開始,因爲它完全改變了一切:) –