从可能不返回任何行的LINQ查询中获取Max值的最佳方法是什么?如果我这样做
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,等等(显然这不是子表的主键).因此,我正在为患者选择最大现有计数器值,然后向其中添加1以创建新行.当没有现有子值时,我需要查询返回0(因此添加1会给我一个计数器值1).请注意,我不想依赖子行的原始计数,以防遗留应用程序在计数器值中引入间隙(可能).我试图让这个问题过于通用我不好.
由于DefaultIfEmpty
未在LINQ to SQL中实现,因此我对其返回的错误进行了搜索,并发现了一篇引人入胜的文章,介绍了聚合函数中的空集.总结一下我发现的内容,你可以通过在你的选择中转换为可空来解决这个限制.我的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
(未经测试的代码如下):
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.但空集{}的最大值是多少?显然这是一个毫无意义的问题.简单地没有定义空集的最大值.试图得到答案是一个数学错误.任何集合的最大值本身必须是该集合中的元素.空集没有元素,因此声称某个特定数字是该集合的最大值而不在该集合中是一个数学上的矛盾.
正如程序员要求它除以零时计算机抛出异常是正确的行为,因此当程序员要求它取出空集的最大值时,计算机抛出异常是正确的行为.除以零,取出空集的最大值,摆动spacklerorke,乘坐飞行的独角兽到Neverland都是毫无意义的,不可能的,未定义的.
现在,你真正想做的是什么?
您可以随时添加Double.MinValue
到序列中.这将确保至少有一个元素,并且Max
仅当它实际上是最小值时才返回它.要确定哪个选项是更有效的(Concat
,FirstOrDefault
或Take(1)
),你应该进行适当的标杆.
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(),则默认值将定义为输出类型,如" 默认值表"中所示.
生成的SELECT不会那么优雅,但它是可以接受的.
希望能帮助到你.
我认为问题是当查询没有结果时你想要发生什么.如果这是一个例外情况,那么我将查询包装在try/catch块中并处理标准查询生成的异常.如果可以让查询返回没有结果,那么你需要弄清楚在这种情况下你想要的结果.可能是@ David的回答(或者类似的东西会起作用).也就是说,如果MAX总是为正,那么将已知的"坏"值插入列表中就足够了,只有在没有结果时才会选择该值.一般来说,我希望查询检索最大值的查询有一些数据可以处理,我会去try/catch路由,否则你总是被迫检查你获得的值是否正确.一世'
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中再次测试)切换到VB LINQ风格会给分组子句带来语法错误.我确信概念等价物很容易找到,我只是不知道如何在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看起来很蹩脚,就像查询执行会检索所有行然后从检索到的集合中选择匹配的一样......问题是SQL Server是否将查询优化为类似于将where子句应用于内部SELECT的东西.我现在正在调查......
我不太精通解释SQL Server中的执行计划,但看起来当WHERE子句在外部SELECT上时,导致该步骤的实际行数是表中的所有行,而不是匹配的行当WHERE子句在内部SELECT上时.也就是说,当考虑所有行时,看起来只有1%的成本转移到下一步,并且无论哪种方式只有一行从SQL Server返回,所以也许它在宏观方案中没有那么大的区别.
我迟到了,但我有同样的担忧......
从原始帖子中重新编写代码,您需要定义的集合S的最大值
(From y In context.MyTable _ Where y.MyField = value _ Select y.MyCounter)
考虑到你的上次评论
可以说,当没有可供选择的记录时,我知道我想要0,这肯定会对最终的解决方案产生影响
我可以将你的问题改为:你想要{0 + S}的最大值.看起来像concat的建议解决方案在语义上是正确的:-)
var max = new[]{0} .Concat((From y In context.MyTable _ Where y.MyField = value _ Select y.MyCounter)) .Max();