2009-02-23 15 views
17

ho questo codice (ok, non lo so, ma qualcosa di simile: p)Sum() provoca un'eccezione invece di restituire 0 quando nessun file

var dogs = Dogs.Select(ø => new Row 
    { 
      Name = ø.Name, 
      WeightOfNiceCats = ø.Owner 
       .Cats 
       .Where(æ => !æ.Annoying) 
       .Sum(æ => æ.Weight), 
    }); 

Qui mi passare attraverso tutti i cani e riassumere la peso (in un decimale non annullabile) di tutti i gatti non fastidiosi che ha lo stesso proprietario del cane. Naturalmente, più o meno tutti i gatti sono fastidiosi, così ottengo questo errore:

The null value cannot be assigned to a member with type System.Decimal which is a non-nullable value type.

Nessuno dei campi o chiavi esterne utilizzati possono essere nulli. Quindi l'errore si verifica quando la clausola Where non restituisce gatti, cosa che spesso accade. Ma come posso risolvere questo? Voglio che restituisca 0 quando ciò accade. Provato con un DefaultIfEmpty() dopo la clausola Where, ma d'altra parte ottengo questo errore:

Object reference not set to an instance of an object.

che credo sia comprensibile. Ho provato ad aggiungere un ?? dopo il Sum, ma poi non ci vorrà compilo a causa di questo errore:

Operator '??' cannot be applied to operands of type 'decimal' and 'decimal'

Il che rende anche il senso di corso. Quindi cosa posso fare? Sarebbe bello se la cosa Sum restituisse 0 quando non c'era nulla da sommare. O una dichiarazione SumOrZero di qualche tipo. Sarebbe difficile creare un metodo SumOrZero che abbia funzionato con Linq2SQL?

+1

Grande domanda. È un peccato che questo comportamento non sembra essere documentato nel MSDN. – Mykroft

risposta

20

Questo è quello che ho finito con per ora:

.Sum(æ => (decimal?) æ.Weight) ?? 0.0M, 

Questo funziona come un fascino.

Preferirei comunque avere un SumOrZero che potrei usare, che funzionerebbe esattamente come il normale Sum tranne che non restituirebbe mai null per nessuna ragione. Come gli altri hanno notato, questo sarebbe abbastanza facile da realizzare per IEnumerable ma un po 'più icky da creare per IQuearyable quindi funzionerebbe con Linq2SQL, ecc. Quindi lo lascerò per ora. Anche se qualcuno un giorno si annoia e scrive SumOrZero per IQueryables che funziona con Linq2SQL per tutti i tipi numerici, per favore fatemi sapere: D

+0

Piccolo errore di battitura - ci dovrebbe essere 'decimale' * –

+1

@Daniel Che è corretto! E ci sono voluti solo 5 anni prima che qualcuno lo notasse e lo avvisasse: p – Svish

3

Il trucco che hai già indicato (.Sum(æ => (decimal?) æ.Weight) ?? 0.0M) è il migliore che possa pensare per questo scenario quando viene eseguito come query nel database. Un po 'fastidioso, ma ci sono cose peggiori ...

A differenza di LINQ-to-Objects, non è possibile aggiungere il proprio metodo di estensione, in quanto deve essere mappato dal provider sottostante.

+0

esattamente. era quello che temevo, hehe. – Svish

5

In LINQ to Objects sarebbe facile. Con LINQ to SQL sarà più difficile. È possibile scrivere il proprio metodo di estensione che ha chiamato Queryable.Sum con un'espressione creata da quella normale, con un cast per il tipo nullable. Sospetto che avresti bisogno di fare il ?? 0m nel codice chiamante però. In altre parole, si potrebbe fare:

.SumOrNull(æ => æ.Weight) ?? 0M, 

Dove sarebbe la firma per .SumOrNull:

public static decimal? SumOrNull<TSource, decimal>(this IQueryable<TSource>, 
    Func<TSource,decimal> projection) 

Si potrebbe fondamentalmente scrivere che per tutti i tipi di valore supportati in Queryable. È in grado di scriverlo genericamente e chiamare il metodo appropriato in Queryable utilizzando il reflection, ma anche quello sarebbe icky.

Penso che tu stia meglio con quello che hai, per essere onesti.

+0

Quindi penso che lascerò semplicemente quello che ho ... – Svish

0

Si desidera utilizzare DefaultIfEmpty, che restituirà un IEnumberable con un singolo elemento di 0 e che sarà semanticamente uguale a quello richiesto.

Dato che si dispone di un decimale nullable, sarà necessario utilizzare la seconda versione del metodo con 2 parametri.

+1

Al contrario; il decimale non è annullabile. La * soluzione alternativa * al glitch LINQ-to-SQL è di renderlo nullable per il calcolo. –

+0

Quindi è ancora più semplice :) – leppie

+1

DefaultIfEmpty ha causato il "riferimento dell'oggetto non impostato su un'istanza di un oggetto". – Svish