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)
Entwicklung & Code
Android 17: „Continue on“-Funktion bringt nahtlose App-Übergabe zwischen Geräten
Google hat seine „Continue On“-Funktion im Rahmen der I/O-Session „What’s new in Android“ angekündigt und sie auf der Entwicklerseite näher erläutert. Sie ist Teil von Android 17 und soll Nutzerinnen und Nutzer ermöglichen, eine App auf einem Android-Gerät zu starten und dann auf ein anderes Gerät in ihrem Android-Ökosystem zu wechseln, um die begonnene Aufgabe fortzusetzen.
Weiterlesen nach der Anzeige
Continue-on: Handoff für Android
Wie der Entwickler auf seiner Developer-Webseite erläutert, ist „Continue On“ für den bidirektionalen Einsatz konzipiert. Das heißt, dass jedes unterstützte Android-Gerät sowohl App-Aktivitäten senden als auch empfangen kann, dabei müssen die Geräte jeweils mit dem gleichen Google-Konto verknüpft sein.
Mit dem Release von Android 17, der im Laufe der kommenden Wochen erwartet wird, soll „Continue On“ zunächst den Übergang von Smartphones zu Tablets unterstützen. Dabei werde in der Taskleiste des Tablets ein Vorschlag für die zuletzt auf dem Smartphone geöffnete App angezeigt. Über diesen können Nutzer die App mit einem Fingertipp starten und dort weitermachen, wo er oder sie aufgehört habe.

Continue-on-Funktion: App-Übergabe zwischen Smartphone und Tablet.
(Bild: Google)
Als Beispiel nennt Google etwa die Möglichkeit, dasselbe Dokument in Google Docs, das man zuerst auf dem Smartphone genutzt hat, auf dem Tablet zu öffnen und daran weiterzuarbeiten. Ein weiteres Beispiel zeigt, dass die Übergabe auch mit einem Webbrowser funktioniert: Eine E-Mail in Gmail wird an Chrome auf einem Tablet weitergeleitet, wo sie direkt geöffnet wird.

Continue-on von Gmail-App auf dem Smartphone in den Chrome-Browser auf dem Tablet.
(Bild: Google)
Trotz der anfänglichen Einschränkungen ähnelt der Ansatz stark Apples „Handoff“-Funktion, mit der iPhone-Nutzer Aufgaben nahtlos auf das iPad oder den Mac übertragen können. Apple hatte dieses Feature schon 2014, also vor über 10 Jahren, eingeführt. Im Hinblick auf Googles baldigen Marktstart der im Zuge der Android Show: I/O Edition angekündigten Googlebooks auf Android-Basis, dürfte die Funktion auch auf dieser neuen Gerätegattung landen, um das Ökosystem zu stärken.
Weiterlesen nach der Anzeige
Entwickler müssen handeln
Erste Informationen zur Handoff-Funktion veröffentlichte Google schon im Februar dieses Jahres in der Dokumentation zu Android 17 im Punkt „Funktionen und APIs“.
Damals erklärte Google, dass die Handoff-Funktion im Hintergrund auf dem Gerät eines Nutzers ausgeführt wird. Das Unternehmen schreibt in der Dokumentation: „Die Handoff-Unterstützung wird auf Basis einzelner Aktivitäten implementiert.“ Um Handoff zu aktivieren, müssen Entwickler die Methode setHandoffEnabled() für die Aktivität aufrufen. „Möglicherweise müssen zusätzliche Daten zusammen mit der Übergabe übermittelt werden, damit die neu erstellte Aktivität auf dem empfangenden Gerät den entsprechenden Status wiederherstellen kann. Implementieren Sie den Rückruf onHandoffActivityRequested(), um ein HandoffActivityData-Objekt zurückzugeben, das Details enthält, die angeben, wie Handoff die Aktivität auf dem empfangenden Gerät verarbeiten und neu erstellen soll.“
Lesen Sie auch
(afl)
-
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 2 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
