Entwicklung & Code
DeerFlow: Super-Agenten-Framework von ByteDance | heise online
OpenClaw hat es vorgemacht und gezeigt, zu welchen Leistungen LLMs schon in der Lage sind, wenn Agenten sie richtig nutzen. Allerdings sind die damit verbundenen Gefahren nicht nur groß, sondern sogar unüberschaubar: In manchen Fällen klaute OpenClaw Kreditkarten oder übernahm gleich den ganzen Computer. Dennoch steckt in der Technologie ein enormes Potenzial, was einige Nachahmer gefunden hat, die versuchen, die Schwächen zu korrigieren.
Weiterlesen nach der Anzeige
ByteDance legt vor
Einer der ersten Player, der sich aus der Deckung wagt, ist ByteDance, die Muttergesellschaft von TikTok, mit dem Tool DeerFlow (Deep Exploration and Efficient Research Flow). Nachdem das Projekt bereits über 56.000 Sterne auf GitHub erhalten hat, lohnt es sich, einen genaueren Blick darauf zu werfen.
ByteDance möchte, dass man für die Nutzung ein Coding-Abo abschließt, notwendig ist der aber nicht: DeerFlow lässt sich genauso mit lokalen Modellen verwenden. Etwas schwieriger wird es bei lokaler Nutzung allerdings, wenn das Framework selbstständig Websuchen durchführen soll. Das ist außerordentlich sinnvoll, denn der Knowledge Cutoff der meisten LLMs liegt beträchtlich in der Vergangenheit und sie sind über neue Entwicklungen nicht informiert. Hier kann man zum Beispiel die Search-Engine Tavily hinzufügen, für die es eine freie Variante gibt, mit der sich tausend Suchanfragen pro Monat durchführen lassen. Die gibt es auch bei InfoQuest, das ByteDance proprietär anbietet. Allerdings ist die Anzahl der Anfragen insgesamt auf tausend pro Account limitiert, Preise für weitere sind nicht direkt zu finden. Interessanterweise bietet ByteDance diesen Dienst in China selbst gar nicht an, möglicherweise auch aus Zensurgründen.
Alle lassen sich lokal installieren und konfigurieren. Die ausführliche Anleitung dazu findet sich auf GitHub. Zunächst muss man eine Konfigurationsdatei im YAML-Format anpassen. Anschließend lassen sich Docker-Container starten, in denen das System dann läuft. Die Container öffnen einen Port (2026, ob das Zufall ist?) auf dem Host-System. Das Ganze funktioniert ziemlich unproblematisch, wenn man alle Environment-Variablen in .env schreibt, die Variablen der Shell verwendet das System nämlich nicht. Das Ergebnis sieht dem Playground von DeerFlow sehr ähnlich, aber es lässt sich mit neuen Chats ergänzen.
Spielt man mit dem Playground, merkt man dem Tool auch seine Herkunft aus dem Deep Research an. Komplexe Themen kann der Agent selbst recherchieren und kommt zu guten Ergebnissen, die auch neue Entwicklungen beinhalten. Die Vorschläge für den heise-Verlag (siehe Abbildung 1) sind wie von anderen Agenten gewohnt sehr umfangreich und klingen auch sinnvoll. Allerdings ist das bei Weitem noch nicht alles.

Beispiel-Konversation mit DeerFlow mit der Frage, wie der heise-Verlag sein Geschäftsmodell ergänzen könnte (Abb. 1).
Von Deep Research zu Super-Agenten
Weiterlesen nach der Anzeige
Intern baut DeerFlow auf LangChain und LangGraph und ist damit hervorragend erweiterbar. Genau das betont auch ByteDance und bezeichnet das Framework als „Super Agent Harness“ – also ein Framework, das sich ausgezeichnet erweitern lässt. Angeblich haben die ByteDance-Developer DeerFlow neu konzipiert, als sie genau diese Erweiterungsfähigkeit von LangChain als einen entscheidenden Punkt erkannt haben.
Derartige Erweiterungen können auf unterschiedlicher Ebene erfolgen. Zentral dafür sind Skills und Tools, in denen Anwender über Markdown-Files Workflows definieren. Auch der im Beispiel der Abbildung benutzte Research-Workflow basiert auf einem solchen Skill. Von Anwendern bereitgestellte Skills lädt DeerFlow bei Bedarf nach, wenn sie benötigt werden. Das spart Speicher und Rechenzeit und erlaubt eine dynamische Erweiterung des Systems zur Laufzeit. Tools sind etwas fortgeschrittener und erledigen komplexe Aufgaben wie beispielsweise die Websuche, können aber auch Bash-Skript oder Python-Programme in einer Sandbox sicher ausführen.
ByteDance hat erkannt, dass komplexe Aufgaben sich nur schlecht in einem einzigen Durchlauf erledigen lassen. Daher stammt das Konzept der Sub-Agenten, die ihren eigenen Kontext, eigene Tools und Abschlussbedingungen haben. Das erlaubt, sie parallel zueinander laufen zu lassen und damit Zeit zu sparen.
Die Sandbox funktioniert in DeerFlow wie ein eigener Computer. Jeder Prozess erhält eine eigene Umgebung mit einem separaten Filesystem, in dem sich wiederum Skills usw. befinden können. Durch diese Isolation können die Prozesse unabhängig voneinander agieren.
Großen Wert legt ByteDance auf die Isolation von Kontexten. Jeder Agent verfügt über einen eigenen Kontext und wird nicht von anderen Agenten gestört. Nebenbei erlaubt das die schon angesprochene Parallelisierung von Agenten. Um den Kontext möglichst klein zu halten (und damit Speicher bzw. Token im LLM zu sparen), findet ständig eine Zusammenfassung statt.
DeerFlow hat ein Langzeitgedächtnis und merkt sich Inhalte auch über eine Unterhaltung hinaus. Es fungiert somit als Wissensspeicher und passt sich den Wünschen der Benutzer immer besser an. Diese Daten speichert es alle lokal. Mit Empfehlungen und Apps, auf die man nicht mehr verzichten möchte, kennt sich ByteDance ja bestens aus. Im Gegensatz zu TikTok entsteht bei DeerFlow aber ein echter Mehrwert.
DeerFlow bringt neue Ideen
ByteDance veröffentlicht mit DeerFlow ein Agenten-Framework mit vielen neuen Ideen und zeigt damit, wie sich Agenten zukünftig entwickeln könnten. Besonders spannend sind dabei die einfach zu implementierenden Skills, die Isolation und Parallelisierung sowie die Sandboxen, die gefährliche Situationen wie bei OpenClaw vermeiden sollen.
Agentische KI beschäftigt Forscher gerade sehr intensiv. Noch weiß niemand ganz genau, wie sich das Feld entfalten wird. Alternative Tools wie n8n oder Dify sind hier noch deutlich statischer als DeerFlow, aber möglicherweise ergeben sich für sie nun auch neue Richtungen. Umso spannender wird es zu beobachten, wie sich das DeerFlow-Framework weiterentwickelt.
(who)
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
