Ultimamente ho avuto la necessità di estrarre tutti gli elementi distinti di una query Linq. La query in questione interroga un EntitySet e restituisce come risultato una proiezione dei dati di una Entity all'interno di una classe; si presenta all'incirca così:
var result =
(from c in dataContext.CaratteristicheChimicheSet
where codition
orderby order descending
select new Chemical()
{
Alluminio = c.Al
}
Le condizioni di uguaglianza delle istanze del risultato dipendono dalla proiezione e sono quindi definite in funzione di Chemical (ovvero della classe che uso come tipo di ritorno) e non delle Entity dell'EntitySet che sto interrogando; Chemical implementa a questo scopo i metodi Equals, GetHashCode e l'interfaccia IEquatable<Chemical>, definendo in questo modo le condizioni di uguaglianza di due istanze.
Cosa ho fatto quindi: un po' ingenuamente mi sono fidato delle apparenze, e per ottenere il set di risultati desiderato ho richiamato il metodo Distinct offerto da Linq sulla query succitata; morale della favola i risultati che ottenevo non erano quelli sperati, bensì era l'intero set di istanze con tanti "doppioni". In più i metodi di ugualianza di Chemical non venivano nemmeno lontanamente sfiorati.
Ho proseguito quindi nei miei tentativi: sono passato all'overload di Distinct che riceve in ingresso un IEqualityComparer; peggio di prima! il metodo "addirittura" non è supportato da LinqToEntities!
A questo punto ho deciso di fermarmi a ragionare e come al solito il ragionamento mi ha guidato ad una soluzione: LinqToEntities implementa il Distinct traducendolo in un Distinct sql, come si può osservare analizzando col profiler cosa succede sul DB all'invocazione del metodo Distinct senza parametri. Questa conclusione spiega perchè i metodi di uguaglianza implementati da Chemical non vengono considerati e perchè è corretto che LinqToEntities non supporti il Distinct che riceve un IEqualityComparer.
Forte di questa osservazione, è stato facile trovare la soluzione al mio problema. Eseguo la query, facendomi restituire tutti i risultati (duplicati compresi) in un array, tramite il metodo ToArray; sull'array risultante invoco il metodo Distinct. In questo scenario entra in gioco LinqToObjects che fa proprio quello che speravo: esegue i confronti di uguaglianza usando i metodi implementati da Chemical e restituendo quindi il set di istanze desiderato.
Qual è lo scopo di questo post? In sostanza volevo sottolineare due punti:
- Esemplificare il funzionamento del metodo Distinct, in relazione al contesto in cui si usa, documentando quindi le differenze "sostanziali" tra l'implentazione di LinqToEntities e quella di LinqToObjects
- Ho avuto una ulteriore conferma di come procedere "ad intuito" non aiuti a trovare le soluzioni ai problemi e men che meno a capire ciò che si sta facendo. Quando si incontra un problema "nuovo" conviene sempre soffermarsi a ragionare e capire "cosa ci sta dietro", piuttosto che buttarsi sulla prima apparente soluzione che ci viene offerta
Matteo