Nov 29

LINQ – Von guten und schlechten Äpfeln…

Tag: Tipps und TricksTorsten @ 02:30

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“…)