最大或默认?
从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
才会返回它。 要确定哪个选项更有效( Concat
, FirstOrDefault
或Take(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; }
用法,在int
types的列或属性上命名为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