ASP.NET: Handarbeit statt DataSource!

Achtung: Polemik, Zuspitzung, Gemeinheit!

Das ASP.NET-Framework bietet diverse datengebundene Controls an, die der Visualisierung von Listen dienen. Die bekanntesten sind Repeater, DataGrid und GridView, gefolgt von einigen Exoten, etwa der DataList. Dazu gibt es wundervolle DataSource-Steuerelemente, die es erlauben, komplett ohne eine einzige Zeile an .NET-Code, Datenbanken an diese Steuerelemente zu binden.

Wenn man sich nun den Spaß macht, mal durch die Foren zu hirschen, dann bekommt man das kalte Grausen, denn die Kombination DataSource-Steuerelement + GridView-Steuerelement beherrscht seit .NET 2.0 quasi die Programmierung im ASP.NET-Umfeld. Was auch letztlich kein Wunder ist, denn sie sind idiotensicher einzusetzen und bringen Ergebnisse in wenigen Sekunden. Mit dabei sind dann gleich Features, wie etwa Paging und Sorting. Geil.

Nur leider ist es totaler Mumpitz und absoluter Schwachsinn, diese Controls extensiv und in Kombination miteinander einzusetzen. Denn sie räumen gründlich auf mit allem, was gemeinhin als Schichtentrennung, Wartbarkeit, Skalierbarkeit oder Qualität bekannt ist.

Die Gründe liegen auf der Hand:

Schichtentrennung ist nicht mehr gegeben, da der Code zum Abrufen von Daten – samt SQL-Statements! – speziell bei Verwendung vom SqlDataSource-Steuerelement in der WebForm selbst liegt. Statt also eine dedizierte Geschäftslogik damit zu beauftragen, Daten zu laden (zu cachen, aufzubereiten, zu analysieren, …), wird hier direkt auf die Datenquelle zugegriffen. Die komplette Verarbeitungslogik wandert dann in irgendwelche Ereignisbehandlungsmethoden im Code-Bereich der WebForm, statt in einer eigenen Verarbeitungsschicht zu liegen, wo sie ggf. auch gegen andere Implementierungen, Weiterentwicklungen oder sonstwas ausgetauscht werden könnte. Einzige akzeptierbare Ausnahme hier: Das ObjectDataSource-Steuerelement, das es erlaubt, gegen statische Methoden von Geschäftsobjekten zu arbeiten. Doof nur, dass mir dann dabei quasi vorgegeben wird, dass ich eben eine statische Methode brauche. Was ist mit meiner Factory (Provider, Builder, Broker, …), die ich ggf. habe?

Wartbarkeit? Gibts nicht mehr. Wie auch? Ein Beispiel: Es gibt zwanzig WebForms in einer Applikation, die auf diese Art arbeiten – also GridView + DataSource-Steuerelement. Jetzt wird das Datenmodell geändert, was im Rahmen von Weiterentwicklungen durchaus vorkommen soll. Das Ergebnis? Ich darf an mindestens zwanzig WebForms Änderungen vornehmen, statt es an nur einer Stelle in meiner Geschäfts- oder Datenlogik zu haben. Klasse. Nicht zu reden von der quasi nicht mehr vorhandenen Lesbarkeit des Codes. Neee, Wartbarkeit gibt es damit nicht.

Die Skalierbarkeit verschwindet ebenfalls hinter dem Horizont, denn ich habe schlicht keine brauchbare Möglichkeit mehr, in Datenhaltungs- und Caching-Prozesse einzugreifen. Geht nicht, ist ja alles inline. Klar, die SqlDataSource kann auch cachen – aber das kann ich im Code über das Application Data Caching viel zielgerichteter und genauer umsetzen. Wie also soll es skalieren? Zumal ich ja nicht mal in der Lage bin, einfach per Konfiguration etwa auf eine Web-Service-basierende Lösung umzustellen – ich müsste es halt überall ändern, wo ich auf die Daten zugreife. Oder eben eine andere Implementierung der Geschäfts- oder Datenhaltungslogik zu verwenden. Klar, es gibt die ObjectDataSource, aber… siehe oben. Dazu kommt: DataGrid und GridView sind zwar mächtig, aber eben auch langsam, denn die ganzen Funktionalitäten müssen ja irgendwo eingebunden werden. Auch, wenn man sie unter Umständen nicht benötigt. Und Tschüss, Skalierbarkeit!

Qualität? ROTFL! Wie denn, wo denn? Wie soll denn mit solch einem Gewurschtel, mit nicht mehr vorhandenen anerkannten Entwurfs- und Design-Patterns noch sowas wie Qualität erzeugt werden? Wenn man auch nur einen kurzen Moment mal nicht nur an die persönliche Bequemlichkeit denkt, dann muss man eigentlich zwangsläufig erkennen, dass man so ganz sicher alles baut, aber keine qualitativ hochwertige Software.

Die Lösung?

Back to the roots! Wenn datengebundene Listensteuerelemente, dann einen Repeater, keine komplett überdimensionierten GridViews und DataGrids. Und aus dem Code heraus die Daten binden. Kontrolle zurück gewinnen! Weiß denn eigentlich überhaupt noch jemand, wie man händisch die Daten binden lässt? Nein? Na, dann hier ein wenig Beispielcode:

protected override void OnPreRender(EventArgs e)
{
   // Factory für den Business-Layer
   BusinessLayer bl = BusinessLayer.GetInstance();

   // Personen laden
   IList persons = bl.GetPersons();

   // Personen binden
   rptPersons.DataSource = persons;

   // Datenbindung
   DataBind();
}

Oh je, sieben Zeilen Code! Wie schrecklich!

Leute, seid mir nicht böse, aber von nix kommt auch nix. Sicher, ich spitze hier zu, aber Webseiten entwickeln hat eben tatsächlich was mit Entwicklung, mit Code, mit Abläufen, mit Architektur, mit Denken zu tun. Ihr müsst das nicht machen, keine Frage – aber genau so werden dann eure Ergebnisse aussehen: Zusammengestückelt, unprofessionell, langsam, nicht pflegbar. You get, what you pay for – kein Einsatz, kein Ergebnis.

Zu den Themen Paging, Sorting und Caching dann ein anderes Mal. Google hilft sicher bis dahin weiter – Stichworte: PagedDataSource, IComparer und Cache.

Update: Ihr wollt, dass man mal was über bestimmte Themen schreibt? Dann sagt es mir.

Weiterempfehlen:
  • Print
  • Digg
  • del.icio.us
  • Facebook
  • Google Bookmarks
  • DotNetKicks
  • MySpace
  • PDF
  • RSS
  • Technorati
  • Twitter

9 Comments so far

  1. Peter Bucher on Juni 6th, 2008

    Ein Mann, ein Wort :-)
    Volle Zustimmung zu deiner Rede, inhaltlich IMHO total korrekt, lehrreich und schön zu Lesen :)

    Weg mit dem Krempel *g* (Um es mit deinen einfühlsamen Worten zu sagen^^)

  2. Daniel Meier on Juni 7th, 2008

    Meine Meinung! Aber leider sind es immer genau die Dinge, die MS an ihren Neuerungen pusht. Und will mans dann mal in einem Enterprise-Umfeld anwenden, kommen die grossen Fragezeichen, wie man das jetzt gescheit machen soll – siehe Linq2Sql – da hab ich bis heute noch keine vernünftigen Ansatz gesehen, wie man sowas in einem Enterprise-Umfeld anwenden könnte.

  3. Alex on Juni 7th, 2008
  4. Bill Gates on Juni 8th, 2008

    Sieben Zeilen Code sind in der tat viel zu viel!

    rptPersons.DataSource =
    BusinessLayer.GetInstance().GetPersons();
    DataBind();

    SCNR *g*

  5. Jürgen Gutsch on Juni 9th, 2008

    Ganz deiner Meinung, Karsten, Danke. Ein weiterer Link der jetzt regelmäßig auf ASP.NET Zone gepostet wird :-)

    Es wird einfach viel zu viel zusammengeklickt. Mir grauts erst richtig, wenn die Leute selbst das SQL für die DataSource Controls zusammenklicken… :-(

    Nebenbei: Die ObjectDatasource benötigt IMHO nicht zwingend statische Methoden. Die entsprechende Klasse muss nur einen parameterlosen Kunstruktor haben.

    Gruß
    Jürgen

  6. Christian Wenz on Juni 9th, 2008

    Volle Zustimmung. Leider fehlt es halt gerade im ASP.NET-Umfeld an *einsteigerfreundlicher* Doku für die spannenderen Elemente. Oder halt diese typischen MSDN-Samples, die leider vier Dinge auf einmal erklären, aber keines davon richtig gut. Konsequenz: Wenn man sich auskennt (wie die meisten Leser des Blogs), kommt man super mit den Möglichkeiten zurecht. Wenn man das nicht tut, nimmt man irgendwas mit “Grid” im Namen :(

  7. Stefan Falz on Juni 9th, 2008

    Ich bin ein Verfechter der “Nimm SuperControls für alles und verwende KlickiBunti bis zum Exzess” Strategie und von daher verteufele ich Dein Posting :)

    Ne, im Ernst: Super geschrieben, genau ins Herz getroffen. Einzige Ausnahme: Das GridView Control kann man IMO problemlos verwenden, lediglich die DataSource Controls verschwinden hoffentlich bald wieder (wohl eher nicht, aber die Hoffnung … sterben … :) oder werden nicht mehr verwendet.

  8. haarrrgh on Juni 10th, 2008

    “Volle Zustimmung. Leider fehlt es halt gerade im ASP.NET-Umfeld an *einsteigerfreundlicher* Doku für die spannenderen Elemente.”

    –> Genau das ist das größte Problem.
    Ich bin Neuling auf den Gebieten .NET & OOP (Erfahrung bis jetzt: 6 Jahre Access & SQL Server)
    Bisher beschränkt sich meine praktische Erfahrung mit .NET im wesentlichen auf alles was Karsten in seinem Post beschrieben hat (also bis zu der Stelle wo er anfängt zu erklären wie man es richtig machen sollte ;-)

    Ich würde ja gerne mehr lernen, aber es ist verdammt schwer, Informationen zu finden mit denen ich was anfangen kann.

    Alles was man so findet fällt normalerweise in eine der folgenden Kategorien (absteigend nach Fundhäufigkeit sortiert):

    - Klicki-Bunti-Anleitungen (“Ziehen Sie die Tabelle einfach aufs Formular, und da ist sie schon – Ihre erste eigene Webseite!”)

    - sehr theoretische Grundlagen von OOP (“Hund.Farbe=braun” / “Hund.Bellen()” / Vererbung Hund –> Dackel) die aber meistens genau an der Stelle aufhören wo die Verbindung zur Praxis anfangen würde

    - OOP-Beispiele bei denen tatsächlich etwas aus einer Datenbank gelesen / in eine Datenbank geschrieben wird. Allerdings sind das dann Tabellen mit 3 Spalten und 3 Zeilen und meistens ohne Beziehung zu anderen Tabellen. Und Vererbung und alles was über Klasse.Property hinausgeht fehlt in diesen Beispielen auch immer, es wird NUR gelesen/geschrieben und sonst nix.

    - Beispiele zur Schichtentrennung, meistens mit schönen Diagrammen auf denen man schön die Verbindungen PL –> BLL –> DAL etc. erkennt, aber ohne eine einzige Zeile Code

    - Blogs wie dieser (oder auch die der meisten Leute die hier schon vor mir Kommentare hinterlassen haben) die ich regelmäßig in der Hoffnung lese noch etwas dabei zu lernen. Leider gehöre ich offensichtlich nicht zur richtigen Zielgruppe: Beiträge zu einem konkreten Problem (meistens mit viel Beispielcode) sind normalerweise ein paar Ebenen zu hoch für mich und behandeln Themen die für mich irgendwann mal relevant sein könnten wenn ich nur mal die Grundlagen kapieren würde (z.B.: wozu brauche ich heute Unit Tests, wenn ich keine vernünftige Klasse geschrieben kriege die ich damit testen könnte?). Beiträge in “Aufsatzform” (so wie dieser hier von Karsten) sind noch mit die informativsten Quellen wenn man die Theorie dahinter erstmal verstehen will, aber eben leider wieder nur Theorie.

    Und da sitze ich nun mit meiner 45 GB großen SQL Server-Datenbank mit 370 Tabellen, meinem 95 MB großen Access-Frontend und weiß nicht wo ich anfangen soll:

    Z.B. Klassendesign:
    Im Moment benutze ich nirgendwo Klassen, sondern wenn ich in 20 Formularen Daten zu Aufträgen brauche, greife ich auch 20x auf die Auftragstabellen zu. Besser wäre es natürlich dafür eine Klasse zu bauen und die überall zu benutzen.
    Die Klassen in den Beispielen (wie gesagt – Tabellen mit 3 Zeilen und 3 Spalten) sind auch immer schön einfach. Ich habe da aber z.B.:
    - Aufträge
    - jeder Auftrag gehört zu einem Kunden (separate Tabelle)
    - jeder Auftrag hat n Positionen (separate Tabelle)
    - jede Position hat n Produktmerkmale (separate Tabelle mit einem DS pro Produktmerkmal, n > 200!!)

    Also müssen die Klassen irgendwelche Relationen untereinander haben um z.B. zusammen mit dem Auftrag die Positionen und einige ausgewählte Produktmerkmale zu laden.
    Und dann muß ich sowas auch noch als Listen laden, z.B. als Liste aller offenen Aufträge mit diesen Daten.
    In den Beispielen werden da dann Sachen gemacht wie:
    -Auftragsliste laden, in Schleife durchlaufen
    -für jeden Auftrag: Positionsliste laden, in Schleife durchlaufen
    -für jede Position: nacheinander 3 Produktmerkmale laden (3 Abfragen)

    Wenn man der einzige Benutzer in der Nordwind-Datenbank ist kann man das ja meinetwegen auch so machen, aber nicht bei einer 6stelligen Anzahl Aufträgen auf die >100 Benutzer zugreifen.
    Solche Sachen mit Schleifen habe ich zu Anfang meiner Access-Zeit gemacht, bevor ich kapiert hatte wie Joins in SQL funktionieren. Und als ich die Schleifen durch Joins ersetzt hatte war die Anwendung auf einmal viel performanter.

    Und da meine Liste eine Liste der offenen Aufträge zum Ausdrucken sein soll, ist auch nix mit Paging und erstmal nur 30 Datensätze laden – wenn es 1000 offene Aufträge gibt, dann müssen die auch alle gedruckt (und damit geladen) werden.

    Beispiele zu sowas? Von wegen!

    Oder Schichtentrennung:
    Schichtentrennung hört sich in der Theorie nach einer tollen Sache an, aber wie setzt man sie um?
    Wenn man dann doch mal ein paar Beispiele findet erfährt man je nachdem welchen Artikel man liest daß man den Datenzugriff mit Datasets / Typed Datasets / SQLDataSource / eigenen Objekten / Stored Procedures / … machen soll, und es werden auch gleich Begründungen mitgeliefert warum die verwendete Methode gut ist und alle anderen (also die, die die Autoren der anderen Artikel verwenden…) nicht.

    Wenn man dann mal konkrete Beispiele dazu findet sind die meistens mit Datasets.
    Die Daten werden dann geladen und gespeichert ohne daß man eine einzige Zeile SQL schreiben muß, das macht der Tableadapter alles von alleine.
    Wenn man Sachen aus mehreren Tabellen haben will werden in den Beispielen einfach mehrere DataTables geladen und im Dataset gejoint (das ist ja soviel besser als eine SQL-Join-Abfrage, denn da kann man die Änderungen hinterher wieder automatisch speichern lassen ohne eine Zeile SQL zu schreiben!). Nur – meine Tabellen sind etwas größer als die in den Beispielen. Ich kann nicht einfach mal 2 Mio. Datensätze in den Speicher laden nur damit ein paar davon mit einer anderen Tabelle gejoint werden. Da denke ich jetzt: für sowas gibt es ja eigentlich Joins auf dem SQL-Server…

    Und so geht es endlos weiter…

    Ich frage mich immer ob ich der einzige bin der Probleme dieser Art hat.

    Man findet immer nur entweder Beispiele für totale Anfänger die wirklich komplett bei Null anfangen, oder eben Beispiele für Superexperten (also von MVPs für MVPs) die davon ausgehen daß man diesen ganzen Architekturkram schon längst hinter sich gelassen hat und die sich deshalb nur mit einzeln herausgepickten Problemchen in viel höheren Sphären befassen.

    Aber es muß doch noch mehr Leute geben die wie ich die Grundlagen beherrschen, schon genug Berufserfahrung etc. haben und für die nur der ganze .NET- und OOP-Kram neu ist?

  9. Liz Helmecke on Juni 12th, 2008

    Ja genau! Ganz, ganz genau so geht es mir auch.

    Gerade hatte ich es ansatzweise geschafft, nach jahrelangem Access-VBA plus SQL-Server mich mit dem .NET Framework 1.1 und dem ADO.NET DataSets zurechtzufinden. Und habe eine Windows-Anwendung mit einer halbwegs ordentlichen DAL-Schicht erstellt.

    Und jetzt mit den VS-Versionen die ganzen Microsoft-Veranstaltungen zum Thema “kein Code mehr notwendig… Geht jetzt alles viel einfacher usw.”, bin ich vor allem ziemlich ratlos. Und stelle bereits mehrfach fest, daß es zwar recht schnell geht, damit einen Prototyp zusammenzuklicken, aber total umständlich ist, daran später etwas zu ändern.

    In Grundlagen-Schulungen für Anfänger will ich nicht gehen, das ist zu wenig und zu langsam. Aber wie, wo und bei wem lernt man schnell und effizient, eine gute .NET 3.x Architektur in die Praxis umzusetzen?

    *Stirn runzel* Liz