c# requete Max ou par défaut?



requete linq to sql c# (14)

Quel est le meilleur moyen d'obtenir la valeur Max à partir d'une requête LINQ qui peut ne renvoyer aucune ligne? Si je fais juste

Dim x = (From y In context.MyTable _
         Where y.MyField = value _
         Select y.MyCounter).Max

J'obtiens une erreur lorsque la requête ne renvoie aucune ligne. je pourrais faire

Dim x = (From y In context.MyTable _
         Where y.MyField = value _
         Select y.MyCounter _
         Order By MyCounter Descending).FirstOrDefault

mais cela semble un peu obtus pour une demande si simple. Ai-je manqué une meilleure façon de le faire?

MISE À JOUR: Voici l'histoire arrière: J'essaie de récupérer le prochain compteur d'admissibilité à partir d'une table enfant (système hérité, ne me lance pas ...). La première ligne d'admissibilité pour chaque patient est toujours 1, la seconde est 2, etc. (évidemment ce n'est pas la clé primaire de la table enfant). Donc, je sélectionne la valeur de compteur max existante pour un patient, puis en ajoutant 1 pour créer une nouvelle ligne. Quand il n'y a pas de valeur enfant existante, j'ai besoin de la requête pour retourner 0 (donc l'addition de 1 me donnera une valeur de 1). Notez que je ne veux pas compter sur le nombre brut de lignes enfants, dans le cas où l'application existante introduit des écarts dans les valeurs du compteur (possible). Mon mauvais pour essayer de rendre la question trop générique.


Depuis .Net 3.5, vous pouvez utiliser DefaultIfEmpty () en passant la valeur par défaut en argument. Quelque chose comme l'un des moyens suivants:

int max = (from e in context.Table where e.Year == year select e.RecordNumber).DefaultIfEmpty(0).Max();
DateTime maxDate = (from e in context.Table where e.Year == year select e.StartDate ?? DateTime.MinValue).DefaultIfEmpty(DateTime.MinValue).Max();

Le premier est autorisé lorsque vous interrogez une colonne NOT NULL et le second est la façon dont un utilisé pour interroger une colonne NULLABLE. Si vous utilisez DefaultIfEmpty () sans arguments, la valeur par défaut sera celle définie pour le type de votre sortie, comme vous pouvez le voir dans la table Default Values .

Le SELECT résultant ne sera pas si élégant mais c'est acceptable.

J'espère que cela aide.


int max = list.Any() ? list.Max(i => i.MyCounter) : 0;

Si la liste contient des éléments (c'est-à-dire non vides), elle prendra le maximum du champ MyCounter, sinon, elle retournera 0.


J'ai juste eu un problème semblable, mais j'utilisais des méthodes d'extension LINQ sur une liste plutôt qu'une syntaxe de requête. Le casting à un tour Nullable fonctionne aussi:

int max = list.Max(i => (int?)i.MyCounter) ?? 0;

Vous pouvez toujours ajouter Double.MinValue à la séquence. Cela assurerait qu'il y ait au moins un élément et Max le renverrait seulement si c'était le minimum. Pour déterminer quelle option est la plus efficace ( Concat , FirstOrDefault ou Take(1) ), vous devez effectuer une analyse comparative adéquate.

double x = context.MyTable
    .Where(y => y.MyField == value)
    .Select(y => y.MyCounter)
    .Concat(new double[]{Double.MinValue})
    .Max();

DefaultIfEmpty ressemble à un cas pour DefaultIfEmpty (le code non testé suit):

Dim x = (From y In context.MyTable _
         Where y.MyField = value _
         Select y.MyCounter).DefaultIfEmpty.Max

peu tard, mais j'avais la même inquiétude ...

En reformulant votre code du message original, vous voulez que le maximum de l'ensemble S soit défini par

(From y In context.MyTable _
 Where y.MyField = value _
 Select y.MyCounter)

Prise en compte de ton dernier commentaire

Il suffit de dire que je sais que je veux 0 quand il n'y a pas d'enregistrements à sélectionner, ce qui a certainement un impact sur la solution éventuelle

Je peux reformuler votre problème en: Vous voulez le maximum de {0 + S}. Et il semble que la solution proposée avec concat soit sémantiquement la bonne :-)

var max = new[]{0}
          .Concat((From y In context.MyTable _
                   Where y.MyField = value _
                   Select y.MyCounter))
          .Max();

Une différence intéressante qui mérite d'être notée est que FirstOrDefault et Take (1) génèrent le même SQL (selon LINQPad, de toute façon), FirstOrDefault renvoie une valeur - la valeur par défaut - lorsqu'il n'y a pas de lignes correspondantes et Take (1) renvoie pas de résultats ... au moins dans LINQPad.


Pensez à ce que vous demandez!

Le maximum de {1, 2, 3, -1, -2, -3} est évidemment 3. Le maximum de {2} est évidemment 2. Mais quel est le maximum de l'ensemble vide {}? De toute évidence, c'est une question vide de sens. Le maximum de l'ensemble vide n'est simplement pas défini. Tenter d'obtenir une réponse est une erreur mathématique. Le maximum de tout ensemble doit lui-même être un élément de cet ensemble. L'ensemble vide n'a pas d'éléments, donc prétendre qu'un certain nombre est le maximum de cet ensemble sans être dans cet ensemble est une contradiction mathématique.

Tout comme il est correct que l'ordinateur lance une exception lorsque le programmeur lui demande de diviser par zéro, il est donc correct que l'ordinateur lance une exception lorsque le programmeur lui demande de prendre le maximum de l'ensemble vide. La division par zéro, en prenant le maximum de l'ensemble vide, en remuant le spacklerorke, et en chevauchant la licorne volante à Neverland sont toutes insignifiantes, impossibles, indéfinies.

Maintenant, qu'est-ce que vous voulez vraiment faire?


Pour Entity Framework et Linq to SQL, nous pouvons y parvenir en définissant une méthode d'extension qui modifie une Expression passée à la méthode IQueryable<T>.Max(...) :

static class Extensions
{
    public static TResult MaxOrDefault<T, TResult>(this IQueryable<T> source, 
                                                   Expression<Func<T, TResult>> selector)
        where TResult : struct
    {
        UnaryExpression castedBody = Expression.Convert(selector.Body, typeof(TResult?));
        Expression<Func<T, TResult?>> lambda = Expression.Lambda<Func<T,TResult?>>(castedBody, selector.Parameters);
        return source.Max(lambda) ?? default(TResult);
    }
}

Usage:

int maxId = dbContextInstance.Employees.MaxOrDefault(employee => employee.Id);
// maxId is equal to 0 if there is no records in Employees table

La requête générée est identique, elle fonctionne comme un appel normal à la méthode IQueryable<T>.Max(...) , mais s'il n'y a pas d'enregistrements, elle renvoie une valeur par défaut de type T au lieu de lancer une exception


decimal Max = (decimal?)(context.MyTable.Select(e => e.MyCounter).Max()) ?? 0;

J'ai assommé une méthode d'extension MaxOrDefault . Il n'y a pas grand-chose mais sa présence dans Intellisense est un rappel utile que Max sur une séquence vide provoquera une exception. De plus, la méthode permet de spécifier la valeur par défaut si nécessaire.

    public static TResult MaxOrDefault<TSource, TResult>(this 
    IQueryable<TSource> source, Expression<Func<TSource, TResult?>> selector,
    TResult defaultValue = default (TResult)) where TResult : struct
    {
        return source.Max(selector) ?? defaultValue;
    }

Utilisation, sur une colonne ou une propriété de type int named:

    sequence.DefaultOrMax(s => (int?)s.Id);

Pourquoi pas quelque chose de plus direct comme:

Dim x = context.MyTable.Max(Function(DataItem) DataItem.MyField = Value)

Juste pour que tout le monde sache que l'utilisation de Linq to Entities ne fonctionne pas ...

Si vous essayez de faire quelque chose comme

var max = new[]{0}
      .Concat((From y In context.MyTable _
               Where y.MyField = value _
               Select y.MyCounter))
      .Max();

Il va jeter une exception:

System.NotSupportedException: le type de noeud d'expression LINQ 'NewArrayInit' n'est pas pris en charge dans LINQ to Entities.

Je suggère de faire juste

(From y In context.MyTable _
                   Where y.MyField = value _
                   Select y.MyCounter))
          .OrderByDescending(x=>x).FirstOrDefault());

Et le FirstOrDefault retournera 0 si votre liste est vide.


Puisque DefaultIfEmpty n'est pas implémenté dans LINQ to SQL, j'ai fait une recherche sur l'erreur retournée et j'ai trouvé un article fascinant qui traite des ensembles nuls dans les fonctions agrégées. Pour résumer ce que j'ai trouvé, vous pouvez contourner cette limitation en transformant en nullable dans votre sélection. Mon VB est un peu rouillé, mais je pense que ça irait quelque chose comme ça:

Dim x = (From y In context.MyTable _
         Where y.MyField = value _
         Select CType(y.MyCounter, Integer?)).Max

Ou en C #:

var x = (from y in context.MyTable
         where y.MyField == value
         select (int?)y.MyCounter).Max();




linq-to-sql