ADO.NET

Software
Dies ist eine alte Version dieser Seite, zuletzt bearbeitet am 7. Mai 2013 um 11:22 Uhr durch MovGP0 (Diskussion | Beiträge) (Transaktionen). Sie kann sich erheblich von der aktuellen Version unterscheiden.

ADO.NET ist ein Teil der von Microsoft entwickelten .NET-Plattform. Es handelt sich um eine Sammlung von Klassen, die den Zugriff auf relationale Datenbanken gewährleisten.

ADO.NET gilt als Nachfolger der ActiveX Data Objects (ADO), hat aber nichts mit der ActiveX-Technologie zu tun. In der Tat ist es um zahlreiche Funktionen erweitert worden, so dass man von einer Neuentwicklung sprechen kann.

Aufgaben

Aufgabe der Klassen (die sich im Namensraum System.Data befinden) ist die Datenbankanbindung und Datenhaltung im Arbeitsspeicher. Dazu existieren Klassen, die Verbindung zu einer Datenbank (Microsoft SQL Server, Oracle etc.) herstellen (sogenannte Connection-Klassen), Klassen, die Tabellen im Arbeitsspeicher repräsentieren, und es ermöglichen, mit ihnen zu arbeiten (sogenannte DataTables) und Klassen, die für gesamte Datenbanken im Arbeitsspeicher stehen (sogenannte DataSets).

Andere Klassen regeln die Anbindung an eine Datenbank. Für die Anbindung an die physische Datenbank existieren sogenannte DataProvider. Die Datenbanken können auf XML-Format abgebildet werden, weshalb es Klassen zum direkten Zugriff auf XML im Namensraum System.Xml gibt.

Architektur von ADO.NET

Das Hauptanliegen von ADO.NET besteht darin, die Datenbeschaffung von der Bereitstellung und Anzeige der Daten vollständig zu trennen. Um dieses Ziel zu erreichen, spaltet sich ADO.NET in die drei Hauptkomponenten DataSet, Datenprovider und die Klassen der Datenbindung auf. Letztere stellen allerdings keinen integralen Bestandteil von ADO.NET dar, stattdessen dienen sie der Anbindung der Steuerelemente an ein DataSet.

Datenprovider

 
Aufbau des ADO.Net Datenproviders

Der Datenprovider ist die Schnittstelle zu einer Datenbank. Er muss fachliche Informationen über die Datenbank besitzen, d. h., er muss die Datenbank kennen. Für unterschiedliche Datenbanken existieren individuelle Datenprovider. Im .NET Framework sind die Datenprovider MS SQL Server und OLE DB standardmäßig enthalten. Auch für viele Open-Source-Datenbanken, wie z. B. MySQL, existieren .NET-Datenprovider.

Die vier Kernkomponenten der .NET-Datenprovider sind:

  • Connection: Stellt eine Verbindung her, die der Kommunikation mit einer Datenquelle dient. Seit .NET 2.0 ist es möglich, bei der Verbindung umfangreiche Metadaten zur Datenbank zu laden.
  • Command: Führt Anweisungen, gespeicherte Prozeduren und Aktionsabfragen aus. Dazu gehören unter anderem SELECT-, UPDATE- oder DELETE-Kommandos.
  • DataAdapter: Der DataAdapter füllt ein DataSet mit Daten und gleicht Aktualisierungen mit der Datenquelle ab. Er fungiert als Brücke zwischen der Datenquelle und einem DataSet-Objekt.
  • DataReader: Es handelt sich um einen vorwärtsgerichteten Datensatzleser, der nur einen lesenden Zugriff auf die Daten gestattet. Eine Navigation durch die Datensätze ist dabei nicht möglich, da diese sequentiell abgearbeitet werden.

DataSet

 
Aufbau eines ADO.NET Datensets

Ein DataSet repräsentiert die speicherresidente, relationale Datenbank in der eigentlichen Anwendung. Es handelt sich dabei um ein Speicherabbild der eigentlichen Datenbank. Ein DataSet wird immer dann eingesetzt, wenn Daten mehrmals benötigt und von der Anwendung geändert werden. In diesem Fall werden die Daten über den DataAdapter im DataSet gespeichert, wo sie der Anwendung zur weiteren Verwendung zur Verfügung stehen.

Die wichtigsten Klassen des DataSet sind:

  • DataSet: Diese Klasse repräsentiert ein logisches Schema. Das Datenbankschema verwaltet Beziehungen zwischen den in der Klasse enthaltenen Tabellen und sorgt dafür, dass die Datenbank relational ist.
  • DataTable: Das DataTable-Objekt stellt eine Datenbanktabelle dar. Es handelt sich um einen Datenspeicher mit Datensätzen und Spalten.
  • DataRow: Die DataRow-Klasse repräsentiert einen konkreten Datensatz in einer DataTable. Eine DataRow-Instanz ist stets an ein DataTable-Objekt gebunden.
  • DataView: Dient zum Filtern (z. B. WHERE) und Sortieren (z. B. ORDER BY) von Daten in einer DataTable. Über eine DataView kann eine spezielle Sicht auf die Daten einer Tabelle angelegt werden.

Einschränkungen

In ADO.NET 1.x konnte zu bestimmten Zeitpunkten pro Verbindung nur ein Datenbankbefehl aktiv sein, beispielsweise ein DataReader lesend auf die Datenbank zugreifen. Versuche eines parallelen Zugriffs waren nicht möglich und führten zu Fehlermeldungen. Diese Architektur kann als Single Active Result Sets (SARS) bezeichnet werden.

ADO.NET 2.0 unterstützt hingegen Multiple Active Result Sets (MARS), also die Mehrfachverwendung einer Verbindung. MARS ist für den SQL Server 2005 und 2008 verfügbar und dort im Standard deaktiviert.

ADO.NET Entity Framework

 
Prinzipielle Funktionsweise des ADO.NET Entity Framework
Entity Framework Modellieransätze
  Code First Model First
Keine Datenbank vorhanden Bestehende Klassen werden mit Annotationen (Table, Column) ausgezeichnet, welche die Abbildung auf eine Datenbank steuern. Darauf aufbauend werden vom DbContext die Datenbank und die Datenbank-Tabellen modelliert und beim Aufruf der SaveChanges()-Methode erstellt. Die Entity-Klassen werden mit einem grafischen Designer modelliert. Das Modell wird einerseits mit Hilfe des Text Template Transformation Toolkit (T4) und der zugehörigen T4-Skriptsprache in Entity-Klassen umgewandelt. Zudem erlaubt es der Designer, ein SQL-Skript zu erstellen, mit dem die Datenbank erstellt wird.
Verwendung einer bestehenden Datenbank Die Entity-Klassen können entsprechend der vorgegebenen Datenbank manuell erstellt, modelliert und ausgezeichnet werden. Dies ist jedoch sehr arbeitsintensiv. Mit Hilfe eines Assistenten wird die Datenbank abgefragt und entsprechend der Datenbankstruktur ein passendes Modell erstellt. Dieses wird mit einem T4-Skript in die entsprechenden Klassen umgewandelt.
Überblick über wichtige ADO.NET Entity Framework Objekte
DbContext API[EF4 1] (EF5) EF4 Aufgabe
DbContext ObjectContext Stellt eine Verbindung mit der Datenbank dar. Stellt Methoden für Abfragen (Query), Änderungsverfolgung (Tracking) und Speichern (Save) bereit.
DbQuery ObjectQuery Stellt Methoden für das Hinzufügen (Add), Anhängen (Attach) und Entfernen (Remove) von Entitäten bereit.
DbSet ObjectSet Erbt von von DbQuery/ObjectQuery und stellt die entsprechenden Methoden für Entity-Typen bereit.
Change Tracker API ObjectContext.ObjectStateManager Bietet Methoden, um Änderungen verfolgen zu können. Hierzu gehört das Abfragen des ursprünglichen und des derzeitigen Zustands von Entitäten.
Validation API Automatische Validierung der Daten im DataLayer.
Code First Model Building Reflektiert über Code-basierte Klassen um für diese passende Datenbankobjekte und die zugehörigen Metadaten im Arbeitsspeicher und der Datenbank zu erstellen.
  1. Während das EF5 standardmäßig die Db-Klassen verwendet, wird im EF4 ein entsprechendes T4-Template (EF4.x DbContext Generator) benötigt.
using(MyDbEntities context = new MyDbEntities())
{
    // Erstelle zwei Personen-Entities
    Person person = new Person(){
        FirstName = "William",
        LastName = "Adama",
        DateOfBirth = DateTime.Now
    };
    Person person = new Person(){
        FirstName = "Laura",
        LastName = "Roslin",
        DateOfBirth = DateTime.Now
    };
    // Erstelle ein Adressen-Entity
    Address address = new Address(){
        Street = "Market Street 70",
        City = "Philadelphia",
        State = "PA",
        Zip = "12345"
    };
    
    // Erste Variante
    context.Persons.Add(person1); 
    
    // Zweite Variante
    // mit dem Kontext verlinken und als hinzugefügt markieren.
    context.Entry(person2).State = EntityState.Added;
    
    // Dritte Variante
    // Das Entity wird an ein bereits vom Kontext beobachtetes Entity gehängt 
    person1.Address.Add(address);

    // Speichere Änderungen am Kontext in der Datenbank
    context.SaveChanges();
}

Abfrage von Daten

Abfragen aller Daten aus einem Datensatz:

using(MyDbEntities context = new MyDbEntities())
{
    IEnumerator<Person> persons = context.Persons; // entspricht SELECT * FROM [Persons]
    
    //// wenn LINQ-Ausdrücke (zB. Joins) benötigt werden: 
    // IEnumerator<Person> persons = from p in context.Persons select p; 
    
    foreach(Person person in persons)
    {
       // führt zusätzliche SQL-Abfragen an die Adresses-Tabelle 
       // mit einem entsprechenden JOIN aus
       foreach(Adress address in person.Adresses) 
       {
           // ...
       }
    }
}

Um zu verhindern, dass dieselbe Datenbankabfrage mehrfach ausgeführt wird, kann die ToList()-Methode verwendet werden:

using(MyDbEntities context = new MyDbEntities())
{
    var persons = context.Persons; 

    // Datenbankabfrage wird ausgeführt und als Liste zurückgegeben
    var allPersons = persons.ToList(); 

    // Keine weitere Datenbankabfragen durch Verwendung der Liste 
    foreach(Person person in allPersons) { /* ... */ }
    foreach(Person person in allPersons) { /* ... */ }
}

Sucht ein bestimmtes Objekt in der Datenbank:

Person person = context.Persons.SingleOrDefault(p => p.PersonId == personId);

Oder mit LINQ:

Person person = (from p in context.Persons 
                 where p.PersonId == personId
                 select p).SingleOrDefault();
LINQ Selectors
Methode Ergebnis
Single() Gibt das eine Element zurück, welches die Anfrage liefert. Wirft eine Exception, falls keine oder mehrere Ergebnisse zurückgeliefert werden.
SingleOrDefault() Gibt das eine Element zurück, welches die Anfrage liefert. Gibt null zurück, falls keine Ergebnisse geliefert werden. Wirft eine Exception, falls mehrere Ergebnisse zurückgeliefert werden.
First() Gibt das erste Element zurück, falls die Anfrage ein oder mehrere Ergebnisse liefert. Wirft eine Exception, falls keine Ergebnisse zurückgeliefert werden.
FirstOrDefault() Gibt das erste Element zurück, falls die Anfrage ein oder mehrere Ergebnisse liefert. Gibt null zurück, falls keine Ergebnisse zurückgeliefert werden.

Bei Db-Objekten steht zudem die Find()-Methode bereit. Diese sucht das Objekt zuerst im Arbeitsspeicher und macht eine Datenbankabfrage, falls das Objekt nicht im Speicher gefunden wird:

Person person = context.Persons.Find(personId);

Lazy, Eager und Explicit Loading

Das Entity Framework verwendet im Normalfall das lazy loading, bei dem Daten aus der Datenbank dann geladen werden, wenn die Daten abgefragt werden:

// lazy loading
// nur die Personen werden abgefragt und geladen  
var persons = context.Peoples;

Falls weitere Daten mit geladen werden müssen, wird das eager loading verwendet:

// eager loading
// Adressen werden bereits bei der Abfrage der Personen geladen
var persons = context.Peoples.Include("Adresses");
// LINQ-to-Entities Beispiel für eager loading
var persons = from p in context.Peoples.Include("Adresses")
              where p.FirstName == fname
              select p;

oder typsicher ab EF5:

// LINQ-to-Entities Beispiel für eager loading
var persons = from p in context.Peoples.Include(p => p.Adresses)
              where p.FirstName == fname
              select p;

Das explizite Laden (explicit loading) der Daten ist ähnlich dem lazy loading, erlaubt jedoch das Laden der Navigationseigenschaften (navigation properties).

// explicit loading
var persons = context.Peoples; // wie lazy loading; Adressen werden nicht mitgeladen
foreach(var person in persons)
{
    person.Adresses.Load(); // explicit loading; navigation properties für Adressen werden geladen 
    foreach(var address in person.Addresses)
    {
        // ...
    }
}

Update Entities

Delete Entities

Löschen eines Entities im Entity Framework 4:

using(MyDbEntities context = new MyDbEntities())
{
    // Abfrage eines Entities aus der Datenbank 
    Person person = (select p in context.Pesons 
                    where p.Id == id 
                    select p).SingleOrDefault(); 
    if(person != null)
    {
        context.Persons.DeleteObject(person);
        context.SaveChanges();
    }
}

Löschen eines Entities im Entity Framework 5:

using(MyDbEntities context = new MyDbEntities())
{
    // Abfrage eines Entities aus der Datenbank 
    Person person = (select p in context.Persons 
                    where p.Id == id 
                    select p).SingleOrDefault(); 
    
    if(person != null)
    {
        context.Entry(person).State = EntityState.Deleted; // Entity zur Löschung markieren
        context.SaveChanges(); // Entity in der Datenbank löschen 
    }
}

Vorkompilierte Abfragen

Datenbankanfragen werden vom Entity Framework in für die Datenbankschnittstelle passende Anfragen kompiliert. Dieser Vorgang kostet jedoch Zeit, weshalb kompilierte Anfragen – sofern diese erneut benötigt werden – nicht verworfen, sondern in einem Objekt gespeichert und später wiederverwendet werden sollten.

Um bei der ersten Verwendung einer Abfrage mit dem Entity Framework 4 Zeit zu sparen, können Abfragen vorkompiliert werden.

static Func<MyDbEntities, int, ObjectQuery<Person>> QueryContactById = 
    CompiledQuery.Compile<MyDbEntities, int, IQueryable<Person>>( 
        (context, personId) =>
            from p in  context.Persons
            where p.Id== personId
            select p);

Im Entity Framework 5 werden Abfragen automatisch bei der Erstellung vorkompiliert.

Transaktionen

using (MyDbEntities context = new MyDbEntities())
{
    using (TransactionScope scope = new TransactionScope())
    {
        // Der TransactionScope sucht den neuesten Context auf dem
        // Stacktrace und verlinkt sich automatisch mit diesem
        try
        {
            // Bearbeitung von Entities
            
            context.SaveChanges(); // speichern der Änderungen in der Datenbank
            scope.Complete(); // Transaktion wird abgeschlossen
        }
        catch (InvalidOperationException e)
        {
            // Transaktion fehlgeschlagen
        }
    } // scope.Dispose()
} // context.Dispose()

Code First Migrations

Code First Migrations ist eine Reihe von Powershell-Skripten, welche die Datenbankmigration erleichtern.

  • Enable-Migrations
    erstellt ein Migrations-Skript
  • Add-Migration
    Erstelle ein Skript zur Herstellung der aktuellen Datenbankstruktur
  • Update-Database
    Bringt die Datenbankstruktur auf einen bestimmten Zustand. Standardmäßig wird das neueste Migrationsskript verwendet.


Literatur

  • Andrew Troelsen: Pro C# 5.0 and the .Net 4.5 Framework. Springer, 2012, ISBN 978-1-4302-4233-8, S. 1560 (englisch).
  • Julia Lerman, Rowan Miller: Programming Entity Framework: DbContext; Querying, Changing, and Validating Your Data with Entity Framework. O'Reilly Media, 2012, ISBN 978-1-4493-1296-1, S. 258 (englisch).
  • Julia Lerman, Rowan Miller: Programming Entity Framework: Code First; Creating and Configuring Data Models from Your Classes. O'Reilly Media, 2012, ISBN 978-1-4493-1294-7, S. 194 (englisch).