使用LINQ时VB TryCast和C#“as”运算符之间的差异
我有一个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和“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?
简短的回答:你可以。
然后答案很长:
我能看到你能做到这一点的唯一方法是显式创建包含TypeAs转换的lambda。 您可以使用以下扩展方法来帮助您:
_ Public Module TypeAsExtensions _ 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 _ 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
会导致任何T
的TryCast(value, 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仍然可以为此生成子查询。
无论如何,为此你可以创建一个额外的扩展方法
_ 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提供商。
Dim maxValue = ctype((From p In db.Products _ Where p.ProductID > 0 _ Select p.ProductID).Max(),Integer?)
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)
有一个更好的方法,对吧?
这有所需的查询计划,没有空值的错误,但有人可以看到它并清理它吗?
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()
返回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()
为什么不在查询中构建等效的isnull检查?
dim maxValue = (from p in Products where IIf(p.ProductID=Null, 0, p.ProductID) < 0 select p.ProductID)).Max()
对不起,如果这不起作用 - 我实际上并没有在这方面测试它,只是在墙上扔意大利面!