惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

小众软件
小众软件
博客园_首页
博客园 - 聂微东
T
Tailwind CSS Blog
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
J
Java Code Geeks
The Cloudflare Blog
aimingoo的专栏
aimingoo的专栏
Martin Fowler
Martin Fowler
D
Docker
人人都是产品经理
人人都是产品经理
WordPress大学
WordPress大学
博客园 - 三生石上(FineUI控件)
Microsoft Azure Blog
Microsoft Azure Blog
Recent Announcements
Recent Announcements
Apple Machine Learning Research
Apple Machine Learning Research
阮一峰的网络日志
阮一峰的网络日志
B
Blog RSS Feed
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
Microsoft Security Blog
Microsoft Security Blog
L
LangChain Blog
Jina AI
Jina AI
博客园 - Franky
D
DataBreaches.Net

heise online News

Machine Learning mit Python – KI und Deep Learning in 5 Sessions erklärt Porsche-Chef Leiters plant umfassenden Konzernumbau Studie: KI bleibt oft im Testlauf stecken iX-Workshop: Grundlagen und Prinzipien eines modernen IT-Managements Missing Link: Aus für De-Mail – warum das „@“ das eingekringelte „e“ besiegte Top 10: Android Auto & Carplay nachrüsten – das beste Display fürs Auto im Test BOS-Funk: ETSI standardisiert Funk für Behörden Repair-Cafés jubeln: Bundestag beschließt Ökodesign-Reform für Nachhaltigkeit Google wehrt sich gegen Monopol-Urteil CERN-Rat beschließt Strategie-Update: FCC-ee soll LHC-Nachfolger werden Product Owner AI Day 2026: Konferenz und Workshop für KI im Produktmanagement Taskforce sieht keine Knappheit bei Kerosin Aus dem Weg! E-Scooter Navee UT5 Max mit Kuhfänger und brachialer Power im Test Krankenhaus-IT: Geldmangel und schlechte Prozesse gefährden Digitalisierung „The Boroughs“: Opa entdeckt Stranger Things iX-Workshop: Lokales Active Directory gegen Angriffe absichern Google Pics und Tiger-Selfies – die Fotonews der Woche 21/2025 Fitbit-App bekommt großes Update auf Version 5.0 und heißt jetzt Google Health Zwischen Wellen, Weite und Wissenschaft: Die Bilder der Woche 21 Sonnenenergie effizient speichern und nutzen | c’t uplink Cyberangriff auf Abrechnungsdienstleister betrifft viele Kliniken Lizenzstreit und Cloud-Zwang: Bambu Lab unter massivem Druck Vom Postweg ins BundID-Konto: Bundestag stimmt für digitales Führungszeugnis Windows 11 ist ein kompletter Verkehrsunfall Europol legt VPN-Dienst lahm TV-Deals zur WM: Die besten Fernseher von OLED bis XXL zum Tiefstpreis Nvidia will mit Vera-Prozessoren nach der CPU-Krone greifen Googles XR-Brillen auf der I/O: Project Aura & Prototyp ausprobiert Proxmox VE 9.2 mit Dynamic Load Balancer und Linux Kernel 7.0 Großstadt blockiert Überwachungssoftware Palantir
Neu in .NET 10.0 [24]: LINQ-Operatoren RightJoin() und Le...
heise online · 2026-05-22 · via heise online News

.NET 10.0 führt die LINQ-Operatoren LeftJoin() und RightJoin() ein, die auch in Entity Framework Core 10.0 mit allen Datenbankmanagementsystemen funktionieren: Der Object Relational Mapper (ORM) übersetzt sie in entsprechende SQL-Befehle, siehe die zugehörigen Issues zu LeftJoin und RightJoin.

Der Dotnet-Doktor – Holger Schwichtenberg

Dr. Holger Schwichtenberg ist technischer Leiter des Expertennetzwerks www.IT-Visions.de, das mit 53 renommierten Experten zahlreiche mittlere und große Unternehmen durch Beratungen und Schulungen sowie bei der Softwareentwicklung unterstützt. Durch seine Auftritte auf zahlreichen nationalen und internationalen Fachkonferenzen sowie mehr als 90 Fachbücher und mehr als 1500 Fachartikel gehört Holger Schwichtenberg zu den bekanntesten Experten für .NET und Webtechniken in Deutschland.

Zuvor musste man einen Left (Outer) Join und Right (Outer) Join wie in LINQ-to-Objects umständlich mit GroupJoin() und SelectMany() sowie DefaultIfEmpty() bilden.

Einige Codebeispiele zeigen im Folgenden den Einsatz von RightJoin() und LeftJoin() bei Entity Framework Core 10.0 in Verbindung mit einer relationalen Datenbank. Auch in Entity Framework konnte man das Ergebnis von RightJoin() und LeftJoin() bisher schon erzielen via GroupJoin() und SelectMany() mit DefaultIfEmpty(). Die Beispiele beginnen mit der alten Variante und zeigen dann die neue Variante.

betterCode() .NET 11.0

(Bild: King / stock.adobe.com)

Das ist neu in .NET 11.0: Dr. Holger Schwichtenberg und weitere Experten präsentieren am 17. November 2026 auf der Online-Konferenz betterCode() .NET 11.0 die Änderungen für Entwicklerinnen und Entwickler in .NET SDK, C# 15.0 und mehr. Bis zur Veröffentlichung des Programms sind vergünstigte Blind-Bird-Tickets verfügbar.

Folgender Code zeigt LeftJoin() in einer Datenbank mit den Tabellen "Flight" und "Pilot":

/// <summary>
 /// LeftJoin(): Suche alle Flüge, zu denen es keinen Piloten gibt
 /// </summary>
 public void EFC10_LeftJoin()
 {
  CUI.Demo(nameof(EFC10_LeftJoin));
  var ctx = new DA.WWWings.WwwingsV1EnContext();
 
  #region --------------- ALT
  CUI.H2("ALT: Suche alle Flüge, zu denen es keinen Piloten gibt via GroupJoin() und SelectMany()");
  var fluegeOhnePilotAlt = ctx.Flights
    .GroupJoin(
        ctx.Pilots,
        f => f.PilotPersonId,
        p => p.PersonId,
        (f, pilots) => new { Flight = f, Pilots = pilots.DefaultIfEmpty() }
    )
    .SelectMany(
        fp => fp.Pilots,
        (fp, p) => new
        {
         fp.Flight.FlightNo,
         fp.Flight.Departure,
         fp.Flight.Destination,
         fp.Flight.FlightDate,
         PilotId = fp.Flight.PilotPersonId == null ? "n/a" : fp.Flight.PilotPersonId.ToString(),
         GivenName = p.Employee.Person.GivenName,
         Surname = p.Employee.Person.Surname
        }
    )
    .Where(x => x.Surname == null)
    .Take(20)
    .ToList();
 
  Console.WriteLine("Gefundene Flüge: " + fluegeOhnePilotAlt.Count);
  foreach (var item in fluegeOhnePilotAlt)
  {
   Console.WriteLine($"{item.FlightNo} {item.Departure}->{item.Destination} am {item.FlightDate}: Pilot {item.PilotId} {item.GivenName} {item.Surname}");
  }
  #endregion
 
  #region --------------- NEU
  CUI.H2("NEU: Suche alle Flüge, zu denen es keinen Piloten gibt via LeftJoin()");
 
  var fluegeOhnePilotNeu = ctx.Flights
    .LeftJoin(
        ctx.Pilots,
        f => f.PilotPersonId,
        p => p.PersonId,
        (f, p) => new
        {
         f.FlightNo,
         f.Departure,
         f.Destination,
         f.FlightDate,
         PilotId = f.PilotPersonId == null ? "n/a" : f.PilotPersonId.ToString(),
         p.Employee.Person.GivenName,
         p.Employee.Person.Surname,
        }).Where(x => x.Surname == null).Take(20).ToList();
 
  Console.WriteLine("Gefundene Flüge: " + fluegeOhnePilotNeu.Count);
  foreach (var item in fluegeOhnePilotNeu)
  {
   Console.WriteLine($"{item.FlightNo} {item.Departure}->{item.Destination} am {item.FlightDate}: Pilot {item.PilotId} {item.GivenName} {item.Surname}");
  }
  #endregion
 
  #region --------------- Kontrolle
  CUI.H3("Zur Kontrolle:");
  // Zur Kontrolle:
  if (fluegeOhnePilotNeu.Count() > 0)
  {
   var f = ctx.Flights.Find(fluegeOhnePilotNeu[0].FlightNo);
   Console.WriteLine(f.ToNameValueString());
  }
  #endregion
 
 }

Aus diesem LINQ-Befehl mit LeftJoin() entsteht folgender SQL-Befehl:

SELECT TOP(@p) [f].[FlightNo], [f].[Departure], [f].[Destination], [f].[FlightDate], CASE
  WHEN [f].[Pilot_PersonID] IS NULL THEN 'n/a'
  ELSE COALESCE(CONVERT(varchar(11), [f].[Pilot_PersonID]), '')
END AS [PilotId], [p0].[GivenName], [p0].[Surname]
FROM [Operation].[Flight] AS [f]
LEFT JOIN [People].[Pilot] AS [p] ON [f].[Pilot_PersonID] = [p].[PersonID]
LEFT JOIN [People].[Employee] AS [e] ON [p].[PersonID] = [e].[PersonID]
LEFT JOIN [People].[Person] AS [p0] ON [e].[PersonID] = [p0].[PersonID]
WHERE [p0].[Surname] IS NULL

Folgender Code zeigt RightJoin() in einer Datenbank mit den Tabellen "Flight" und "Pilot":

/// <summary>
 /// Gibt zu den letzten drei angelegten Piloten alle Flüge aus
 /// </summary>
 public void EFC10_RightJoin()
 {
  CUI.Demo(nameof(EFC10_RightJoin));
  var ctx = new DA.WWWings.WwwingsV1EnContext();
 
  CUI.H2("Alt: Gibt zu den letzten drei angelegten Piloten alle Flüge aus via GroupJoin() und SelectMany()");
 
  var ctx2 = new DA.WWWings.WwwingsV1EnContext();
 
  #region --------------- ALT
  var pilotenMitFlugAlt = ctx.Pilots
    .OrderByDescending(x => x.PersonId)
    .Take(3)
    .GroupJoin(
        ctx.Flights,
        p => p.PersonId,
        f => f.PilotPersonId,
        (p, flights) => new { Pilot = p, Flights = flights.DefaultIfEmpty() }
    )
    .SelectMany(
        pf => pf.Flights,
        (pf, f) => new
        {
         PilotId = pf.Pilot.PersonId,
         pf.Pilot.Employee.Person.GivenName,
         pf.Pilot.Employee.Person.Surname,
         Flight = f,
         f.Departure,
         f.Destination,
        }
    )
    .OrderBy(x => x.PilotId)
    .ToList();
 
  foreach (var p in pilotenMitFlugAlt)
  {
   Console.WriteLine($"Pilot #{p.PilotId} {p.GivenName} {p.Surname} fliegt " + (p.Flight != null ? $"Flug #{p.Flight?.FlightNo} {p.Flight.Departure}->{p.Flight.Destination} am {p.Flight.FlightDate}" : "bisher keinen Flug"));
  }
  Console.WriteLine("Anzahl: " + pilotenMitFlugAlt.Count);
  #endregion
 
  #region --------------- NEU
  CUI.H2("Neu: Gibt zu den letzten drei angelegten Piloten alle Flüge aus via RightJoin()");
  var pilotenMitFlugNeu = ctx.Flights
    .RightJoin(
        ctx.Pilots.OrderByDescending(x => x.PersonId).Take(3),
        f => f.PilotPersonId,
        p => p.PersonId,
        (f, p) => new
        {
         PilotId = p.PersonId,
         p.Employee.Person.GivenName,
         p.Employee.Person.Surname,
         Flight = f,
         f.Departure,
         f.Destination,
        }).OrderBy(x => x.PilotId).ToList();
  
  foreach (var p in pilotenMitFlugNeu)
  {
   Console.WriteLine($"Pilot #{p.PilotId} {p.GivenName} {p.Surname} fliegt " + (p.Flight != null ? $"Flug #{p.Flight?.FlightNo} {p.Flight.Departure}->{p.Flight.Destination} am {p.Flight.FlightDate}" : "bisher keinen Flug"));
  }
  Console.WriteLine("Anzahl: " + pilotenMitFlugNeu.Count);
  #endregion
 
  #region --------------- Kontrolle
  CUI.H2("Nur zur Kontrolle: Gibt zu den letzten drei angelegten Piloten alle Flüge aus via Navigation Property");
 
  var pilotenMitFlug2 = ctx.Pilots.Include(p => p.Employee).ThenInclude(p => p.Person).Include(p => p.Flights).OrderByDescending(x => x.PersonId).Take(3).OrderBy(x => x.PersonId).ToList();
  int count = 0;
  foreach (Pilot p in pilotenMitFlug2)
  {
   if (p.Flights.Count == 0)
   {
    count++;
    Console.WriteLine($"Pilot #{p.PersonId} {p.Employee.Person.GivenName} {p.Employee.Person.Surname} fliegt bisher keinen Flug");
   }
   else
   {
    foreach (var f in p.Flights)
    {
     count++;
     Console.WriteLine($"Pilot #{p.PersonId} {p.Employee.Person.GivenName} {p.Employee.Person.Surname} fliegt Flug #{f.FlightNo} {f.Departure}->{f.Destination} am {f.FlightDate}");
   
    }
   }
  }
  Console.WriteLine("Anzahl: " + count);
  #endregion
 }

Aus dem LINQ-Befehl mit RightJoin() entsteht folgender SQL-Befehl:

SELECT [p0].[PersonID] AS [PilotId], [p1].[GivenName], [p1].[Surname], [f].[FlightNo], [f].[Airline], [f].[Departure], [f].[Destination], [f].[FlightDate], [f].[FreeSeats], [f].[Memo], [f].[NonSmokingFlight], [f].[Pilot_PersonID], [f].[Seats], [f].[Timestamp]
FROM [Operation].[Flight] AS [f]
RIGHT JOIN (
  SELECT TOP(@p) [p].[PersonID]
  FROM [People].[Pilot] AS [p]
  ORDER BY [p].[PersonID] DESC
) AS [p0] ON [f].[Pilot_PersonID] = [p0].[PersonID]
INNER JOIN [People].[Employee] AS [e] ON [p0].[PersonID] = [e].[PersonID]
INNER JOIN [People].[Person] AS [p1] ON [e].[PersonID] = [p1].[PersonID]
ORDER BY [p0].[PersonID]

Es sei zudem explizit darauf hingewiesen, dass man die neuen Operatoren RightJoin() und LeftJoin() einschließlich der vorher schon vorhandenen Operatoren Join() und GroupJoin() nur für die Verbindung von Tabellen braucht, für die es im Objektmodell keine Navigationsbeziehung gibt.

So kann man statt des aufwendigen RightJoin() bei einer vorhandenen Navigationsbeziehung im Objektmodell dasselbe Ausgabeergebnis mit einem Include() erreichen. In diesem Fall erhält man allerdings keine flache Liste mit Daten aus Pilot und Flug, sondern eine Objekthierarchie, daher zwei verschachtelte foreach-Schleifen.

Folgender Code verwendet ein Include() über Navigationsbeziehung statt RightJoin():

CUI.H2("Gibt zu den letzten drei angelegten Piloten alle Flüge aus via Navigation Property");
 
 var pilotenMitFlug2 = ctx.Pilots.Include(p => p.Employee).ThenInclude(p => p.Person).Include(p => p.Flights).OrderByDescending(x => x.PersonId).Take(3).OrderBy(x => x.PersonId).ToList();
 
 foreach (Pilot p in pilotenMitFlug2)
 {
  if (p.Flights.Count == 0)
  {
   Console.WriteLine($"Pilot #{p.PersonId} {p.Employee.Person.GivenName} {p.Employee.Person.Surname} fliegt bisher keinen Flug");
  }
  else
  {
   foreach (var f in p.Flights)
   {
    Console.WriteLine($"Pilot #{p.PersonId} {p.Employee.Person.GivenName} {p.Employee.Person.Surname} fliegt Flug #{f.FlightNo} {f.Departure}->{f.Destination} am {f.FlightDate}");
   }
  }
 }

Entity Framework Core führt dabei nur Inner Joins aus:

SELECT [p0].[PersonID], [p0].[FlightHours], [p0].[FlightSchool], [p0].[LicenseDate], [p0].[LicenseType], [e].[PersonID], [e].[EmployeeNo], [e].[HireDate], [e].[Supervisor_PersonId], [p1].[PersonID], [p1].[Birthday], [p1].[City], [p1].[Country], [p1].[EMail], [p1].[GivenName], [p1].[Memo], [p1].[Photo], [p1].[Surname], [f].[FlightNo], [f].[Airline], [f].[Departure], [f].[Destination], [f].[FlightDate], [f].[FreeSeats], [f].[Memo], [f].[NonSmokingFlight], [f].[Pilot_PersonID], [f].[Seats], [f].[Timestamp]
FROM (
  SELECT TOP(@p) [p].[PersonID], [p].[FlightHours], [p].[FlightSchool], [p].[LicenseDate], [p].[LicenseType]
    FROM [People].[Pilot] AS [p]
    ORDER BY [p].[PersonID] DESC
   ) AS [p0]
INNER JOIN [People].[Employee] AS [e] ON [p0].[PersonID] = [e].[PersonID]
INNER JOIN [People].[Person] AS [p1] ON [e].[PersonID] = [p1].[PersonID]
LEFT JOIN [Operation].[Flight] AS [f] ON [p0].[PersonID] = [f].[Pilot_PersonID]
ORDER BY [p0].[PersonID], [e].[PersonID], [p1].[PersonID]

(rme)