giovedì 21 febbraio 2013

Quando un distinct non basta. Linq workaround


Ci troviamo spesso nella situazione di dover effettuare dei distinct di oggetti di una collection di cui ci interessa che il distinct effettuato riguardi un sottogruppo di campi. Capita anche, lavorando con iQuerable provenienti da un orm interfacciato con un database quali EntityFramework o Linq-To-Sql classes, che le entità in oggetto in realtà comprendano dei campi scritti da trigger database che non ci interessano particolarmente. Oltre all'iterazione dell'intera collection o dell'intero iQuerable (che tra l'altro causa la materializzazione in ram di ogni singola entità iterata con conseguente aumento del costo computazionale) una soluzione relativamente standard consiste nel creare un'implementazione di IEqualityComparer per la classe specifica delle entità in considerazione.




Prendo ad esempio una classe generica, dotata di alcuni attributi significativi ed altri superflui

Public Class Albero
 Dim Altezza As Integer
 Dim Posizione As String
 Dim Peso As Integer
 Dim Specie As Integer
 Dim ColoreFoglie as Color
 Dim DataRilevazione as Date
 Dim OperatoreRilevazione as String
End Class

e scrivo un'overload di Equals e GetHashCode secondo le proprietà che mi interessano


Public Class AlberoCompare
    Implements System.Collections.Generic.IEqualityComparer(Of Albero)
    Public Function Equals(ByVal x As Albero, ByVal y As Albero) As Boolean
        Return x.Altezza=y.Altezza and x.Peso=y.Peso and x.Specie=y.Specie
    End Function
    Public Function GetHashCode(ByVal x As Albero) As Integer
        Return x.Altezza + x.Peso
    End Function
End Class


Basterà passare questa classe di comparazione come parametro al metodo distinct per ottenere il risultato desiderato

Un'alternativa più veloce invece consiste nel raggruppare le entities secondo i parametri di interesse
Dim group = (From p As Albero In db.Alberi Group p By p.Altezza, p.Peso, p.Specie Into Group Select Group)
e quindi selezionare il primo elemento di ogni gruppo:

dim result = group.Select(Function(d) d.First())
ottenendo nuovamente una collection o un iQuerable dello stesso tipo di entities (nel caso di estrazione dati da un orm, questa soluzione non materializza nulla in ram fino alla risoluzione dell'IQuerable)


Nessun commento: