LINQ Max()函数处理空值

68

我有一个包含许多点(具有X和Y分量)的列表。

我想要获取列表中所有点的最大X值,如下所示:

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

问题是当列表中存在 null 而不是 point 时。如何最好地解决这个问题?

9个回答

126

好的,你可以直接将它们过滤掉:

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

另一方面,如果你想将null视为具有X坐标0(或类似)的点来处理,你可以这样做:

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

请注意,这两种技术都会在序列为空时引发异常。如果需要的话,一个解决方法是:

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

1
我不建议使用DefaultIfEmpty。请查看我的答案以了解原因:链接 - Nikkelmann
1
@Nikkelmann 我相信这是一个LINQ查询内存实体的代码,没有连接到任何数据库。 - Ramon Snir
@RamonSnir 如果是这样的话,那么两者都可以 :) 但现在人们知道不要将其用于 LinqToSql。 - Nikkelmann
或者说:Linq to Entities。 - Nikkelmann
如果需要的话,他也可以使用可空类型pointList.Max(p => p == null ? (double?)null : p.X)。请注意,使用可空类型时,即使是空序列,Max也不会抛出异常。 - Jeppe Stig Nielsen

30

如果你想要为一个空指针的X属性提供默认值:

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

或者为一个空列表提供默认值:

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

1
有趣的方法,我喜欢它比我的更好,因为它避免了条件语句,并且DefaultIfEmpty应用于结果类型。 - Ani

14

在这种情况下,我不建议使用DefaultIfEmpty,因为它会产生比其他替代方案更大的SQL。

请看这个例子:

我们有一个页面模块的列表,并想获取列"Sort"的最大值。如果该列表没有记录,则返回null。 DefaultIfEmpty检查是否为null,并在该列为null时返回列数据类型的默认值。

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

这将生成以下SQL语句:
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

如果我们将该列转换为可空类型,并让Convert.ToInt32()处理null值,那么代码如下:
var max = Convert.ToInt32(db.PageModules.Where(t => t.PageId == id).Max(t => (int?)t.Sort));

然后我们得到以下SQL语句:

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

我强烈推荐使用ExpressProfiler来检查执行的SQL语句:http://expressprofiler.codeplex.com/

最后一个Linq表达式也可以写成:

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

这将产生相同的SQL代码,但我更喜欢更简洁的.Max(t => (int?)t.Sort)


2
任何ORM或生成SQL工具的问题在于它们使得你容易自己搞砸。你只需要处理代码,几乎没有人查看生成的SQL。这些机械化的翻译存在的问题是它们生成“一刀切”的代码,有时比你实际需要的要大得多。感谢您在此文章中强调了这一点。最重要的是,始终检查你生成的查询语句!+1!! - Mrchief
+1 总是要检查你生成的查询语句!你可能会写出糟糕的 SQL(所以我们要对其进行分析),但当我们不对 ORM 生成的 SQL 进行分析(甚至不看它)时,ORM 总是会受到责备! - Dave
我正在尝试使用这种方法,但我的查询语法不同,所以似乎无法使选择中的lamda表达式起作用。我正在使用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());是否有一种方法可以使用您的语法进行连接,或修改我的方法以获得可空int转换? - Paul Gibson
@paul-gibson 从我的角度来看,尝试一下“.Max(t => (int?)t.Sort)”。 - Nikkelmann
哦...答案就在你的回答里...谢谢 :) - Paul Gibson

5
在表达式中放置一个可为空的转换,以确保空列表将被转换为 null。然后您可以添加默认值。
double max = pointList.Max(p=>(double?)p.X) ?? 0;

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

应该可以正常工作。

1

检查 null 对我没用,我使用了 DefaultIfEmpty()。

   int max_sequence = _dbContext.myTable
                .Where(e=>e.field1==param.field1
                && e.fieldDate==param.fieldDate
                )
                .Select(e => e.Sequence)
                .DefaultIfEmpty()
                .Max();

0

具有最大值的可空列如下所示

var maximum = objectEntity.where(entity => entity.property != null).max(entity => entity.property.HasValue);

上述语句返回实体属性的最大数量


0

0
为什么不简单地这样做:
double? maxOrNull  = pointList.
    .Where(p => p != null)
        .OrderByDescending(p => p.x)
        .FirstOrDefault();
double max = 0;
if (maxOrNull.HasValue) max = maxOrNull.Value;

这将适用于内存列表和Linq2Sql,并且可能也很高效。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接