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:

  1. 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”.
  2. 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:

  1. From c In dcFerreroERP.ORDACQs _
    Where c.Anno = Today.Year _
    Order By c.Anno Descending, c.Numero Descending _
    Select c.Anno, c.Numero
  2. 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]