Linq e clausola Order By
La clausola Order By in certe situazioni può non venire generata, si consideri ad esempio questa frase Linq:
From c In dcDB.ORDVENs _
Where c.SerieProduzione >= 7000 And c.SerieProduzione <= 8999 _
Order By c.AnnoProduzione Descending, c.SerieProduzione Descending _
Select c.SerieProduzione, c.AnnoProduzione _
Distinct
ch genera la seguente frase SQL (per ottenerla è sufficiente impostare dcDB.Log = Console.Out):
SELECT DISTINCT [t0].[SerieProduzione], [t0].[AnnoProduzione]
FROM [dbo].[ORDVEN] AS [t0]
WHERE ([t0].[SerieProduzione] >= @p0) AND ([t0].[SerieProduzione] <= @p1)
— @p0: Input Int (Size = 0; Prec = 0; Scale = 0) [7000]
— @p1: Input Int (Size = 0; Prec = 0; Scale = 0) [8999]
e come si può notare la clausola Order By non è stata generata.
Il motivo è la presenza della clausola Distinct come viene spiegato nei seguenti:
- Known Issues and Considerations in LINQ to Entities al punto Ordering Information Lost:
“If any additional operations are performed after an ordering operation, there is no assurance that the ordering will be preserved in those additional operations. This includes operations such as Select or Where”. -
A bug? – OrderBy and Distinct:
“Matt Warren – MSFT
You have to apply the ordering after the distinct operator. Operators like Distinct and Union/Concat don’t preserve ordering.”
Soluzione 1
Utilizzare le funzioni lambda per aggiungere le clausole Order By:
From c In dcDB.ORDVENs _
Where c.SerieProduzione >= 7000 And c.SerieProduzione <= 8999 _
Select c.SerieProduzione, c.AnnoProduzione _
Distinct).OrderByDescending( _
Function(o) o.SerieProduzione).OrderByDescending( _
Function(o) o.AnnoProduzione)
generando la seguente frase SQL:
SELECT [t1].[SerieProduzione], [t1].[AnnoProduzione]
FROM (
SELECT DISTINCT [t0].[SerieProduzione], [t0].[AnnoProduzione]
FROM [dbo].[ORDVEN] AS [t0]
WHERE ([t0].[SerieProduzione] >= @p0) AND ([t0].[SerieProduzione] <= @p1)
) AS [t1]
ORDER BY [t1].[AnnoProduzione] DESC, [t1].[SerieProduzione] DESC
— @p0: Input Int (Size = 0; Prec = 0; Scale = 0) [7000]
— @p1: Input Int (Size = 0; Prec = 0; Scale = 0) [8999]
Si noti che vengono generate due Select annidate e che l’ordine dei metodi nella frase Linq è inverso all’ordine delle clausole Order By nella frase SQL il che può generare qualche confuzione senza contare che la frase Linq perde in leggibilità.
Soluzione 2
Mettere la clausola Order By al termine della frase Linq:
From c In dcDB.ORDVENs _
Where c.SerieProduzione >= 7000 And c.SerieProduzione <= 8999 _
Select c.SerieProduzione, c.AnnoProduzione _
Distinct _
Order By AnnoProduzione Descending, SerieProduzione Descending
La frase SQL generata è la medesima di quella generata utilizzando le funzioni Lambda, ma in questo caso la frase Linq è decisamente più leggibile
SELECT [t1].[SerieProduzione], [t1].[AnnoProduzione]
FROM (
SELECT DISTINCT [t0].[SerieProduzione], [t0].[AnnoProduzione]
FROM [dbo].[ORDVEN] AS [t0]
WHERE ([t0].[SerieProduzione] >= @p0) AND ([t0].[SerieProduzione] <= @p1)
) AS [t1]
ORDER BY [t1].[AnnoProduzione] DESC, [t1].[SerieProduzione] DESC
— @p0: Input Int (Size = 0; Prec = 0; Scale = 0) [7000]
— @p1: Input Int (Size = 0; Prec = 0; Scale = 0) [8999]
La posizione della clausola Order By nella frase Linq genera di fatto sempre la stessa frase SQL si considerino ad esempio le seguenti:
- From c In dcFerreroERP.ORDACQs _
Where c.Anno = Today.Year _
Order By c.Anno Descending, c.Numero Descending _
Select c.Anno, c.Numero - From c In dcFerreroERP.ORDACQs _
Where c.Anno = Today.Year _
Select c.Anno, c.Numero _
Order By Anno Descending, Numero Descending
entrambe le frasi Linq genereranno la stessa frase SQL:
SELECT [t0].[Anno], [t0].[Numero]
FROM [dbo].[ORDACQ] AS [t0]
WHERE [t0].[Anno] = @p0
ORDER BY [t0].[Anno] DESC, [t0].[Numero] DESC
— @p0: Input Int (Size = 0; Prec = 0; Scale = 0) [2009]