最大或默认?

从LINQ查询中获取可能不返回行的最大值的最佳方法是什么? 如果我只是这样做

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

查询返回没有行时,我得到一个错误。 我可以

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

但是对于这样一个简单的要求来说,这一点让人感觉有些迟钝。 我错过了一个更好的方法来做到这一点?

更新:这是背后的故事:我试图从子表中检索下一个资格计数器(遗留系统,不要让我开始…)。 每个病人的第一个资格行总是1,第二个是2等等(显然这不是子表的主键)。 所以,我为病人select最大的现有计数器值,然后加1来创build一个新的行。 当没有现有的孩子值,我需要查询返回0(所以加1将给我一个计数器值为1)。 请注意,我不想依赖子行的原始计数,以防遗传应用程序在计数器值中引入空位(可能)。 我不想让这个问题过于通用。

由于DefaultIfEmpty并没有在LINQ to SQL中实现,所以我对它返回的错误进行了search,发现了一个处理聚合函数中空集的引人入胜的文章 。 总结一下我发现的东西,你可以通过在你的select范围内强制转换为空值来解决这个限制。 我的VB有点生疏,但我认为它会像这样:

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

或在C#中:

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

我只是有一个类似的问题,但我在列表上使用LINQ扩展方法,而不是查询语法。 在这里也可以使用Nullable技巧:

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

听起来像是DefaultIfEmpty (未经testing的代码如下)的情况:

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

想想你在问什么!

{1,2,3,-1,-2,-3}的最大值显然是3. {2}的最大值明显是2.但空集{}的最大值是多less? 显然这是一个毫无意义的问题。 空集的最大值没有被定义。 试图得到答案是一个math错误。 任何集合的最大值本身都是该集合中的一个元素。 空集没有元素,所以声称某个特定数是该集中的最大值,而不是在该集中是一个math上的矛盾。

正如计算机在程序员要求它除以零时抛出一个exception是正确的行为,所以当程序员要求它取空集的最大值时,计算机抛出exception是正确的行为。 划分为零,以最大的空位,摆弄spacklerorke,骑飞麒麟到Neverland,都是毫无意义的,不可能的,不明确的。

现在,你究竟想要做什么?

您始终可以将Double.MinValue添加到序列中。 这将确保至less有一个元素,并且只有当Max实际上是最小值时, Max才会返回它。 要确定哪个选项更有效( ConcatFirstOrDefaultTake(1) ),您应该执行适当的基准testing。

 double x = context.MyTable .Where(y => y.MyField == value) .Select(y => y.MyCounter) .Concat(new double[]{Double.MinValue}) .Max(); 
 int max = list.Any() ? list.Max(i => i.MyCounter) : 0; 

如果列表中有任何元素(即不是空的),它将占用MyCounter字段的最大值,否则将返回0。

由于.net 3.5,您可以使用DefaultIfEmpty()传递默认值作为参数。 类似以下方法之一:

 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(); 

第一个是查询NOT NULL列时允许的,第二个是用来查询NULLABLE列的方式。 如果使用不带参数的DefaultIfEmpty(),则默认值将根据您的输出types定义,如您在“ 默认值表”中所看到的。

由此产生的select将不会是如此优雅,但它是可以接受的。

希望它有帮助。

我认为这个问题是当查询没有结果时你想要发生什么。 如果这是一个特殊情况,那么我会将查询包装在try / catch块中,并处理标准查询生成的exception。 如果查询没有返回结果是可以的,那么你需要弄清楚你想要的结果是在这种情况下。 这可能是@戴维的答案(或类似的东西会起作用)。 也就是说,如果MAX始终为正值,那么将已知的“坏”值插入列表中,只有在没有结果时才会被选中。 一般来说,我希望一个查询是最大的检索有一些数据的工作,我会去尝试/赶上路线,否则你总是被迫检查,如果你获得的价值是正确的。 我宁愿那个非例外的情况就是能够使用所获得的价值。

 Try Dim x = (From y In context.MyTable _ Where y.MyField = value _ Select y.MyCounter).Max ... continue working with x ... Catch ex As SqlException ... do error processing ... End Try 

另一种可能性是分组,类似于你在原始SQL中可能的方式:

 from y in context.MyTable group y.MyCounter by y.MyField into GrpByMyField where GrpByMyField.Key == value select GrpByMyField.Max() 

唯一的事情是(在LINQPad中再次testing)切换到VB LINQ风格在分组子句给出语法错误。 我相信这个概念相当容易find,我只是不知道如何反映在VB中。

生成的SQL将会是:

 SELECT [t1].[MaxValue] FROM ( SELECT MAX([t0].[MyCounter) AS [MaxValue], [t0].[MyField] FROM [MyTable] AS [t0] GROUP BY [t0].[MyField] ) AS [t1] WHERE [t1].[MyField] = @p0 

嵌套的SELECT看上去很痒,就像查询执行会检索所有的行,然后从检索的集合中select匹配的一个…问题是SQL Server是否优化查询,将其与将内部子句应用于内部SELECT相比。 我现在正在调查

我不熟悉解释SQL Server中的执行计划,但看起来像WHERE子句在外部SELECT时,导致该步骤的实际行数是表中的所有行,而只有匹配的行当WHERE子句在内部SELECT时。 也就是说,看起来只有1%的成本转移到了所有行被考虑的下一个步骤,无论哪种方式只有一行从SQL Server回来,所以也许这不是在大事。

但是我也有同样的担心

从原始文章中改写代码,需要定义的集合S的最大值

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

考虑到你最后的评论

只要说我知道我想要0时没有logging可供select,这肯定会对最终的解决scheme产生影响

我可以把你的问题改为:你想要{0 + S}的最大值。 它看起来像concat提出的解决scheme在语义上是正确的:-)

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

为什么不是更直接的东西:

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

只是为了让大家知道,使用Linq实体上述方法将无法正常工作…

如果你尝试做类似的事情

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

它会抛出一个exception:

System.NotSupportedException:LINQ to Entities不支持LINQexpression式节点types“NewArrayInit”。

我会build议只是在做

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

如果列表为空,则FirstOrDefault将返回0。

一个值得注意的差异是FirstOrDefault和Take(1)生成相同的SQL(根据LINQPad,无论如何),FirstOrDefault返回一个值 – 默认值 – 当没有匹配的行时,Take(1)返回没有结果…至less在LINQPad。

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

我只是有一个类似的问题,我的unit testing通过使用Max(),但失败时运行一个实时数据库。

我的解决scheme是将查询与正在执行的逻辑分开,而不是在一个查询中join它们。
我需要一个解决scheme,使用Linq对象进行unit testing(在Linq-objects Max()中使用null)和Linq-sql。

(我嘲笑select()在我的testing)

 var requiredDataQuery = _dataRepo.Select(x => new { x.NullableDate1, .NullableDate2 }); var requiredData.ToList(); var maxDate1 = dates.Max(x => x.NullableDate1); var maxDate2 = dates.Max(x => x.NullableDate2); 

效率低下? 大概。

我关心,只要我的应用程序不会下降? 不。

我敲了一个MaxOrDefault扩展方法。 没有太多的东西,但它在Intellisense中的存在是一个有用的提醒, Max在一个空序列会导致exception。 另外,如果需要,该方法允许指定默认值。

  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; } 

用法,在inttypes的列或属性上命名为Id

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

对于Entity Framework和Linq to SQL,我们可以通过定义一个扩展方法来实现这一点,该方法修改传递给IQueryable<T>.Max(...)方法的Expression

 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); } } 

用法:

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

生成的查询是相同的,它就像正常调用IQueryable<T>.Max(...)方法一样工作,但是如果没有logging,它将返回Ttypes的默认值而不是抛出一个exception