问题
tl;dr; Putting orderby contact.Property
before my projection (let defaultAddress = contact.Addresses...
) creates an exponentially more complex SQL statement and sometimes causes an OutOfMemoryException.
Question
I have a simple data model for storing contact information
public class Contact {
public int ContactID { get; set; }
public virtual ICollection<Address> Addresses { get; set; }
//etc
}
public class Address {
public int AddressID { get; set; }
public int ContactID { get; set; }
public virtual Contact Contact { get; set; }
//etc
}
I want to project that into a flat model and sort the results, for example
from c in Contacts
let a = c.Addresses.FirstOrDefault()
orderby c.DOB
select new {
ContactID = c.ContactID,
AddressID = a.AddressID
}
That generates straightforward SQL
SELECT
[Project2].[ContactID] AS [ContactID],
[Project2].[C1] AS [C1]
FROM ( SELECT
[Extent1].[ContactID] AS [ContactID],
[Extent1].[DOB] AS [DOB],
(SELECT TOP (1)
[Extent2].[AddressID] AS [AddressID]
FROM [dbo].[Addresses] AS [Extent2]
WHERE [Extent1].[ContactID] = [Extent2].[ContactID]) AS [C1]
FROM [dbo].[Contacts] AS [Extent1]
) AS [Project2]
ORDER BY [Project2].[DOB] ASC
If I switch the order of the orderby c.DOB
to be before let a = ...
, EF adds an entire extra OUTER APPLY
statement to the generated SQL.
SELECT
[Project2].[ContactID] AS [ContactID],
[Project2].[C1] AS [C1]
FROM ( SELECT
[Extent1].[ContactID] AS [ContactID],
[Extent1].[DOB] AS [DOB],
(SELECT TOP (1)
[Extent3].[AddressID] AS [AddressID]
FROM [dbo].[Addresses] AS [Extent3]
WHERE [Extent1].[ContactID] = [Extent3].[ContactID]) AS [C1]
FROM [dbo].[Contacts] AS [Extent1]
OUTER APPLY (SELECT TOP (1) [Extent2].[AddressID] AS [AddressID]
FROM [dbo].[Addresses] AS [Extent2]
WHERE [Extent1].[ContactID] = [Extent2].[ContactID] ) AS [Limit1]
) AS [Project2]
ORDER BY [Project2].[DOB] ASC
The query gets significantly more complicated and has exponentially more unused OUTER APPLYs, LEFT JOINs and CROSS JOINs if I add more collection properties to my projection or if I add a filter to the collections. If the query is has enough flat properties putting the orderby in the wrong place will cause an OutOfMemoryException while trying to build a query plan for the massive SQL that gets generated!
回答1:
It requires intimate knowledge of EF's source code to understand how the SQL generator parses an expression into SQL. It's not my ambition to dig that deep (that would take many days), so unless someone from the EF team chimes in, I guess we have to live with the facts as they are.
It's always good to know about issues like this. Having seen more issues at Stack Overflow, it seems a rule of the thumb emerges:
Sort as late as possible in a LINQ statement
Another case I already knew of is a query like Set<T>().OrderBy(...).GroupBy(...)
. Here, the OrderBy
doesn't cause a bad query shape. It's ignored completely! Conversely, the statement Set<T>().GroupBy(...).OrderBy(...)
does translate into a SQL ORDER BY
, but of course the statements are semantically different.
来源:https://stackoverflow.com/questions/46151694/why-does-ef-add-tons-of-unused-outer-apply-clauses-when-i-put-orderby-at-the-beg