Daten im (Zu-)Griff mit .NET: DataReader, DataSet oder ORM?

Werkzeuge  –  Kommentare

Diskussionen um die richtige Datenzugriffsstrategie und insbesondere die Entscheidung zwischen DataReader und DataSet bewegten die .NET-Entwicklergemeinde seit der ersten Stunde. Mit der Einführung von objektrelationalen Mappern (ORM) fällt die Entscheidung noch schwerer.

10 wichtige Fragen zu .NET

In dieser zehnteiligen Serie liefert .NET-Experte Holger Schwichtenberg Antworten auf die am häufigsten gestellten Fragen, die .NET-Entwickler beschäftigen.

  1. .NET 2.0 oder .NET 3.5?
  2. VB oder C#?
  3. Express oder Professional?
  4. Windows Forms oder WPF?
  5. LINQ-to-SQL oder ADO.NET Entity Framework?
  6. Visual Studio auf Deutsch oder auf Englisch?
  7. ASP.NET, Ajax oder Silverlight?
  8. DataReader, DataSet oder ORM?

Microsofts DataReader ist das Basisinstrument für den Zugriff auf Datenbanken in der .NET-Klassenbibliothek und alleine deswegen schon das schnellste Instrument zum Lesen von Daten, weil alle anderen, höherwertigen Konzepte wie DataSet und ORM-Werkzeuge im Hintergrund den DataReader nutzen. Seine Leistung hängt von der Datenquelle, der Netzverbindung und vom verwendeten Datenbanktreiber ab. Vorzuziehen sind immer echte ADO.NET-Treiber. Oft nutzen Entwickler aus Unachtsamkeit sie aber nicht, sondern alte OLEDB- oder gar steinalte ODBC-Treiber. Letztere lassen sich in .NET zwar einsetzen, bieten aber keine optimale Leistung. Die einzige wichtige Datenbank, für die es keinen ADO.NET-Treiber gibt und für die OLEDB die richtige Wahl ist, ist Microsofts Access (wobei Entwickler gerne witzeln, dass Access ja gar keine "richtige Datenbank" sei). Neben den mitgelieferten ADO.NET-Treibern im .NET Framework (aktuell sind das SQL Server und Oracle, wobei Microsoft angekündigt hat, den für Oracle in Zukunft nicht mehr zu liefern), gibt es ADO.NET-Treiber bei den Herstellern direkt (etwa ODP.NET bei Oracle) und bei zahlreichen auf die Herstellung von Datenbanktreibern spezialisierten Drittanbietern.

Wie der Name sagt, kann der DataReader nur Daten lesen und immer nur eine Ergebnismenge sequenziell von vorn nach hinten datensatzweise durchlaufen. Rücksprünge sind nicht erlaubt. Für schreibende Zugriffe kann man als komplementäres Konzept nur die Command-Objekte einsetzen, wobei der Entwickler für die Konstruktion der Änderungsbefehle (in SQL DML [Data Manipulation Language] oder durch Aufruf von gespeicherten Prozeduren) verantwortlich ist. Weil ein DataReader während seiner Arbeit eine kontinuierliche Verbindung zur Datenbank braucht, lässt er sich nicht serialisieren und in einem verteilten System nutzen.

DataSets – die Hauptspeicherdatenbank

Das DataSet kann viel mehr als ein DataReader. Es ist ein Datencontainer, der mehrere Ergebnismengen gleichzeitig aufnehmen und sie miteinander verknüpfen kann, wenngleich automatisches Nachladen verbundener Datensätze nicht implementiert ist. Darüber hinaus lässt sich nachträglich im Hauptspeicher filtern und beliebig zu den Datensätzen navigieren, sodass manche Entwickler von einer "Hauptspeicherdatenbank" sprechen. Das DataSet kann man serialisieren (in XML-Form oder einem Binärformat) und im Netz verschicken. Vor allem ist der Programmierer in der Lage, Daten im DataSet zu ändern. Es erkennt Änderungen (Change Tracking) und unterstützt die Entwickler beim Zurückspeichern der Änderungen zur Datenquelle.

Aber viele sehen das DataSet kritisch. "The dark side of using DataSets", "DataSets suck" oder gar "Are DataSets the Spawn of Satan?" liest man im Internet. Die Kritik dreht sich immer um den
Leistungs- und Speicherhunger. DataSets können mehr als DataReader, verlangen aber auch bedeutend mehr als diese.

Dabei ist noch zwischen typisierten und untypisierten DataSets zu differenzieren. Das normale DataSet ist genau wie der DataReader untypisiert. Das heißt, dass man jedes Tabellenschema damit einlesen kann. Der Preis für die Flexibilität ist, dass der Entwickler im Programmcode die Spalten nur über vom Compiler nicht überprüfbare Zeichenketten ansprechen kann und alle Werte in Form des Basistyps System.Object geliefert bekommt, sodass er Typkonvertierungen vornehmen muss, zum Beispiel:

Zeile["Counter"] = Convert.ToInt64(Zeile["Counter"])+1

Typisierte DataSet erstellt Entwickler durch Auslesen des Tabellenschemas zur Entwicklungszeit. Sie sind die Verpackung (englisch Wrapper), um die sich die ADO.NET-DataSet-Klasse bildet. Die Wrapper-Klasse stellt die Spalten der enthaltenen Tabelle als Attribute in Tabellenobjekten zur Verfügung und bietet Methoden zum Holen, Ändern, Hinzufügen und Löschen von Daten.

Datenbindung an Steuerelemente in der Benutzeroberfläche ist zwar mit untypisierten Containern möglich, der typisierte Container hat jedoch den großen Vorteil, dass Visual Studio den Entwickler bei der Erstellung von Datenbindungen mit Assistenten und Auswahlfeldern unterstützt. Typisierte DataSets implementieren zudem Schnittstellen, um ein Steuerelement zur Laufzeit über Änderungen von Daten zu informieren. Der Komfort hat seinen Preis: Das typisierte DataSet ist noch schwerfälliger als das normale DataSet (siehe Abbildung 1). Außerdem ist das typisierte DataSet nur für ein bestimmtes Datenbankmanagementsystem zu verwenden. Eine gemeinsame Codebasis für mehrere Systeme kann es nicht geben, außer wenn man mit ganz viel Eigenarbeit und Tricks an die Sache herangeht.

Geschwindigkeitsvergleich von unterschiedlichen Datenzugriffslösungen am Beispiel des Einlesens von 10.000 Datensätzen mit einem SQL-Befehl aus einer Tabelle (Abb. 1)

In Desktopanwendungen mit direktem Datenbankzugriff (Two-Tier-Anwendungen) kann ein Entwickler die Schwerfälligkeit des DataSet meist hinnehmen, daher ist es dort heute das Standardinstrument. In Webanwendungen und verteilten Systemen, wo sich Leistungs- und Speicherbedarf beim Datenzugriff für alle Nutzer auf einem Server bündeln, ist der Einsatz des DataSet kritisch zu bewerten, insbesondere bei größeren Benutzerzahlen und Skalierungsanforderungen.