我有一个LINQ查询来检索整数列的最大值。该列在数据库中定义为NOT NULL。但是,在SQL中使用MAX聚合函数时,如果查询没有返回任何行,则会得到NULL结果。
以下是我使用针对Northwind数据库的示例LINQ查询,以演示我正在做什么。
var maxValue = (from p in nw.Products
where p.ProductID < 0
select p.ProductID as int?).Max();
C#正确解析此查询,而maxValue的类型为int?。此外,生成的SQL是完美的:
SELECT MAX([t0].[ProductID]) AS [value]
FROM [Products] AS [t0]
WHERE [t0].[ProductID] < @p0
问题是,如何使用VB.NET对此进行编码并获得相同的结果?如果我直接翻译:
dim maxValue = (from p in Products
where p.ProductID < 0
select TryCast(p.ProductID, integer?)).Max()
我收到编译错误。 TryCast仅适用于引用类型,而不适用于值类型。 TryCast&amp; “as”在这方面略有不同。 C#为装箱处理值类型做了一些额外的工作。所以,我的下一个解决方案是使用CType而不是TryCast:
dim maxValue = (from p in Products
where p.ProductID > 0
select CType(p.ProductID, integer?)).Max()
这样可行,但它会生成以下SQL:
SELECT MAX([t1].[value]) AS [value]
FROM (
SELECT [t0].[ProductID] AS [value], [t0].[ProductID]
FROM [Products] AS [t0]
) AS [t1]
WHERE [t1].[ProductID] > @p0
虽然这是正确的,但它不是很干净。当然,在这种特殊情况下,SQL Server可能会将查询优化为与C#版本相同,我可以设想这种情况可能并非如此。有趣的是,在C#版本中,如果我使用普通的强制转换(即(int?)p.ProductID)而不是使用“as”运算符,我会获得与VB版本相同的SQL。
有没有人知道是否有办法在VB中为这种类型的查询生成最佳SQL?
答案 0 :(得分:1)
简短回答:你可以。
然后答案很长:
我能看到你能做到这一点的唯一方法是显式创建包含TypeAs转换的lambda。您可以使用以下扩展方法来帮助您:
<Extension()> _
Public Module TypeAsExtensions
<Extension()> _
Public Function SelectAs(Of TElement, TOriginalType, TTargetType)( _
ByVal source As IQueryable(Of TElement), _
ByVal selector As Expression(Of Func(Of TElement, TOriginalType))) _
As IQueryable(Of TTargetType)
Return Queryable.Select(source, _
Expression.Lambda(Of Func(Of TElement, TTargetType))( _
Expression.TypeAs(selector.Body, GetType(TTargetType)), _
selector.Parameters(0)))
End Function
<Extension()> _
Public Function SelectAsNullable(Of TElement, TType As Structure)( _
ByVal source As IQueryable(Of TElement), _
ByVal selector As Expression(Of Func(Of TElement, TType))) _
As IQueryable(Of TType?)
Return SelectAs(Of TElement, TType, TType?)(source, selector)
End Function
End Module
SelectAs
将导致TryCast(value, T)
T
,包括Integer?
。
要使用它,你会说
Dim maxValue = Products _
.Where(Function(p) p.ProductID < 0) _
.SelectAsNullable(Function(p) p.ProductID) _
.Max()
它不漂亮,但它有效。 (这会生成与C#相同的查询。)只要您不在子查询中调用SelectAsNullable,就可以了。
另一种选择可能是使用
Dim maxValue = (From p In Products _
Where p.ProductID < 0
Select p.ProductID) _
.SelectAsNullable(Function(id) id) _
.Max()
这个问题是你得到一个双重选择,即
from p in Products
where p.ProductID < 0
select p.ProductID
select p.ProductID as int?
用C#表示。引用LINQ to SQL仍然可以为此生成一个子查询。
无论如何,为此你可以创建一个额外的扩展方法
<Extension()> _
Public Function SelectAsNullable(Of TType As Structure)( _
ByVal source As IQueryable(Of TType)) _
As IQueryable(Of TType?)
Return SelectAs(Of TType, TType, TType?)(source, Function(x) x)
End Function
进一步简化LINQ查询
Dim maxValue = (From p In Products _
Where p.ProductID < 0
Select p.ProductID) _
.SelectAsNullable() _
.Max()
但正如我所说,这取决于LINQ提供商。
答案 1 :(得分:0)
Dim maxValue = ctype((From p In db.Products _
Where p.ProductID > 0 _
Select p.ProductID).Max(),Integer?)
答案 2 :(得分:0)
HOLY废话是什么PITA
Dim maxValue = (From p In db.Products _
Where p.ProductID > 300 _
Select new With {.id=CType(p.ProductID, Integer?)}).Max(Function(p) p.id)
有更好的方法,对吧?
这有所需的查询计划,并且没有空值的错误,但有人可以看到它并清理它吗?
答案 3 :(得分:0)
C#
var maxValue = nw.Products
.Where(p => p.ProductID < 0)
.Select(p => p.ProductID)
.DefaultIfEmpty(int.MinValue)
.Max();
VB
Dim maxValue = nw.Products _
.Where(Function(p) p.ProductID < 0) _
.Select(Function(p) p.ProductID) _
.DefaultIfEmpty(Integer.MinValue) _
.Max()
答案 4 :(得分:0)
返回Nullable的函数怎么样? (对不起,如果语法不太正确的话。)
Function GetNullable(Of T)(val as Object)
If (val Is Nothing) Then
Return new Nullable(Of T)()
Else
Return new Nullable(Of T)(DirectCast(val, T))
End If
End Function
dim maxValue = (from p in Products
where p.ProductID < 0
select GetNullable(Of Integer)(p.ProductID)).Max()
答案 5 :(得分:0)
为什么不在查询中构建等效的isnull检查?
dim maxValue = (from p in Products
where IIf(p.ProductID=Null, 0, p.ProductID) < 0
select p.ProductID)).Max()
很抱歉,如果这不起作用 - 我实际上并没有在这方面测试它,只是把意大利面扔在墙上!