Valore di ritorno massimo se query vuota


175

Ho questa domanda:

int maxShoeSize = Workers
    .Where(x => x.CompanyId == 8)
    .Max(x => x.ShoeSize);

Cosa accadrà maxShoeSizese la compagnia 8 non ha alcun lavoratore?

AGGIORNAMENTO:
Come posso modificare la query per ottenere 0 e non un'eccezione?


Naor: hai sentito parlare di LINQPad?
Mitch Wheat,

3
Non capisco perché chiederesti "Cosa accadrà maxShoeSize?" se lo avessi già provato.
jwg

@jwg: Immagino di voler vedere se conosci la risposta :) Alla fine ho avuto un modo migliore di fare quello che ho chiesto e questo è ciò che intendevo.
Naor,

@Naor, questo non è un gioco d'ipotesi. Vorrei anche sottovalutare la domanda originale. Se conosci la risposta, dagliela, altrimenti sembri pigro. Proprio ora stavo per fare la stessa domanda e preparo tutte le informazioni incluso il messaggio di eccezione.
Juan Carlos Oropeza,

Risposte:


298
int maxShoeSize = Workers.Where(x => x.CompanyId == 8)
                         .Select(x => x.ShoeSize)
                         .DefaultIfEmpty(0)
                         .Max();

Lo zero in DefaultIfEmptynon è necessario.


Funziona :) Ma sul mio codice era necessario lo zero in DefaultIfEmpty.
Carlos Tenorio Pérez,

1
Per quanto riguarda la prima parte della domanda, a cui non viene data una risposta completa qui: secondo la documentazione ufficiale , se il tipo generico della sequenza vuota è un tipo di riferimento, si ottiene null. Altrimenti, secondo questa domanda , invocare Max()una sequenza vuota provoca un errore.
Raimund Krämer,

59

So che questa è una vecchia domanda e la risposta accettata funziona, ma questa domanda ha risposto alla mia domanda se un set così vuoto avrebbe comportato un'eccezione o un default(int)risultato.

La risposta accettata, tuttavia, mentre funziona, non è la soluzione ideale IMHO, che non viene fornita qui. Quindi lo sto fornendo nella mia risposta a beneficio di chiunque lo stia cercando.

Il codice originale del PO era:

int maxShoeSize = Workers.Where(x => x.CompanyId == 8).Max(x => x.ShoeSize);

Ecco come lo scriverei per evitare eccezioni e fornire un risultato predefinito:

int maxShoeSize = Workers.Where(x => x.CompanyId == 8).Max(x => x.ShoeSize as int?) ?? 0;

Ciò causa il tipo restituito della Maxfunzione int?, che consente il nullrisultato e quindi ??sostituisce il nullrisultato con 0.


MODIFICA
Solo per chiarire qualcosa dai commenti, Entity Framework al momento non supporta la asparola chiave, quindi il modo di scriverla quando si lavora con EF sarebbe:

int maxShoeSize = Workers.Where(x => x.CompanyId == 8).Max<[TypeOfWorkers], int?>(x => x.ShoeSize) ?? 0;

Dato che [TypeOfWorkers]potrebbe essere un nome di classe lungo ed è noioso da scrivere, ho aggiunto un metodo di estensione per dare una mano.

public static int MaxOrDefault<T>(this IQueryable<T> source, Expression<Func<T, int?>> selector, int nullValue = 0)
{
    return source.Max(selector) ?? nullValue;
}

Questo solo maniglie int, ma lo stesso potrebbe essere fatto per long, doubleo qualsiasi altro tipo di valore è necessario. L'uso di questo metodo di estensione è molto semplice, basta passare la funzione di selezione e facoltativamente includere un valore da utilizzare per null, che per impostazione predefinita è 0. Quindi quanto sopra può essere riscritto in questo modo:

int maxShoeSize = Workers.Where(x => x.CompanyId == 8).MaxOrDefault(x => x.ShoeSize);

Spero che questo aiuti le persone ancora di più.


3
Ottima soluzione! La DefaultIfEmptyrisposta più popolare funziona bene solo quando Maxnon si esegue una valutazione.
McGuireV10

@ McGuireV10 Sì, in genere non mi piace usare Selectcome intermediario quando sto per usare una funzione aggregata come Maxsul risultato. Ho anche che (non ho ancora testato questa) che l'SQL generato sarebbe utilizzare una query in più subselect così facendo, mentre la mia sarebbe solo che fare con un insieme vuoto restituendo null. Grazie per il voto e il feedback! ;)
CptRobby,

@ McGuireV10 Allo stesso modo, se l' ShoeSizeera in realtà in un relativo Uniformentità, io non uso Workers.Where(x => x.CompanyId == 8).Select(x => x.Uniform).Max(x => x.ShoeSize), invece vorrei solo mantenere l'intera valutazione nel Maxfunzione di: Workers.Where(x => x.CompanyId == 8).Max(x => x.Uniform.ShoeSize). Preferisco utilizzare il minor numero possibile di metodi nelle mie query per consentire a EF di avere la massima libertà nel decidere come costruire query in modo efficiente. ;-)
CptRobby,

1
Non riuscivo a farlo funzionare in EntityFramework. Qualcuno può chiarire? (Utilizzando la tecnica DefaultIfEmpty ha funzionato).
Moe Sisko,

1
Una versione generica del metodo di estensione:public static TResult MaxOrDefault<TElement, TResult>(this IQueryable<TElement> items, Expression<Func<TElement, TResult>> selector, TResult defaultValue = default) where TResult : struct => items.Select(selector).Max(item => (TResult?)item) ?? defaultValue;
relativamente_


17
int maxShoeSize = Workers.Where(x => x.CompanyId == 8)
                     .Select(x => x.ShoeSize)
                     .DefaultIfEmpty()
                     .Max();

10

Se questo è Linq to SQL, non mi piace usarlo Any()perché provoca più query al server SQL.

Se ShoeSizenon è un campo nullable, quindi usare solo il .Max(..) ?? 0non funzionerà, ma quanto segue:

int maxShoeSize = Workers.Where(x = >x.CompanyId == 8).Max(x => (int?)x.ShoeSize) ?? 0;

Non modifica assolutamente l'SQL emesso, ma restituisce 0 se la sequenza è vuota perché modifica il Max()per restituire un int?invece di un int.


4
int maxShoeSize=Workers.Where(x=>x.CompanyId==8)
    .Max(x=>(int?)x.ShoeSize).GetValueOrDefault();

(supponendo che ShoeSizesia di tipo int)

Se Workersè un DbSeto ObjectSetda Entity Framework la tua query iniziale genererebbe un InvalidOperationException, ma non si lamenta di una sequenza vuota ma si lamenta che il valore materializzato NULL non può essere convertito in un int.


2

Max genererà System.InvalidOperationException "La sequenza non contiene elementi"

class Program
{
    static void Main(string[] args)
    {
        List<MyClass> list = new List<MyClass>();

        list.Add(new MyClass() { Value = 2 });

        IEnumerable<MyClass> iterator = list.Where(x => x.Value == 3); // empty iterator.

        int max = iterator.Max(x => x.Value); // throws System.InvalidOperationException
    }
}

class MyClass
{
    public int Value;
}

2

NB: la query con DefaultIfEmpty()potrebbe essere significativamente più lenta . Nel mio caso è stata una semplice query con .DefaultIfEmpty(DateTime.Now.Date).

Ero troppo pigro per profilarlo, ma ovviamente EF ha cercato di ottenere tutte le righe e quindi prendere il Max()valore.

Conclusione: a volte la gestione InvalidOperationExceptionpotrebbe essere la scelta migliore.


0

Puoi usare un ternario all'interno .Max()per gestire il predicato e impostarne il valore;

// assumes Workers != null && Workers.Count() > 0
int maxShoeSize = Workers.Max(x => (x.CompanyId == 8) ? x.ShoeSize : 0);

Dovresti gestire la Workersraccolta come nulla / vuota se questa è una possibilità, ma dipenderà dalla tua implementazione.


0

Puoi provare questo:

int maxShoeSize = Workers.Where(x=>x.CompanyId == 8).Max(x => x.ShoeSize) ?? 0;

Penso che questo fallirà poiché Max si aspetta un int e sta ottenendo un null; quindi si è già verificato un errore prima dell'entrata in vigore dell'operatore a coalescenza nulla.
d219,

0

Puoi verificare se ci sono lavoratori prima di eseguire il Max ().

private int FindMaxShoeSize(IList<MyClass> workers) {
   var workersInCompany = workers.Where(x => x.CompanyId == 8);
   if(!workersInCompany.Any()) { return 0; }
   return workersInCompany.Max(x => x.ShoeSize);
}
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.