Nov 29
LINQ – Von guten und schlechten Äpfeln…
Wer kennt das nicht: Man hat einen riesen Korb voller Obst und die Hälfte davon ist mit Sicherheit schon verfault. Jetzt braucht man 5 der größten leckeren Äpfel zum Essen, und fünf der halb verfaulten für ungebetene Gäste…
Jetzt dachte ich mir, das ist doch eine ganz leichte Aufgabe für für LINQ2SQL, viel schneller, als wenn ich die jetzt mit der Hand raussuche….
So hatte ich mir das gedacht:
// Ich nehme mir max. fünf Äpfel var queryApples = (from apple in ctx.Apple orderby apple.Size descending select apple).Take(5); // Ich nehme mir max. fünf faule Äpfel var queryRottenApples = (from rottenApple in ctx.RottenApple orderby rottenApple.Size descending select new Apple() { ID = rottenApple.ID, Size == rottenApple.Size }).Take(5); // Äpfel und faule Äpfel kommen in einen "Korb", // und ich ich möchte jetzt max. fünf der davon Größten... var queryAllApples = queryApples.Concat(queryRottenApples). OrderByDescending(apple => apple.Size).Take(5); foreach (Apple apple in queryAllApples) { // Äpfel Verarbeiten... } // foreach (apple)
Aber so einfach geht das nicht. Anstatt Würmern bekommt man hier eine NotSupportedException: „Explicit construction of entity type ‚FruitsDatabase.DBM.Apple‘ in query is not allowed.“
LINQ erlaubt es einem nicht (mehr), manuelle Instanzen von seinen Entitäten als Teil einer Projektion zu erstellen. Hintergrund ist wohl der, dass dadurch der Cache mit nicht wohlgeformten Objekten gefüllt werden könnte. Deswegen hat Microsoft das letztmalig in der LINQ Beta2/RTM zugelassen.
Es gibt aber auch hierfür eine Lösung, denn ab und zu kann es durchaus sinnvoll sein, auch mal „Äpfel mit Birnen“ zu mischen…
Der Schlüssel ist das Anlegen einer abgeleiteten Klasse der LINQ-Entität:
/// <summary> /// My apple /// </summary> public class MyApple : Apple { } // class MyApple
Mit Hilfe dieser Klasse, lässt sich nun auch die Äpfelthematik lösen:
// Ich nehme mir max. fünf Äpfel var queryApples = (from apple in ctx.Apple orderby apple.Size descending select new MyApple() { ID = apple.ID, Size == apple.Size }).Take(5); // Ich nehme mir max. fünf faule Äpfel var queryRottenApples = (from rottenApple in ctx.RottenApple orderby rottenApple.Size descending select new MyApple() { ID = rottenApple.ID, Size == rottenApple.Size }).Take(5); // Äpfel und faule Äpfel kommen in einen "Korb", // und ich ich möchte jetzt max. fünf der davon Größten... var queryAllApples = queryApples.Concat(queryRottenApples). OrderByDescending(apple => apple.Size).Take(5); foreach (MyApple apple in queryAllApples) { // ...Äpfel Verarbeiten... } // foreach (apple)
Tipp: Anstatt Concat hätte man auch Union verwenden können, um die zwei Mengen (gute und schlechte Äpfel) am Ende zusammen zu führen. Es kommt ganz darauf an, was man herausbekommen möchte. Union ist das SQL UNION, während Concat UNION ALL ist (bei dem somit beide Teile der Menge, inkl. möglichen Duplikaten mit erhalten bleiben). Nähere Infos hierzu gibt es hier.
Man könnte noch weiter gehen und die abgeleiteten Klasse noch um zusätzliche Felder erweitern… Aber es gibt durchaus Grenzen bei dieser Art von Klasse – das merkt man dann relativ schnell, wenn man damit arbeitet.
/// <summary> /// My apple /// </summary> public class MyApple : Apple { public int WormCount { get { return Size * 13; } } } // class MyApple
Fazit: An apple a day keeps the doctor away…
(womit natürlich die Früchte gemeint sind, nicht irgendwelche „Designerstücke“…)