使用LINQ时VB TryCast和C#“as”运算符之间的差异

时间:2009-08-19 00:36:01

标签: c# .net vb.net linq casting

我有一个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?

6 个答案:

答案 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()

很抱歉,如果这不起作用 - 我实际上并没有在这方面测试它,只是把意大利面扔在墙上!