Entwicklung & Code
Plane 1.3.0: Update der freien Jira-Alternative
Plane schließt in Version 1.3.0 der freien Community-Version mehrere Sicherheitslücken, bringt neue Integrationen und überarbeitet die Benutzeroberfläche umfassend. Das Release führt unter anderem eine Gitea-Authentifizierung ein, öffnet die API für externe Projektzusammenfassungen und validiert Dateiuploads sowie Projektkennungen strenger. Parallel haben die Entwickler mehrere sicherheitsrelevante Schwachstellen behoben – darunter manipulierbare Rate-Limits für API-Tokens, eine SSRF-Lücke in Webhooks und CSV-Injection beim Datenexport.
Weiterlesen nach der Anzeige
Bei Plane handelt es sich um ein quelloffenes Projektmanagement- und Issue-Tracking-System für Entwickler. Es bietet Aufgabenverwaltung, Roadmaps und API-Anbindung und ist als selbst hostbare Alternative zu Jira oder Linear gedacht.
Integrationen: Gitea-Login und neue API-Endpunkte
Plane unterstützt ab Version 1.3.0 die Authentifizierung über Gitea. Teams mit selbst gehosteter Git-Infrastruktur können damit Benutzerkonten koppeln oder Single Sign-on einrichten. Neu ist außerdem eine externe Project-Summary-API, über die Drittsysteme Projektübersichten abrufen können – etwa für Dashboards oder Reporting. Webhook-Payloads lassen sich nun zudem lokalisieren.
Projektnamen und -kennungen akzeptieren keine potenziell ausführbaren Sonderzeichen mehr, was Injection-Angriffe erschwert. Im Administrationsmodus verhindert Plane außerdem, dass Admins versehentlich alle Authentifizierungsmethoden deaktivieren. Erweiterte Logs liefern detailliertere Fehlermeldungen bei Authentifizierungsproblemen und erleichtern die Fehlersuche und das Auditing.
Überarbeitete Oberfläche mit einheitlichem Designsystem
Einen großen Teil des Releases machen Änderungen an der Oberfläche aus. Plane setzt stärker auf Design-Tokens – also zentral definierte Werte für Farben, Abstände und andere UI-Parameter –, die für ein einheitlicheres Erscheinungsbild sorgen. Die Entwickler haben unter anderem die Detailansichten von Work Items, die Sidebar, Kommentare sowie die Workspace- und Mitgliederverwaltung überarbeitet. Dropdowns verhalten sich kontextsensitiver und zeigen ausgewählte Einträge oben an. Neue Ladezustände sorgen für flüssigere Übergänge, überarbeitete Themes und Editor-Farben für mehr visuelle Konsistenz.
Attachments akzeptieren jetzt auch Markdown-Dateien. Eine Zeitzonen-Einstellung auf Workspace-Ebene sorgt dafür, dass neue Projekte automatisch die richtige Zeitzone übernehmen. Die Mention-Suche nutzt jetzt Debouncing und löst damit bei schneller Eingabe weniger API-Anfragen aus. Die Navigation zeigt eingehende Aufgaben (Intake) direkt als Zähler an.
Weiterlesen nach der Anzeige
Sicherheit: Mehrere Angriffsvektoren geschlossen
Im Bereich Sicherheit verhindert eine serverseitige Absicherung nun, dass Angreifer Rate-Limits von API-Tokens manipulieren. Webhook-URLs durchlaufen eine strengere Validierung, um Server-Side Request Forgery (SSRF) zu unterbinden – also Angriffe, bei denen ein Server dazu gebracht wird, interne Ressourcen anzusprechen.
Beim CSV-Export bereinigt Version 1.3.0 eingebettete Formeln, die Tabellenprogramme wie Excel sonst ungefragt ausführen könnten. Zusätzlich blockiert eine neue Upload-Validierung potenziell schädliche Dateien, und der HTTP-Header X-Frame-Options schützt vor Clickjacking.
Bei der Internationalisierung kommt Ukrainisch als neue Sprache hinzu, die russische Übersetzung wurde erweitert. Zahlreiche Bugfixes betreffen den Editor, die Navigation, API-Endpunkte und E-Mail-Templates.
Alle Informationen zum Update finden sich in den Release Notes auf der Projektseite von Plane auf GitHub.
Lesen Sie auch
(fo)
Entwicklung & Code
Won’t fix! – Teil 1: Warum Softwareschätzungen so zuverlässig falsch sind
Manche Probleme der Softwareentwicklung sind keine Fehler, die sich beheben ließen, sondern strukturelle Eigenschaften der Disziplin. Es sind Dauerthemen, die Entwicklerinnen und Entwickler seit Jahrzehnten begleiten und auch in den nächsten Jahrzehnten begleiten werden.
Weiterlesen nach der Anzeige
Dieser Artikel ist der erste Teil einer Serie, die Probleme beleuchtet, die sich nicht wegoptimieren lassen: Won’t fix – wie irreparable Issues in GitHub-Repositories heißen.
Wie lange braucht man, um ein Osterei zu bemalen? Ein bisschen Wiese, einen Osterhasen, eine Sonne und einige Wolken am Himmel, einschließlich Ausblasen und das Einfädeln des an ein Streichholz geknoteten Fadens. 10 Minuten? 15 Minuten? Eine halbe Stunde? Oder gar eine ganze Stunde? Selbst bei einer Aufgabe, deren Anforderungen klar und deren Prozess seit der Kindheit bestens bekannt sind, bewegen sich die Antworten zwischen 10 Minuten und einer Stunde, was einer Spanne von 500 Prozent entspricht.

Golo Roden ist Gründer und CTO von the native web GmbH. Er beschäftigt sich mit der Konzeption und Entwicklung von Web- und Cloud-Anwendungen sowie -APIs, mit einem Schwerpunkt auf Event-getriebenen und Service-basierten verteilten Architekturen. Sein Leitsatz lautet, dass Softwareentwicklung kein Selbstzweck ist, sondern immer einer zugrundeliegenden Fachlichkeit folgen muss.
Die Serie „Wont‘ fix“ behandelt Probleme in der Softwareentwicklung, die sich nicht wegoptimieren lassen, mit denen man aber lernen kann umzugehen:
In der Softwarebranche gelten solche Schwankungen als inakzeptabel. Dort sollen Entwicklerinnen und Entwickler bei unvergleichlich komplexeren Aufgaben, deren Anforderungen häufig noch nicht einmal vollständig bekannt sind, verlässliche Vorhersagen über Kosten und Zeitrahmen treffen. „Wie teuer wird es, und wie lange wird es dauern?“ sind die gängigen Fragen, auf die Kundinnen und Kunden eine belastbare Antwort erwarten.
Doch Studien in der Softwarebranche zeigen seit Jahrzehnten ein konsistentes Bild: Projekte dauern länger als geplant, kosten mehr als veranschlagt und liefern weniger als versprochen. Dieses Muster zieht sich durch alle Themen, alle Unternehmensgrößen und alle Methoden. Weder Wasserfall noch Agile noch irgendetwas dazwischen haben daran grundlegend etwas geändert. Dass verlässliche Vorhersagen regelmäßig scheitern, überrascht bei näherer Betrachtung weniger als die Erwartung, dass es passen sollte.
Was heißt hier eigentlich schätzen?
Bevor sich die Frage beantworten lässt, warum Softwareschätzungen so häufig danebenliegen, lohnt es sich, einen Schritt zurückzutreten: Warum wird überhaupt geschätzt? Die Antwort ist weniger offensichtlich, als sie scheint, denn hinter der Frage „Wie lange dauert das?“ verbergen sich ganz unterschiedliche Anliegen.
Weiterlesen nach der Anzeige
Manchmal geht es um eine Kostenvorhersage. Ein Unternehmen möchte wissen, ob sich eine Investition lohnt, und benötigt eine Zahl für die Budgetplanung. Manchmal geht es um eine Zeitvorhersage, etwa weil ein Marktereignis einen Termin diktiert. Und manchmal geht es schlicht um die Machbarkeit: Ist das überhaupt realisierbar und wenn ja, in welcher Größenordnung bewegt sich der Aufwand?
Diese Unterscheidung wird in der Praxis selten getroffen, obwohl sie entscheidend ist. Wer die Machbarkeit prüfen will, benötigt keine Stundenzahl. Wer ein Jahresbudget plant, benötigt keine Präzision auf Tagesebene. Und wer einen festen Liefertermin benötigt, dem hilft eine Aufwandsschätzung in Story-Points wenig. Hinzu kommt ein weiteres Missverständnis: Eine Schätzung wird oft als Zusage behandelt, obwohl sie ihrem Wesen nach eine Annäherung unter Unsicherheit ist. Wer eine Schätzung abgibt, kommuniziert eine Erwartung. Wer eine Schätzung entgegennimmt, versteht häufig ein Versprechen. Dieser Unterschied mag subtil klingen, ist aber in der Praxis die Quelle zahlloser Konflikte zwischen Entwicklungsteams und ihren Auftraggebern.
Es heißt Entwicklung, nicht Produktion
Die Softwarebranche leistet einer fundamentalen Fehleinschätzung Vorschub, indem sie über Produktionseffizienz, Softwarefabriken und Durchsatz spricht, als ginge es um die Fertigung von Bauteilen am Fließband. Diese Formulierungen suggerieren einen industriellen, reproduzierbaren Prozess, bei dem Schätzungen möglich und sinnvoll sind, weil das Fließband den Takt bestimmt.
Softwareentwicklung aber ist keine Produktion. Der Name sagt es bereits: Es ist Entwicklung. Ein kreativer, in gewissem Sinne sogar künstlerischer Prozess. Der herausragende US-amerikanische Informatiker Donald E. Knuth nannte sein Lebenswerk „The Art of Computer Programming“ und prägte mit dem Konzept des Literate Programming die Idee, dass Programme nicht nur funktionieren, sondern auch lesbar und ästhetisch sein sollten. Seine damit verbundene Frage hat nichts von ihrer Aktualität verloren: „Wann haben Sie das letzte Mal einen angenehmen Abend in einem bequemen Sessel verbracht und dabei ein gutes Programm gelesen?“ Dass diese Frage für die meisten Menschen absurd klingt, sagt viel über das Missverständnis der Branche gegenüber ihrem eigenen Tun.
Software ist ausführbares Wissen. Und Wissen lässt sich nicht auf Verlangen in einem vorgegebenen zeitlichen oder finanziellen Rahmen erzeugen. Es entsteht durch Entdecken, Erfinden und Verstehen. Und dieser Prozess lässt sich nicht takten. Ideen kommen, wenn der kreative Freiraum dafür besteht, nicht wenn der Projektplan sie vorsieht. Fred Brooks unterschied bereits 1986 in seinem Essay „No Silver Bullet“ zwischen wesentlicher und akzidentieller Komplexität. Die akzidentielle Komplexität lässt sich durch bessere Werkzeuge und Methoden reduzieren: bessere Editoren, leistungsfähigere Frameworks, effizientere Build-Systeme. Die wesentliche Komplexität aber steckt im Problem selbst und kein Tool der Welt kann sie beseitigen. Sie ist der Teil der Aufgabe, der übrig bleibt, wenn alle technischen Hürden beseitigt sind.
Wer Software schätzt, versucht im Grunde vorherzusagen, wie lange es dauert, ein Problem zu verstehen, das noch niemand vollständig verstanden hat. Dass diese Vorhersage selten stimmt, ist weniger erstaunlich als die Tatsache, dass sie trotzdem ständig verlangt wird.
Die Reise von Berlin nach München
Eine Metapher macht das greifbar. Angenommen, jemand soll vorhersagen, wie lange eine Wanderung von Berlin nach München dauert. Die Rechnung scheint einfach: Entfernung auf der Karte abmessen, eine durchschnittliche Gehgeschwindigkeit ansetzen, fertig. Auf dem Papier sieht das überzeugend aus.
In der Realität nicht. Die Karte zeigt keine Höhenunterschiede, zumindest nicht im erforderlichen Detail. Sie zeigt nicht, dass Wege gesperrt sein können, dass Flüsse überquert werden müssen und erst eine Brücke gefunden werden will, dass das Wetter umschlagen kann und dass Umwege unvermeidlich sind. Wer schon einmal gewandert ist, weiß, dass selbst auf einer vermeintlich bekannten Route Überraschungen lauern: ein umgestürzter Baum, eine Baustelle, ein falsch abgebogener Pfad, der einen Kilometer extra kostet. Der Plan und der tatsächliche Weg haben wenig miteinander zu tun.
Softwareprojekte verhalten sich genauso. Die Spezifikation ist die Karte, der Code ist der Weg. Und zwischen beiden liegt das unbekannte Gelände: Bibliotheken, die sich anders verhalten als dokumentiert, Anforderungen, die sich erst während der Umsetzung als widersprüchlich herausstellen, technische Schulden in bestehenden Systemen, die niemand auf der Karte verzeichnet hat.
In der Softwareentwicklung beschreibt der Cone of Uncertainty genau dieses Phänomen. Er besagt, dass die Unschärfe einer Schätzung zu Projektbeginn am größten ist und erst im Verlauf des Projekts abnimmt, je mehr über das Problem und seine Lösung bekannt wird. Zu Beginn eines Projekts kann die tatsächliche Dauer um den Faktor vier nach oben oder unten vom Schätzwert abweichen. Erst wenn ein signifikanter Teil der Arbeit bereits geleistet ist, nähert sich die Schätzung der Realität an. Das Paradoxe daran: Genau zu Beginn, wenn die Unsicherheit am größten ist, werden Budgets festgelegt, Verträge geschlossen und Timelines kommuniziert. Es ist, als würde man die Wanderung planen, ohne je einen Blick auf das Gelände geworfen zu haben, und dann den errechneten Zeitplan als verbindlich betrachten.
Wenn der Kopf mitspielt
Auch wenn die strukturellen Schwierigkeiten bekannt sind, bleibt ein zweites Problem: Der menschliche Verstand arbeitet systematisch gegen realistische Einschätzungen. Die Psychologen Daniel Kahneman und Amos Tversky beschrieben in den 1970er-Jahren die Planning Fallacy, die Beobachtung, dass Menschen Projekte systematisch zu optimistisch einschätzen, selbst wenn sie über Erfahrung mit ähnlichen Vorhaben verfügen. Das Bemerkenswerte an der Planning Fallacy ist, dass Wissen um ihre Existenz kaum hilft: Selbst wer weiß, dass die eigene letzte Schätzung um den Faktor drei daneben lag, wird die nächste Schätzung nicht entsprechend korrigieren. Der Optimism Bias sorgt dafür, dass man Risiken und Hindernisse unterschätzt. Der Ankereffekt bewirkt, dass die erste genannte Zahl alle weiteren Schätzungen in ihre Richtung zieht, unabhängig davon, wie gut diese erste Zahl begründet war.
In Teams kommen Gruppeneffekte hinzu. Beim Planning-Poker etwa, einer verbreiteten Schätzmethode in agilen Teams, schätzen alle Beteiligten gleichzeitig, um genau diese gegenseitige Beeinflussung zu reduzieren. Die Methode enthält noch ein weiteres stilles Eingeständnis: Die verwendeten Werte entsprechen einer angenäherten Fibonacci-Folge. Kleine Aufgaben lassen sich feingranular einordnen, deshalb gibt es die Werte 1, 2, 3 und 5. Große Aufgaben hingegen lassen sich nur noch grob schätzen. Dort genügt eine 13, eine 20 oder eine 40. Die Skala selbst bildet ab, was die Branche längst weiß: je größer die Aufgabe, desto unzuverlässiger die Schätzung.
Ein weiterer oft unterschätzter Faktor ist die Verwechslung von Code Complete mit Feature Complete. Viele Schätzungen enden gedanklich beim abschließenden Commit. Aber zum fertigen Feature gehören auch Debugging, Fehlersuche, Testen, Integration, Code Review, Dokumentation und Deployment. Diese Tätigkeiten machen häufig den größeren Teil des Gesamtaufwands aus, tauchen in der ursprünglichen Schätzung aber häufig nicht auf.
Dazu kommt ein Phänomen, das Fred Brooks bereits 1975 in „The Mythical Man-Month“ beschrieb und das als Brook’s Law bekannt ist: Wenn ein Softwareprojekt in Verzug gerät und zusätzliche Mitarbeiterinnen und Mitarbeiter hinzugezogen werden, wird es noch später fertig. Der Kommunikationsaufwand in einem Team wächst quadratisch mit der Teamgröße und die Einarbeitung neuer Kolleginnen und Kollegen bindet Kapazitäten bei denen, die ohnehin schon unter Druck stehen. Zwischen je zwei Personen entsteht ein Kommunikationskanal. Ein Team von 5 Personen hat deshalb 10 solcher Kanäle, ein Team von 10 bereits 45. Jede zusätzliche Person bringt nicht nur einen neuen Kanal mit, sondern so viele, wie es bereits Teammitglieder gibt. Mit anderen Worten: Jede zusätzliche Person erhöht nicht nur die Kapazität, sondern auch den Koordinationsaufwand und ab einem bestimmten Punkt überwiegt der Aufwand den Nutzen. Diese Erkenntnis ist seit einem halben Jahrhundert bekannt und wird trotzdem regelmäßig ignoriert.
Pragmatische Auswege
Wenn Schätzungen aus strukturellen und psychologischen Gründen unzuverlässig sind, stellt sich die Frage, ob es bessere Ansätze gibt. Die gibt es tatsächlich, auch wenn keiner das Grundproblem löst.
Die Bewegung unter dem Schlagwort No Estimates vertritt die radikalste Position: Statt besser zu schätzen, sollte man die Frage ändern. Wenn Arbeit in so kleine Einheiten zerlegt wird, dass jede davon in wenigen Tagen abgeschlossen werden kann, verliert die Frage nach der Gesamtschätzung an Bedeutung. Der Fortschritt wird nicht prognostiziert, sondern gemessen. Was gestern geliefert wurde, ist ein besserer Indikator für morgen als jede Schätzung von vor drei Monaten.
Wer dennoch eine Vorhersage benötigt, findet in probabilistischen Methoden einen Mittelweg. Monte-Carlo-Simulationen etwa nutzen historische Daten, um nicht einen einzelnen Schätzwert, sondern eine Wahrscheinlichkeitsverteilung zu erzeugen: „Mit 85 Prozent Wahrscheinlichkeit sind wir in acht bis zwölf Wochen fertig“ ist eine ehrlichere Aussage als „Das dauert zehn Wochen“. Sie macht die Unsicherheit sichtbar, statt sie hinter einer Pseudopräzision zu verstecken. Und sie zwingt alle Beteiligten, über Risikotoleranz zu sprechen, statt über vermeintlich feste Termine.
Der vielleicht wirksamste Ansatz ist jedoch ein grundsätzlich anderer Umgang mit der Frage. Statt ein Projekt als monolithischen Block zu denken, der vorab vollständig geschätzt und geplant werden muss, lässt sich in vielen Fällen iterativ vorgehen. Schritt für Schritt, solange, bis das Ergebnis gut genug ist oder bis das investierte Budget erschöpft ist. Wenn eine Software von Anfang an Wertschöpfung liefert, wenn jede Iteration ein nutzbares Ergebnis hervorbringt, dann verschiebt sich die Frage von „Wann ist alles fertig?“ zu „Lohnt sich der nächste Schritt noch?“. Die Entscheidung über Fortführung oder Abbruch wird dann nicht auf Basis einer Schätzung getroffen, die Monate zurückliegt, sondern auf Basis des tatsächlich erreichten Fortschritts und des investierten Aufwands.
Das funktioniert nicht in jedem Kontext, etwa wenn regulatorische Anforderungen ein vollständiges System voraussetzen oder wenn ein physisches Produkt zu einem festen Termin ausgeliefert werden muss. Aber in überraschend vielen Fällen ist es ein tragfähiger Ansatz, der zumindest eine ehrliche Diskussion wert ist. Er verlagert das Risiko von einer großen Vorabentscheidung auf viele kleine Entscheidungen im Verlauf, bei denen jeweils mehr Wissen zur Verfügung steht als zu Beginn.
Der Glaube an das Zählen und Messen
Der deutsche Typograph Paul Renner schrieb einst: „Der Glaube an das Zählen und Messen verführt in allen Künsten zu den gröbsten Fehlern.“ Die Softwarebranche ist diesem Trugschluss erlegen, dass sich ein kreativer Prozess mit denselben Methoden planen ließe wie ein industrieller. Softwareschätzungen scheitern nicht an mangelnder Disziplin, nicht an fehlender Erfahrung und nicht an schlechten Werkzeugen. Sie scheitern an der Natur von Software als ausführbares Wissen und an der Natur menschlichen Denkens, gleichzeitig und auf eine Weise, die sich nicht einfach wegoptimieren lässt.
Wer das akzeptiert, kann anders mit Schätzungen umgehen. Andere Fragen stellen, andere Formate wählen, andere Erwartungen setzen. Schätzungen als das behandeln, was sie sind: Annäherungen unter Unsicherheit, die regelmäßig überprüft und angepasst werden müssen. Und vor allem aufhören, Schätzungen als Versprechen zu behandeln, denn genau das sind sie nicht, und genau das können sie nicht sein.
Die Illusion, ein kreativer Prozess ließe sich wie eine Fertigungsstraße planen, ist dabei nur eine von mehreren, denen die Softwarebranche erlegen ist. Eine andere, mindestens ebenso hartnäckige, betrifft die Frage nach der Fehlerfreiheit: die Vorstellung, dass sich Bugs mit genügend Sorgfalt und den richtigen Werkzeugen vollständig vermeiden ließen.
Warum auch das eine Illusion ist, und warum selbst „Hello World“ einen Bug enthält, ist Thema des zweiten Teils.
(who)
Entwicklung & Code
Software Testing: So ändert Agentic Engineering die Softwareentwicklung
Richard Seidl spricht im Interview mit Benedikt Stemmildt über die Veränderung, die Agentic Engineering in der Softwareentwicklung anstößt. Im Mittelpunkt steht die Frage, warum dieser Begriff besser passt als der des Vibe Coding. Die Diskussion dreht sich um den praktischen Umgang mit Code, Qualitätsprinzipien und die wachsende Rolle von Architekturarbeit.
Weiterlesen nach der Anzeige

Richard Seidl ist Berater, Speaker und Podcast-Host. Für ihn ist klar: Wer heute exzellente Software kreieren möchte, denkt den Entwicklungsprozess ganzheitlich: Menschen, Kontext, Methoden und Tools. Er hat seine Erfahrungen in acht Fachbüchern veröffentlicht, betreibt erfolgreich zwei Community-Podcasts und ist Beirat der heise-Konferenz betterCode() Testing.
„Wenn du schlechte Prinzipien hast und AI anwendest, dann wirst du schneller schlechter. Und wenn du gute Prinzipien hast, wirst du schneller besser.“ – Benedikt Stemmildt
Als Technologe seit Kindheitstagen widmet sich Benedikt Stemmildt seit über 20 Jahren der Aufgabe, die Arbeitswelt von Entwicklerinnen und Entwicklern zu verbessern. Developer Experience ist seine Leidenschaft und Mission: Teams dabei zu unterstützen, die neue Arbeitsweise des Agentic Software Engineering zu adaptieren und gewinnbringend einzusetzen.
Softwarequalität im Gespräch
Dieses Format fokussiert sich auf Softwarequalität: Ob Testautomatisierung, Qualität in agilen Projekten, Testdaten oder Testteams – Richard Seidl und seine Gäste betrachten die Dinge, die die Qualität in der Softwareentwicklung steigern.
Die aktuelle Episode ist auch auf Richard Seidls Blog verfügbar.
Weiterlesen nach der Anzeige
(who)
Entwicklung & Code
Neu in .NET 10.0 [24]: LINQ-Operatoren RightJoin() und LeftJoin() in EF Core
.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.
Weiterlesen nach der Anzeige

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.
(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“:
///
/// LeftJoin(): Suche alle Flüge, zu denen es keinen Piloten gibt
///
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:
Weiterlesen nach der Anzeige
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“:
///
/// Gibt zu den letzten drei angelegten Piloten alle Flüge aus
///
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)
-
Social Mediavor 3 MonatenCommunity Management und Zielgruppen-Analyse: Die besten Insights aus Blog und Podcast
-
Entwicklung & Codevor 2 MonatenCommunity-Protest erfolgreich: Galera bleibt Open Source in MariaDB
-
Künstliche Intelligenzvor 3 MonatenBlade‑Battery 2.0 und Flash-Charger: BYD beschleunigt Laden weiter
-
Künstliche Intelligenzvor 3 Monaten
Top 10: Der beste Luftgütesensor im Test – CO₂, Schadstoffe & Schimmel im Blick
-
Apps & Mobile Entwicklungvor 2 MonatenMähroboter ohne Begrenzungsdraht für Gärten mit bis zu 300 m²
-
Künstliche Intelligenzvor 2 MonateniPhone Fold Leak: Apple spart sich wohl iPad‑Multitasking
-
Social Mediavor 2 MonatenVon Kennzeichnung bis Plattformpflichten: Was die EU-Regeln für Influencer Marketing bedeuten – Katy Link im AllSocial Interview
-
Künstliche Intelligenzvor 2 Monaten
JBL Bar 1300MK2 im Test: Soundbar mit Dolby Atmos, starkem Bass und Akku‑Rears
