SQLite-Datenmodellierung per C#

Wenn Sie ein Abonnement des Magazins 'DATENBANKENTWICKLER' besitzen, können Sie sich anmelden und den kompletten Artikel lesen.
Anderenfalls können Sie das Abonnement hier im Shop erwerben.

SQLite-Datenmodellierung per C#

Wenn Sie eine SQLite-Datenbank von einer WPF/C#-Anwendung aus nutzen, möchten Sie gegebenenfalls einmal Tabellen in dieser Datenbank anlegen, ändern oder löschen oder gar neue Datenbanken mit C# erstellen. Dies kann beispielsweise interessant werden, wenn Sie eine neue Version einer Anwendung ausliefern, aber der Benutzer die Daten in der Datenbank der vorherigen Version weiter nutzen möchte. Dieser Artikel stellt die grundlegenden Vorgehensweisen für diese Arbeitsschritte vor.

NuGet-Paket für SQLite hinzufügen

Wenn Sie von einer C#-Anwendung aus Änderungen an einer SQLite-Datenbank vornehmen oder diese sogar erstellen wollen, benötigen Sie das NuGet-Paket mit den entsprechenden Erweiterungen. Dazu klicken Sie mit der rechten Maustaste auf das Projekt im Projektmappen-Explorer und wählen den Kontextmenü-Eintrag NuGet-Pakete verwalten... aus. Im nun erscheinenden Bereich wechseln Sie auf Durchsuchen und suchen nach SQLite. Den nun auftauchenden Eintrag System.Data.SQLite können Sie dann auswählen und installieren.

SQLite-Befehle verfügbar machen

In der Klasse, in der Sie die Befehle zum Erstellen und Anpassen von SQLite-Datenbanken unterbringen wollen, benötigen Sie zunächst einen Verweis auf den Namespace System.Data.SQLite, der erst nach dem Hinzufügen des oben genannten NuGet-Pakets vorfinden:

using System.Data.SQLite;

Leere Datenbank erstellen

Um eine leere Datenbank zu erstellen, fügen wir dem Fenster MainWindow.xaml ein Textfeld zur Eingabe des Datenbanknamens sowie eine Schaltfläche zum Erstellen der leeren Datenbankdatei hinzu. Diese Schaltfläche soll die folgende Methode auslösen:

private void btnCreateDatabase_Click(object sender, RoutedEventArgs e) {
     string databaseFile = AppDomain.CurrentDomain.BaseDirectory + txtDatenbankname.Text;
     File.Delete(databaseFile);
     SQLiteConnection.CreateFile(databaseFile);
     if (File.Exists(databaseFile)) {
         MessageBox.Show("Leere Datenbank
'" + databaseFile + "'
wurde erstellt.");
     }
}

Die Methode trägt den Pfad der .exe-Datei plus den vom Benutzer gewählten Datenbanknamen in die Variable databaseFile ein. Diese Datei wird, sofern vorhanden, gelöscht und dann mit der Methode CreateFile des Objekts SQLiteConnection neu erstellt. Dabei übergeben Sie die Variable mit dem Datenbankpfad als Parameter. Die folgende if-Bedingung prüft, ob nach diesem Vorgang eine Datei mit dem angegebenen Namen vorhanden ist und zeigt in diesem Fall eine entsprechende Meldung an.

Neue Tabelle erstellen

Nun wollen wir der frisch angelegten Datenbank eine erste Tabelle hinzufügen. Das ist dann auch tatsächlich der erste Inhalt der Datenbankdatei – diese weist nach dem Anlegen nämlich die Dateigröße 0 auf (siehe Bild 1). Danach rufen wir mit der Tasten btnCreateTable die folgende Methode auf, die wieder den Pfad der .exe-Datei und den Dateinamen aus dem Textfeld in der Variablen databaseFile zusammensetzt. Dann erstellt sie ein neues Objekt des Typs SQLiteConnection, der sie als Konstruktor-Parameter eine Zeichenkette übergibt, die aus Data Source=, dem Datenbankpfad und der zu verwenden Version besteht.

Frisch angelegte Datenbankdatei

Bild 1: Frisch angelegte Datenbankdatei

Die Open-Methode öffnet die Verbindung zu dieser Datenbank. Dann schreibt die Methode eine CREATE TABLE-Anweisung, die eine neue Tabelle namens Beispieltabelle mit den beiden Feldern id und Beispielstring enthält. Nun erstellt die Methode ein neues SQLiteCommand-Objekt und übergibt die SQL-Anweisung aus der String-Variablen sql sowie das Verbindungsobjekt aus connection als Parameter. Die ExecuteNonQuery-Methode führt die CREATE TABLE-Anweisung schließlich aus. Mit der Close-Methode schließen wir die Verbindung:

private void btnCreateTable_Click(object sender, RoutedEventArgs e) {
     string databaseFile = AppDomain.CurrentDomain.BaseDirectory + txtDatenbankname.Text;
     SQLiteConnection connection = new SQLiteConnection("Data Source=" + databaseFile + ";Version=3;");
     connection.Open();
     string sql = "CREATE TABLE Beispieltabelle (id INT, Beispielstring VARCHAR(255))";
     SQLiteCommand command = new SQLiteCommand(sql, connection);
     command.ExecuteNonQuery();
     connection.Close();
}

Ein Blick in den Windows Explorer zeigt, dass dies nicht spurlos an der Datenbankdatei vorübergegangen ist: Diese hat nun immerhin eine Größe von acht Kilobytes. Aber befindet sich auch die neue Tabelle darin? Dies prüfen wir mit dem Tool SQLiteStudio, das Sie kostenlos über das Internet herunterladen können. Mit dem Menüeintrag Datenbank|Datenbank hinzufügen öffnen Sie den Dialog aus Bild 2. Hier wählen Sie die soeben angelegte Datei aus und klicken nach dem Betätigen von Verbindung testen auf die Schaltlfäche OK.

Hinzufügen der Datenbank zum SQLiteStudio

Bild 2: Hinzufügen der Datenbank zum SQLiteStudio

Danach finden Sie im Hauptfenster von SQLiteStudio bereits die Datenbank mit der ersten Tabelle im Datenbank-Explorer vor (siehe Bild 3). Allerdings verfügt das Feld id noch nicht über eine Primärschlüssel, aber das können wir ja gleich nachholen – indem wir die neue Tabelle verändern.

Die neue Tabelle im SQLiteStudio

Bild 3: Die neue Tabelle im SQLiteStudio

Tabelle mit Primärschlüsselfeld erstellen

Im nächsten Beispiel wollen wir eine Tabelle erstellen und das ID-Feld direkt als Primärschlüsselfeld kennzeichnen. Dazu fügen wir der CREATE TABLE-Methode gleich das Schlüsselwort PRIMARY KEY für das Feld ID hinzu. Dazu nutzen wir prinzipiell die gleiche Methode wie für das vorherige Beispiel:

private void btnTabelleMitPKAnlegen_Click(object sender, RoutedEventArgs e) {
     string databaseFile = AppDomain.CurrentDomain.BaseDirectory + txtDatenbankname.Text;
     string connectionString = "Data Source=" + databaseFile + ";Version=3;";
     MessageBox.Show(connectionString);
     SQLiteConnection connection = new SQLiteConnection(connectionString);
     connection.Open();
     string sql = "CREATE TABLE BeispieltabelleMitPK (id INT PRIMARY KEY, Beispielstring VARCHAR(255))";
     SQLiteCommand command = new SQLiteCommand(sql, connection);
     command = new SQLiteCommand(sql, connection);
     command.ExecuteNonQuery();
     connection.Close();
}

Auch das liefert das gewünschte Ergebnis, wie ein Blick in SQLiteStudio beweist (siehe Bild 4).

Tabelle mit Primärschlüssel im SQLiteStudio

Bild 4: Tabelle mit Primärschlüssel im SQLiteStudio

Nun habe ich allerdings beim Erstellen der neuen Tabelle bei Experimentieren vergessen, das Textfeld txtDatenbankname zu füllen. Es gibt allerdings beim Durchlaufen der obigen Methode keinerlei Fehlermeldung – wie kann das sein? Theoretisch dürfte der Zugriff auf die Datenbank mit der Open-Methode doch gar nicht funktionieren, da die Datenbankdatei gar nicht vorhanden ist? Doch das ist ein Fehlschluss: Dadurch, dass wir txtDatenbankname nicht gefüllt haben, übergeben wir nämlich einen String wie den folgenden mit connectionString an die Open-Methode:

Data Source=C:UsersUser...inDebug;Version=3;

Wenn wir nun in das Verzeichnis ...inDebug schauen, finden wir dort auch keine neue Datenbank etwa mit einem beim Öffnen temporär vergebenen Namen. Wenn wir allerdings eine Ebene nach oben gehen, also in das Verzeichnis ...in, finden wir die Situation aus Bild 5 vor. SQLite legt, wenn die in der Verbindungszeichenfolge für die Open-Methode angegebene Datenbankdatei noch nicht vorhanden ist, eine neue Datenbank an. In diesem Fall hat SQLite Debug;Version=3; als Dateinamen interpretiert und eine Datei namens Debug;Version=3 angelegt (das Backslash-Zeichen wurde dabei als Escape-Zeichen für das Semikolon interpretiert). Die Größe von 20 KB zeigt an, dass hier auch gleich die gewünschte Tabelle angelegt wurde.

Eine Datenbankdatei namens Debug;Version=3

Bild 5: Eine Datenbankdatei namens Debug;Version=3

Autowert definieren

Beim Einfügen von Datensätzen müssen wir uns Gedanken darum machen, auf welche Weise der Wert des Primärschlüsselfeldes definiert wird. Unter Access wurde das immer einfach durch Festlegen der Eigenschaft Autowert für das betroffene Feld erledigt. Unter SQLite ist das auch nicht viel anders: Hier fügen Sie hinter dem PRIMARY KEY-Schlüsselwort einfach noch AUTOINCREMENT ein (siehe Methode btnTabelleMitAutowertAnlegen_Click):

CREATE TABLE BeispieltabelleMitAutowert(ID INTEGER PRIMARY KEY AUTOINCREMENT, ...)

SQLite hat hier allerdings eine etwas andere Philosophie als andere Datenbanksysteme: Sie müssen gar nicht unbedingt eine Autoincrement-Funktion definieren, sondern können das Primärschlüsselfeld auch einfach nur als INTEGER PRIMARY KEY definieren. Wenn Sie dann beim Neuanlegen eines Datensatzes keinen Wert für das Primärschlüsselfeld angeben, fügt SQLite automatisch einen entsprechend ermittelten Wert ein. Gegenüber anderen Datenbanksystemen haben Sie so die Wahl, ob Sie das System einen Primärschlüsselwert auswählen lassen wollen oder ob Sie diesen selbst vergeben wollen. Mehr dazu erfahren Sie weiter unten unter Datensätze einfügen.

Prüfen, ob Datenbank vorhanden ist

Wir wollen an dieser Stelle den Benutzer darauf hinweisen, dass die Datenbank noch nicht vorhanden ist und die Methode abbrechen. Das erledigen wir mit einer entsprechenden if-Bedingung:

private void btnTabelleMitPKAnlegen_Click(object sender, RoutedEventArgs e) {
     string databaseFile = AppDomain.CurrentDomain.BaseDirectory + txtDatenbankname.Text;
     if (!File.Exists(databaseFile)) {
         MessageBox.Show("Die Datenbank
'" + databaseFile + "'
ist nicht vorhanden.");
     }
     else {
        //Tabelle anlegen
     }
}

Herstellen einer Verbindung in eigener Methode

In den bisherigen Beispielen haben wir jeweils einige Anweisungen eingebaut, welche die Verbindung herstellen und mit der Variablen connection referenzieren. Damit wir diese Zeilen nicht in jeder neuen Methode erneut schreiben müssen, gliedern wir diese in eine eigene Methode aus und erweitern deren Funktionsumfang gleich ein wenig.

Die neue Methode heißt VerbindungOeffnen und erwartet den Pfad zur Datenbankdatei als Parameter (datenbankpfad) sowie einen Boolean-Wert namens datenbankErstellen, der angibt, ob die Datenbank erstellt werden soll, wenn Sie noch nicht vorhanden ist. Die Methode stellt die Boolean-Variable datenbankExistiert zunächst auf true ein. Dann prüft sie, ob die mit datenbankpfad angegebene Datei existiert. Falls nicht, wird datenbankExistiert auf false eingestellt und eine Meldung ausgegeben, dass die Datenbank nicht vorhanden ist. Falls die Datenbank existiert, baut die Methode in der zweiten if-Bedingung eine Verbindungszeichenfolge zusammen, erstellt ein SQLiteConnectionString-Objekt auf Basis dieser Verbindung und öffnet diese mit der Open-Methode. Die Verbindung wird dann als Rückgabewert zurückgeliefert. Sollte datenbankExistiert den Wert false enthalten, liefert die Methode den Wert null zurück:

private SQLiteConnection VerbindungOeffnen(string datenbankpfad, bool datenbankErstellen = false) {
     bool datenbankExistiert = true;
     if (!File.Exists(datenbankpfad)) {
         if (!datenbankErstellen) {
             datenbankExistiert = false;
             MessageBox.Show("Die Datenbank
'" + datenbankpfad+ "'
ist nicht vorhanden.");
         }
     }
     if (datenbankExistiert) {
         string connectionString = "Data Source=" + datenbankpfad + ";Version=3;";
         SQLiteConnection connection = new SQLiteConnection(connectionString);
         connection.Open();
         return connection;
     }
     else {
         return null;
     }
}

Prüfen, ob Tabelle vorhanden ist

Der nächste Schritt beim Anlegen einer Tabelle ist eine vorherige Prüfung, ob diese Tabelle bereits vorhanden ist. Wenn wir eine Connection haben und den Namen der zu prüfenden Tabelle, können Sie die folgende Methode verwenden, um zu prüfen, ob die Tabelle bereits vorhanden ist. Die Methode erwartet die Verbindung (connection) und den Tabellennamen (tabellenname) als Parameter. Sie fügt in sql eine SQL-Abfrage zusammen, welche die Tabelle sqlite_master nach einem Datensatz durchsucht, dessen Feld type den Wert table und dessen Feld name den übergebenen Tabellennamen enthält. Diese Abfrage übergeben wir wieder mit der Connection an ein neues SQLiteCommand-Objekt. Wir führen dieses allerdings diesmal nicht mit der Methode ExecuteNonQuery aus, sondern mit ExecuteScalar. Dies liefert die erste Spalte der ersten Zeile des Ergebnisses zurück. Wir haben für die erste Spalte den Wert 1 angegeben. Findet ExecuteScalar keinen passenden Datensatz, ist die Tabelle nicht vorhanden und es wird der Wert null zurückgeliefert. Als Rückgabewert nutzen wir einen Boolean-Wert, der angibt, ob die Variable tabelleVorhanden, die nun entweder den Wert 1 oder null enthält, nicht den Wert null aufweist – also den Wert true, wenn die Tabelle gefunden wurde und false, falls nicht:

Dies war die Leseprobe dieses Artikels.
Melden Sie sich an, um auf den vollständigen Artikel zuzugreifen.

Bitte geben Sie die Zeichenfolge in das nachfolgende Textfeld ein

Die mit einem * markierten Felder sind Pflichtfelder.

Ich habe die Datenschutzbestimmungen zur Kenntnis genommen.