Devo scrivere query complesse su Linq?

6

Spesso trovo complesse query di Linq che si estendono su 10-15 linee nella nostra base di codice.
Trovo tali domande difficili da capire e impossibili da eseguire il debug.
Ho cercato di suddividere la query in parti più piccole, ma è difficile da eseguire poiché di solito le parti interne dipendono dalle variabili dell'ambito esterno. Pensa a cicli annidati in codice imperativo.
Questa è un'indicazione della mia inattendenza con Linq? Le query di Linq complesse sono giustificate?
La soluzione imperativa sarebbe meglio in questi casi?
Di seguito è riportata una query di esempio ...

 var xItems = (
            from responseData in filteredItems
            let parentLevelItems = responseData.Items.Where(adi => ids.Contains(adi.Id))
            select new
            {
                secondLevelData = 
                (from c in secondLevelLabels
                    join secondLevel in responseData.children on c
                    equals string.IsNullOrEmpty(secondLevel.Label) ? 
                                           Constants.BlankData : secondLevel.Label into coun
                    from cn in coun.DefaultIfEmpty()
                    select new
                    {
                        secondLevelLabel = c,
                        parentLevelItems = parentLevelItems,
                        dataItems = cn == null ? response : 
                                                 cn.Items.Where(adi => ids.Contains(adi.Id))
                    })
            });
    
posta DanielS 05.06.2015 - 01:41
fonte

1 risposta

8

Riscriviamolo senza estensioni LINQ o LINQ:

var xItems = new List<object>();
foreach(var responseData in filteredItems)
{
    var parentLevelItems = new List<Item>();
    foreach(var adi in responseData.Items)
    {
        if(ids.Contains(adi.Id))
        {
            parentLevelItems.Add(adi);
        }
    }

    var coun = new Dictionary<Label, List<Data>>();
    foreach(var c in secondLevelLabels)
    {
        foreach(var secondLevel in responseData.Children)
        {

            if(c == string.IsNullOrEmpty(secondLevel.Label) ? 
                                           Constants.BlankData : secondLevel.Label)
            {
                if(!coun.ContainsKey(c))
                {
                   coun[c] = new List<Data>();
                }
                count[c].Add(secondLevel);
            }
        }
        var secondLevelData = new List<object>();
        foreach(var cn in coun.DefaultIfEmpty())
        {
            secondLevelData.Add(new
            {
                secondLevelLabel = c,
                parentLevelItems = parentLevelItems,
                dataItems = cn == null ? response : 
                                                 cn.Values.Where(adi => ids.Contains(adi.Id))
            });
        }
    }

    xItems.Add(new 
    {
        secondLevelData = secondLevelData;
    });
}

Mi sono preso qualche libertà con la composizione dei nomi dei tipi, la traduzione del join del gruppo e ho mantenuto alcune semplici estensioni LINQ ( Contains e DefaultIfEmpty ) per evitare di diventare troppo pazzo. Tuttavia, considerando quanto sopra, penso che sia relativamente chiaro che il tuo problema di root non è la scelta di LINQ sui loop.

In realtà, il modo corretto per gestire questo codice è probabilmente con i soliti refactoring: rinomina e metodo di estrazione .

I nomi variano da terribile ( cn , coun ) a piuttosto scadente ( xItems , secondLevelItems ). È difficile dirlo con certezza, senza conoscere il contesto di questo codice, ma dovresti essere in grado di trovare altri nomi espressivi per questi. Questa è una logica molto complessa, sembra che ciò a cui si applica dovrebbe essere più specifico di un semplice "oggetto"!

Come per l'estrazione di metodi, tu dici:

I've tried to split the query to smaller parts, but it's hard to do since usually inner parts depend on the outer scope variables.

Bene, è qui che i parametri tornano utili! Se i loop sono ancora più naturali per te, puoi sempre provare a tradurre da LINQ a loop, estraendo i tuoi metodi, quindi traducendo di nuovo i singoli bit su LINQ.

Più in generale, non si ottiene alcun tipo di punti bonus automatici per l'utilizzo di LINQ. Ci sono due ragioni per cui potrebbe essere preferibile:

  • È più dichiarativo della scrittura in loop, rendendo il tuo codice più espressivo consentendo di scrivere che stai cercando di ottenere piuttosto che come lo stai facendo.
  • Potrebbe essere più facile mantenere una valutazione pigra.

Quest'ultimo è molto specifico della situazione, quindi concentriamoci sul primo. Questo è davvero solo un particolare tipo di vantaggio di leggibilità. Di solito è abbastanza, ma ho sicuramente visto casi (spesso con il metodo di estensione Aggregate ) in cui la versione LINQ è semplicemente più difficile da decifrare rispetto alla versione loop. In tal caso, vai per i loop! Sarebbe pazzesco scegliere l'opzione meno leggibile in nome della leggibilità.

Se sei preoccupato che la mancanza di esperienza con LINQ significhi che il tuo giudizio potrebbe non essere buono per la leggibilità, non c'è davvero molto che tu possa fare su quell'altro che cercare il giudizio degli altri e acquisire più esperienza. Ma se, come nel tuo esempio, la complessità riguarda solo la lunghezza piuttosto che troppa roba da includere in troppe poche affermazioni, c'è una buona possibilità che la scelta di LINQ non sia in definitiva la causa del problema.

    
risposta data 05.06.2015 - 05:07
fonte

Leggi altre domande sui tag