57 votes

LINQ Max() avec des nuls

J'ai une liste qui contient un certain nombre de points (avec une composante X et Y).

Je veux obtenir le Max X pour tous les points de la liste, comme ceci :

double max = pointList.Max(p=> p.X);

Le problème se pose lorsque j'ai un null dans la liste au lieu d'un point. Quelle serait la meilleure façon de contourner ce problème ?

115voto

Ani Points 59747

Eh bien, tu pourrais juste les filtrer :

pointList.Where(p => p != null).Max(p => p.X)

D'un autre côté, si vous voulez null à traiter comme s'il s'agissait de points ayant une coordonnée X 0 (ou similaire), vous pourriez le faire :

pointList.Max(p => p == null ? 0 : p.X)

Notez que les deux techniques échoueront si la séquence est vide. Une solution de contournement pour ceci (si souhaitable) serait :

pointList.DefaultIfEmpty().Max(p => p == null ? 0 : p.X)

1 votes

Je ne recommanderais pas d'utiliser le DefaultIfEmpty . Veuillez consulter ma réponse pour savoir pourquoi : lien

1 votes

@Nikkelmann Je crois qu'il s'agit d'une requête LINQ d'entités en mémoire, sans connexion à une base de données.

0 votes

@RamonSnir si c'est le cas alors l'un ou l'autre est bien :) Mais maintenant les gens savent qu'il ne faut pas l'utiliser pour LinqToSql.

28voto

Mark Byers Points 318575

Si vous voulez fournir une valeur par défaut pour X d'un point nul :

pointList.Max(p => p == null ? 0 : p.X)

Ou pour fournir une valeur par défaut pour une liste vide :

int max = points.Where(p => p != null)
                .Select(p => p.X)
                .DefaultIfEmpty()
                .Max();

1 votes

Approche intéressante, je la préfère à la mienne car elle évite le conditionnel et la DefaultIfEmpty est appliqué sur le résultat type.

14voto

Nikkelmann Points 271

Je ne recommanderais pas d'utiliser le DefaultIfEmpty dans ce cas, puisqu'il produit un SQL assez grand par rapport aux autres alternatives.

Regardez cet exemple :

Nous avons une liste de modules pour une page et nous voulons obtenir la valeur maximale de la colonne "Sort". Si la liste n'a pas d'enregistrements, alors null est retourné. DefaultIfEmpty vérifie la présence de null et renvoie la valeur par défaut du type de données de la colonne lorsque celle-ci est nulle.

var max = db.PageModules.Where(t => t.PageId == id).Select(t => t.Sort).DefaultIfEmpty().Max();

Cela produit le SQL suivant :

exec sp_executesql N'SELECT 
[GroupBy1].[A1] AS [C1]
FROM ( SELECT 
    MAX([Join1].[A1]) AS [A1]
    FROM ( SELECT 
        CASE WHEN ([Project1].[C1] IS NULL) THEN 0 ELSE [Project1].[Sort] END AS [A1]
        FROM   ( SELECT 1 AS X ) AS [SingleRowTable1]
        LEFT OUTER JOIN  (SELECT 
            [Extent1].[Sort] AS [Sort], 
            cast(1 as tinyint) AS [C1]
            FROM [dbo].[PageModules] AS [Extent1]
            WHERE [Extent1].[PageId] = @p__linq__0 ) AS [Project1] ON 1 = 1
    )  AS [Join1]
)  AS [GroupBy1]',N'@p__linq__0 int',@p__linq__0=11
go

Si, au lieu de cela, nous transformons la colonne en nullable et que nous laissons Convert.ToInt32() traiter le null comme tel :

var max = Convert.ToInt32(db.PageModules.Where(t => t.PageId == id).Max(t => (int?)t.Sort));

Nous obtenons alors le SQL suivant :

exec sp_executesql N'SELECT 
[GroupBy1].[A1] AS [C1]
FROM ( SELECT 
    MAX([Extent1].[Sort]) AS [A1]
    FROM [dbo].[PageModules] AS [Extent1]
    WHERE [Extent1].[PageId] = @p__linq__0
)  AS [GroupBy1]',N'@p__linq__0 int',@p__linq__0=11
go

Je peux vraiment recommander l'utilisation d'ExpressProfiler pour vérifier le SQL qui est exécuté : http://expressprofiler.codeplex.com/

La dernière expression Linq peut également être écrite comme suit :

var max = Convert.ToInt32(db.PageModules.Where(t => t.PageId == id).Select(t => (int?)t.Sort).Max());

et produiront le même SQL, mais j'aime bien la méthode plus concise .Max(t => (int?)t.Sort) .

2 votes

Le problème avec tout ORM ou outil qui génère du SQL pour vous est qu'il est très facile de se tirer une balle dans le pied. Tout ce que vous avez à faire est du code et presque personne ne regarde le SQL généré. Le problème avec ces traductions mécaniques est qu'elles produisent un code "taille unique" et parfois, il est bien plus gros que ce dont vous pouvez avoir besoin. Merci de l'avoir souligné dans cet article. En résumé, vérifiez toujours vos requêtes générées ! +1 !!

0 votes

+1 pour toujours vérifier vos requêtes générées ! Vous pouvez écrire du mauvais SQL (donc nous le profilons) mais d'une certaine manière l'ORM est toujours blâmé lorsque nous ne profilons pas (ou même ne regardons pas) le SQL qu'il génère !

0 votes

J'essaie d'utiliser cette méthode, mais j'ai une syntaxe de requête différente et je n'arrive pas à faire fonctionner l'expression lamda dans le select. J'utilise Convert.ToInt32( (from x in table join y in table 2 on x.ID equals y.ID where y.ProjectID == ProjectID select x.ObservationNum).Max()); Y a-t-il un moyen de faire les jointures avec votre syntaxe, ou de modifier ma méthode pour obtenir le nullable int cast ?

2voto

Robaticus Points 14665
double max = pointList.Where(p=>p != null).Max(p=>p.X)

Ça devrait marcher.

0voto

Petr Points 953

Essayez de convertir en nullable

double max = (double?)pointList.Max(p => p.X);

plus : Max ou défaut ?

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X