Valeur de retour maximale si requête vide


175

J'ai cette question:

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

Que se passera-t-il maxShoeSizesi l'entreprise 8 n'a pas du tout de travailleurs?

MISE À JOUR:
Comment puis-je changer la requête pour obtenir 0 et pas une exception?


Naor: avez-vous entendu parler de LINQPad?
Mitch Wheat

3
Je ne comprends pas pourquoi vous demanderiez "Qu'est-ce qui va se passer maxShoeSize?" si vous l'aviez déjà essayé.
jwg

@jwg: Je suppose que je voulais voir si vous connaissez la réponse :) Finalement, j'ai eu une meilleure façon de faire ce que j'ai demandé et c'est ce que je voulais dire.
Naor

@Naor, ce n'est pas un jeu de devinettes. Je voterais également contre la question initiale. Si vous connaissez la réponse, donnez-la nous sinon vous avez l'air paresseux. J'étais sur le point de poser à l'instant la même question et je prépare toutes les informations, y compris le message d'exception.
Juan Carlos Oropeza

Réponses:


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

Le zéro DefaultIfEmptyn'est pas nécessaire.


Fonctionne :) Mais sur mon code, le zéro dans DefaultIfEmpty était nécessaire.
Carlos Tenorio Pérez

1
Concernant la première partie de la question, à laquelle il n'y a pas de réponse complète ici: Selon la documentation officielle , si le type générique de la séquence vide est un type référence, vous obtenez null. Sinon, selon cette question , l'appel Max()à une séquence vide entraîne une erreur.
Raimund Krämer

59

Je sais que c'est une vieille question et que la réponse acceptée fonctionne, mais cette question a répondu à ma question de savoir si un tel ensemble vide entraînerait une exception ou un default(int)résultat.

La réponse acceptée cependant, bien que cela fonctionne, n'est pas la solution idéale à mon humble avis, qui n'est pas donnée ici. Ainsi je le fournis dans ma propre réponse pour le bénéfice de tous ceux qui le recherchent.

Le code original de l'OP était:

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

Voici comment je l'écrirais pour éviter les exceptions et fournir un résultat par défaut:

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

Cela provoque le type de retour de la Maxfonction int?, ce qui autorise le nullrésultat, puis ??remplace le nullrésultat par 0.


EDIT
Juste pour clarifier quelque chose à partir des commentaires, Entity Framework ne prend actuellement pas en charge le asmot - clé, donc la façon de l'écrire lorsque vous travaillez avec EF serait:

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

Comme le [TypeOfWorkers]peut être un nom de classe long et qu'il est fastidieux à écrire, j'ai ajouté une méthode d'extension pour vous aider.

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

Cela ne les poignées int, mais la même chose pourrait être fait pour long, doubleou tout autre type de valeur que vous avez besoin. L'utilisation de cette méthode d'extension est très simple, il vous suffit de passer votre fonction de sélection et d'inclure éventuellement une valeur à utiliser pour null, qui vaut par défaut 0. Donc, ce qui précède pourrait être réécrit comme suit:

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

Espérons que cela aide encore plus les gens.


3
Excellente solution! La DefaultIfEmptyréponse la plus populaire ne fonctionne bien que lorsque vous Maxne faites pas d'évaluation.
McGuireV10

@ McGuireV10 Ouais, je n'aime généralement pas utiliser Selectcomme intermédiaire quand je vais juste utiliser une fonction d'agrégation comme Maxsur le résultat. Je pense également (je ne l'ai pas encore testé) que le SQL généré utiliserait une requête de sous-sélection supplémentaire en faisant cela, tandis que le mien traiterait simplement un ensemble vide en retournant null. Merci pour le vote positif et les commentaires! ;)
CptRobby

@ McGuireV10 De même, si l' ShoeSizeétait en fait dans une liée Uniformentité, je ne voudrais pas utiliser Workers.Where(x => x.CompanyId == 8).Select(x => x.Uniform).Max(x => x.ShoeSize), au lieu que je voudrais juste garder toute l'évaluation dans la Maxfonction: Workers.Where(x => x.CompanyId == 8).Max(x => x.Uniform.ShoeSize). Je préfère utiliser le moins de méthodes possible dans mes requêtes pour permettre à EF d'avoir la plus grande liberté pour décider comment construire efficacement des requêtes. ;-)
CptRobby

1
Je ne pouvais pas faire fonctionner cela dans EntityFramework. Quelqu'un peut-il nous éclairer? (L'utilisation de la technique DefaultIfEmpty a fonctionné).
Moe Sisko

1
Une version générique de la méthode d'extension: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;
relative_random


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

10

S'il s'agit de Linq to SQL, je n'aime pas l'utiliser Any()car cela entraîne plusieurs requêtes sur le serveur SQL.

Si ce ShoeSizen'est pas un champ Nullable, utiliser uniquement le .Max(..) ?? 0ne fonctionnera pas, mais ce qui suit:

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

Cela ne change absolument pas le SQL émis, mais il renvoie 0 si la séquence est vide car il change le Max()pour retourner un int?au lieu d'un int.


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

(en supposant que ShoeSizec'est de type int)

Si Workersest un DbSetou ObjectSetdepuis Entity Framework, votre requête initiale lèverait un InvalidOperationException, mais ne se plaignant pas d'une séquence vide, mais se plaignant que la valeur matérialisée NULL ne peut pas être convertie en un int.


2

Max lancera System.InvalidOperationException "La séquence ne contient aucun élément"

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 requête avec DefaultIfEmpty()peut être significativement plus lente . Dans mon cas, c'était une simple requête avec .DefaultIfEmpty(DateTime.Now.Date).

J'étais trop paresseux pour le profiler, mais évidemment EF a essayé d'obtenir toutes les lignes, puis de prendre la Max()valeur.

Conclusion: la manipulation InvalidOperationExceptionpeut parfois être le meilleur choix.


0

Vous pouvez utiliser un ternaire à l'intérieur .Max()pour gérer le prédicat et définir sa valeur;

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

Vous devrez gérer la Workerscollection comme étant nulle / vide si c'est une possibilité, mais cela dépendra de votre implémentation.


0

Vous pouvez essayer ceci:

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

Je pense que cela échouera car Max attend un int et qu'il obtient un nul; donc une erreur s'est déjà produite avant que l'opérateur de fusion nul n'entre en vigueur.
d219

0

Vous pouvez vérifier s'il y a des ouvriers avant de faire 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);
}
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.