2009-08-19 7 views
4

Ich habe eine LINQ-Abfrage, um den Maximalwert einer Ganzzahlspalte abzurufen. Die Spalte ist in der Datenbank als NOT NULL definiert. Wenn Sie jedoch die MAX-Aggregatfunktion in SQL verwenden, erhalten Sie ein NULL-Ergebnis, wenn von der Abfrage keine Zeilen zurückgegeben werden.Unterschiede zwischen VB TryCast und C# "as" -Operator bei Verwendung von LINQ

Hier ist eine Beispiel-LINQ-Abfrage, die ich für die Northwind-Datenbank verwende, um zu demonstrieren, was ich mache.

var maxValue = (from p in nw.Products 
       where p.ProductID < 0 
       select p.ProductID as int?).Max(); 

C# analysiert diese Abfrage korrekt und maxValue hat eine Art int ?. Darüber hinaus, dass die SQL erzeugt wird, ist perfekt:

SELECT MAX([t0].[ProductID]) AS [value] 
FROM [Products] AS [t0] 
WHERE [t0].[ProductID] < @p0 

Die Frage ist, wie code ich dies mit VB.NET und identische Ergebnisse erhalten? Wenn ich eine direkte Übersetzung mache:

dim maxValue = (from p in Products 
       where p.ProductID < 0 
       select TryCast(p.ProductID, integer?)).Max() 

Ich bekomme einen Kompilierfehler. TryCast funktioniert nur mit Referenztypen, nicht mit Werttypen. TryCast & "as" sind in dieser Hinsicht etwas anders. C# macht ein bisschen mehr Arbeit mit Boxen, um Werttypen zu behandeln. Also, meine nächste Lösung ist CType statt TryCast zu verwenden:

dim maxValue = (from p in Products 
       where p.ProductID > 0 
       select CType(p.ProductID, integer?)).Max() 

Dies funktioniert, aber es generiert die folgende 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 

Während dies richtig ist, ist es nicht sehr sauber ist. Zugegeben, in diesem speziellen Fall würde SQL Server wahrscheinlich die Abfrage so optimieren, dass sie der C# -Version entspricht. Ich kann mir Situationen vorstellen, in denen dies nicht der Fall ist. Interessanterweise erhalte ich in der C# -Version, wenn ich eine normale Umwandlung (d. H. (Int?) P.ProductID) anstelle des Operators "as" verwende, dasselbe SQL wie die VB-Version.

Weiß jemand, ob es eine Möglichkeit gibt, die optimale SQL in VB für diese Art von Abfrage zu generieren?

+0

Dies ist Linq SQL? –

+0

Was passiert, wenn Sie DirectCast ausprobieren? – klabranche

+0

Ja, das ist LINQ to SQL. Wenn ich DirectCast verwende, erhalte ich einen Kompilierungsfehler. Nur CType funktioniert. –

Antwort

0
 Dim maxValue = ctype((From p In db.Products _ 
     Where p.ProductID > 0 _ 
     Select p.ProductID).Max(),Integer?) 
+0

So einfach! Ich dachte, ich spiele mit einer äußeren Besetzung, aber ich habe nicht die richtige Syntax gefunden. –

+0

Korrektur. Leider funktioniert das nicht. Es denkt, das Ergebnis ist Integer, nicht Integer? Wenn die Abfrage keine Zeilen zurückgibt, erhalte ich eine InvalidCast-Ausnahme. –

+0

Sie haben Recht. Posted eine hässliche Lösung oben. – Rob

0

Heilige Scheiße, was ein 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) 

Es muss ein besserer Weg sein, nicht wahr?

Dies hat den gewünschten Abfrageplan und keinen Fehler mit Nullwerten, aber kann jemand eine Säge dazu nehmen und es aufräumen?

+0

Leider hat dies nicht den richtigen Abfrageplan (nach LINQPad). Es ruft alle Zeilen ab und führt die Max-Aggregation im Speicher aus. –

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() 
+0

Beide geben mir eine "Nicht unterstützte Überlastung für Abfrage-Operator 'DefaultIfEmpty'" Fehler. Fehlt etwas? –

+0

Ah, tut mir leid, es scheint, dass LINQ to SQL DefaultIfEmpty nicht unterstützt. Dies funktioniert in einer LINQ to Objects-Abfrage einwandfrei. –

0

Wie über eine Funktion, die Nullable zurückkehrt? (Entschuldigung, wenn die Syntax nicht ganz stimmt.)

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() 
1

Kurze Antwort: Sie können.

Und dann die lange Antwort:

Die einzige Möglichkeit, die ich sehe, dass Sie dies tun können, ist explizit das Lambda mit der TypeAs Umwandlung zu erstellen.Sie können folgende Erweiterungsmethoden verwenden Sie hier um zu helfen:

<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 Willen in Folge in einem TryCast(value, T) für jede T, einschließlich Integer?.

Um dies zu nutzen, würden Sie

Dim maxValue = Products _ 
       .Where(Function(p) p.ProductID < 0) _ 
       .SelectAsNullable(Function(p) p.ProductID) _ 
       .Max() 

sagen, es ist nicht schön, aber es funktioniert. (Dies erzeugt dieselbe Abfrage wie C#.) Solange Sie SelectAsNullable nicht innerhalb einer Unterabfrage aufrufen, sind Sie in Ordnung.

könnte eine weitere Option, dass Sie ein Doppel wählen, das heißt,

from p in Products 
where p.ProductID < 0 
select p.ProductID 
select p.ProductID as int? 

in C# parlance

erhalten zu benutzen

Dim maxValue = (From p In Products _ 
       Where p.ProductID < 0 
       Select p.ProductID) _ 
       .SelectAsNullable(Function(id) id) _ 
       .Max() 

Das Problem dabei ist. Es ist möglich, dass LINQ to SQL immer noch eine Unterabfrage erzeugt.

hierfür Wie auch immer, können Sie ein zusätzliches Erweiterungsmethode

<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 

Vereinfachung der LINQ-Abfrage weiter

Dim maxValue = (From p In Products _ 
       Where p.ProductID < 0 
       Select p.ProductID) _ 
       .SelectAsNullable() _ 
       .Max() 

erstellen Aber wie gesagt, das hängt von der LINQ-Anbieter.

0

Warum nicht das Äquivalent einer isnull-Prüfung in die Abfrage erstellen?

Tut mir leid, wenn das nicht funktioniert - ich teste es nicht wirklich an diesem Ende, nur Spaghetti an die Wand werfen!

Verwandte Themen