Gebruik een Left Outer Join in LINQ

Veel ontwikkelaars vinden het een uitdaging om met LINQ een Left Outer Join te implementeren. En om eerlijk te zijn, de benodigde code lijkt op het eerste gezicht ook zeker wel wat vreemd. Je kunt het op meerdere manieren implementeren, maar in het voorbeeld dat ik verder uitwerk, gebruik ik een Group Join … Into .. en de DefaultIsEmpty() methode. Laten we eerst eens beginnen met een normale Inner Join voorbeeld. Het gebruikt de NorthWnd database (ja, daar is ie weer!) en de query haalt alle klanten (customers) op met tenminste 1 order. De SQL die we zouden moeten gebruiken, ziet er als volgt uit:

 
SELECT c.CustomerID, c.CompanyName, 
       o.OrderID, o.OrderDate 
FROM dbo.Customers c 
JOIN dbo.Orders o 
ON c.CustomerID = o.CustomerID 
Order By o.OrderID 


Wanneer je deze query in Visual Basic .NET en met LINQ wilt schrijven, dan leidt dat ongeveer de volgende code:

 
Dim result = From c In _context.Customers 
             Join o In _context.Orders 
             On c.CustomerID Equals o.CustomerID 
             Order By o.OrderID 
             Select New MyModel With {.CustomerID = c.CustomerID, 
                                      .CompanyName = c.CompanyName, 
                                      .OrderID = o.OrderID, 
                                      .OrderDate = o.OrderDate} 


Ik heb een klasse MyModel gemaakt om de data naartoe te projecteren. De twee eigenschappen met betrekking tot de order (OrderID As Integer? en OrderDate As Date?) zijn gemarkeerd als Nullable. Dit heb ik gedaan om te voorkomen dat wanneer een klant geen order heeft er ook geen exceptie optreedt.

 
Public Class MyModel 
       
  Public Property CustomerID As String 
  Public Property CompanyName As String 
  Public Property OrderID As Integer? 
  Public Property OrderDate As Date? 
   
End Class 


Maar wat moeten we doen om ook de klanten te tonen die nog niets hebben besteld, dus nog geen orders hebben? Binnen SQL zal je hiervoor een Left Outer Join toepassen, zoals je kunt zien in het onderstaande SQL statement.

   
SELECT c.CustomerID, c.CompanyName, 
       o.OrderID, o.OrderDate 
FROM dbo.Customers c 
LEFT OUTER JOIN dbo.Orders o 
ON c.CustomerID = o.CustomerID 
Order By o.OrderID 


Om hetzelfde te bewerkstelligen met behulp van LINQ kun je onder andere het Group Join … Into statement in combinatie met de DefaultIfEmpty() extension method gebruiken. In tegenstelling tot het eerder beschreven LINQ statement, gebruiken we nu een Group Join in plaats van een normale Join. Daarnaast moet je dan ook het Into-gedeelte specificeren. In dit voorbeeld geef ik de groep de alias 'co', maar dit zou je achterwege kunnen laten. Dit is echter niet het enige verschil met het eerste voorbeeld. Er is nu ook een extra 'From' statement gebruikt. Zoals je kunt zien passen we nu ook de Group.DefaultIfEmpty() extension methode toe. Of, om specifieker te zijn: co.DefaultIfempty(). Deze methode retourneert de elementen van een IEnumerable(Of T), of een singleton collection indien het resultaat leeg mocht zijn.

 
Dim result = From c In _context.Customers 
             Group Join o In _context.Orders 
             On c.CustomerID Equals o.CustomerID Into co = Group 
             From o In co.DefaultIfEmpty() 
             Order By o.OrderID 
             Select New MyModel With {.CustomerID = c.CustomerID, 
                                      .CompanyName = c.CompanyName, 
                                      .OrderID = o.OrderID, 
                                      .OrderDate = o.OrderDate} 


Voor ontwikkelaars die het gebruik van lamda's preferen, zie je hieronder hetzelfde statement, maar dan zonder het gebruik van de Group Join. Het is iets korter en persoonlijk vind ik deze syntax prettiger.

 
Dim result = From c In _context.Customers 
             From o In _context.Orders.Where(Function(o) o.CustomerID = c.CustomerID).DefaultIfEmpty() 
             Order By o.OrderID 
             Select New MyModel With {.CustomerID = c.CustomerID, 
                                      .CompanyName = c.CompanyName, 
                                      .OrderID = o.OrderID, 
                                      .OrderDate = o.OrderDate} 


In de schermafbeelding zie je dat we ook twee klanten hebben zonder orders. Een opmerking moet ik nog wel maken. Bovenstaande code werkt alleen wanneer jouw applicatie draait op .NET Framework 4.0 of hoger. Maar dat zal vandaag de dag eigenlijk geen issue zijn...



Ik heb deze post eerder in het Engels gepubliceerd op mijn eigen blog: www.obelink.com

Nieuwsbrief

Blijf op de hoogte van alles wat op VBcentral gebeurd. Meld je nu aan voor onze nieuwsbrief! »

Over ons

Wij zijn gek op het Microsoft .NET ontwikkelplatform en haar ontwikkeltalen, maar we hebben een sterke voorkeur voor Visual Basic .NET! »

Neem contact op

VBcentral.nl
Bataafseweg 20
7101 PA Winterswijk
Nederland
+31 (543) 538 388
info@vbcentral.nl