Come forzare la somma LINQ () per restituire 0 mentre la raccolta di origine è vuota


183

Fondamentalmente quando eseguo la seguente query, se non sono stati trovati lead corrispondenti, la query seguente genera un'eccezione. In tal caso, preferirei che la somma fosse pari a 0 anziché generare un'eccezione. Ciò sarebbe possibile nella query stessa - intendo piuttosto che archiviare la query e verificarla query.Any()?

double earnings = db.Leads.Where(l => l.Date.Day == date.Day
                && l.Date.Month == date.Month
                && l.Date.Year == date.Year
                && l.Property.Type == ProtectedPropertyType.Password
                && l.Property.PropertyId == PropertyId).Sum(l => l.Amount);

2
Il Wherenon sarebbe tornato nullse non ha trovato alcun record, sarebbe restituirà un elenco di zero elementi. Qual è l'eccezione?
Mike Perrenoud,

3
Qual è l'eccezione?
Tot

3
Ottengo l'eccezione: il cast per il tipo di valore 'Int32' non è riuscito perché il valore materializzato è null. Il parametro generico del tipo di risultato o la query devono utilizzare un tipo nullable.
John Mayer,

1
@Stijn, no, quello che hai fatto non avrebbe funzionato. Il problema è il modo in cui SQLviene generato. Amountnon è in realtà null, è davvero un problema che circonda il modo in cui gestisce zero risultati. Dai un'occhiata alla risposta che è stata fornita.
Mike Perrenoud,

39
Non dovresti usare il doppio per gli importi in dollari! Anche importi in dollari frazionari. Mai e poi mai usare il doppio quando si intende un importo esatto. Le colonne del tuo database dovrebbero essere decimal, il tuo codice dovrebbe usare decimal. Dimentica che hai mai conosciuto floate doublenella tua carriera di programmatore fino al giorno in cui qualcuno ti dice di usarli, per statistiche o luminanza delle stelle o i risultati di un processo stocastico o la carica di un elettrone! Fino ad allora, stai sbagliando .
ErikE,

Risposte:


391

Prova a modificare la query in questo modo:

db.Leads.Where(l => l.Date.Day == date.Day
            && l.Date.Month == date.Month
            && l.Date.Year == date.Year
            && l.Property.Type == ProtectedPropertyType.Password
            && l.Property.PropertyId == PropertyId)
         .Select(l => l.Amount)
         .DefaultIfEmpty(0)
         .Sum();

In questo modo, la tua query selezionerà solo il Amountcampo. Se la raccolta è vuota, restituirà un elemento con il valore di 0e quindi la somma verrà applicata.


Sicuramente fa il trucco, ma non selezionerebbe prima un elenco di valori Amount e Sumquelli sul lato server, anziché sul lato database? La soluzione di imo 2kay è più ottimale, almeno più semanticamente corretta.
Maksim Vi.

3
@MaksimVI EF genererà la query sul primo materializzazione, quando la IQueryable<T>catena si ferma (in genere quando si chiama ToList, AsEnumerableecc .. e in questo caso Sum). Sumè un metodo noto e gestito dal provider di query EF e genererà la relativa istruzione SQL.
Simon Belanger,

@SimonBelanger Sono corretto, la somma è fatta sul lato DB, ma è fatta su una sottoquery che seleziona prima gli importi. Fondamentalmente la query è SELECT SUM(a.Amount) FROM (SELECT Amount FROM Leads WHERE ...) AS ainvece di solo SELECT SUM(Amount) FROM Leads. Anche la sottoquery ha un controllo null aggiuntivo e uno strano join esterno con una tabella a riga singola.
Maksim Vi.

Non è una differenza significativa di prestazioni ed è probabilmente ottimizzata, ma penso ancora che l'altra soluzione appaia più pulita.
Maksim Vi.

5
Essere consapevoli del fatto che DefaultIfEmptynon è supportato da un numero di provider LINQ, quindi è necessario inserire unToList() o qualcosa di simile prima di utilizzarlo in quei casi in modo che venga applicato nello scenario LINQ to Objects .
Christopher King,

188

Preferisco usare un altro trucco:

double earnings = db.Leads.Where(l => l.Date.Day == date.Day
                                      && l.Date.Month == date.Month
                                      && l.Date.Year == date.Year
                                      && l.Property.Type == ProtectedPropertyType.Password
                                      && l.Property.PropertyId == PropertyId)
                          .Sum(l => (double?) l.Amount) ?? 0;

18
Quando si utilizza Linq to SQL, questo genera un codice SQL molto più breve della risposta accettata
wertzui,

3
Questa è la risposta corretta Tutti gli altri falliscono. Prima lanciare su nullable e poi confrontare il risultato finale con null.
Mohsen Afshin,

3
Questo è molto meglio della risposta accettata per Linq To EF. Per me l'SQL generato funziona circa 3,8 volte meglio di DefaultIfEmpty.
Florian,

2
Questo è MOLTO PIÙ VELOCE.
Frakon,

1
non lo definirei un hack, poiché questo è l'uso esatto per il quale nullable è progettato, ovvero mostra la differenza tra nessun dato e valore predefinito
MikeT


4

Questa è la vittoria per me:

int Total = 0;
Total = (int)Db.Logins.Where(L => L.id == item.MyId).Sum(L => (int?)L.NumberOfLogins ?? 0);

Nella mia tabella LOGIN, nel campo NUMBEROFLOGINS alcuni valori sono NULL e altri hanno un numero INT. Riassumo qui NUMBEROFLOGINS totali di tutti gli utenti di una società (ogni ID).


1

Provare:

guadagni doppi = db.Leads.Where (l => l.ShouldBeIncluded) .Sum (l => (double?) l.Amount) ?? 0 ;

La query " SELECT SUM ([Amount]) " restituirà NULL per l'elenco vuoto. Ma se usi LINQ si aspetta che " Sum (l => l.Amount) " ritorni doppio e non ti permetta di usare " ?? operatore " per impostare 0 per la raccolta vuota.

Per evitare questa situazione, devi fare in modo che LINQ si aspetti " doppio? ". Puoi farlo lanciando " (double?) L.Amount ".

Non influenza la query su SQL ma fa funzionare LINQ per raccolte vuote.


0
db.Leads.Where(l => l.Date.Day == date.Day
        && l.Date.Month == date.Month
        && l.Date.Year == date.Year
        && l.Property.Type == ProtectedPropertyType.Password
        && l.Property.PropertyId == PropertyId)
     .Select(l => l.Amount)
     .ToList()
     .Sum();

1
Aggiungi alcune informazioni alla risposta sul codice
Jaqen H'ghar,

1
Ho ricevuto un errore quando provo senza ToList (), poiché non restituisce nulla. Ma ToList () renderà l'elenco vuoto e non dà alcun errore quando faccio ToList (). Sum ().
Mona,

2
Probabilmente non vorrai utilizzare ToListqui se tutto ciò che desideri è la somma. Ciò restituirà l'intero set di risultati (solo Amountper ogni record in questo caso) in memoria e quindi Sum()quel set. Molto meglio usare un'altra soluzione che esegue il calcolo tramite SQL Server.
Josh M.,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.