EDM für bestehende Datenbank mit Code First

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.

EDM für bestehende Datenbank mit Code First

Wenn Sie ein Entity Data Model mit der Vorlage »Code First aus Datenbank« auf Basis einer bestehenden Datenbank erstellen, haben Sie vielleicht Pech und die Namen der Tabellen der Datenbank und der enthaltenen Felder lauten nicht so, wie Sie die Entitätsklassen, die DbSet-Elemente und die Eigenschaften der Klassen nennen möchten. Dann haben Sie verschiedene Möglichkeiten: Zum Beispiel können Sie die Bezeichnungen in der Datenbank anpassen. Das geht aber oft nicht, weil vielleicht noch andere Frontends auf die gleiche Datenbank zugreifen. Dann haben Sie noch die Möglichkeit, die Bezeichnungen von Datenbank und Entity Data Model so zu mappen, dass beide Seiten zufrieden sind. Wie letzteres gelingt, zeigen wir im vorliegenden Artikel am Beispiel der Südsturm-Datenbank.

Einfaches Beispiel: tblFotos

Wir starten mit einem sehr einfachen Beispiel, nämlich der Tabelle tblFotos. Diese haben wir als Tabelle zum Speichern von Fotos erstellt, die mit einer PowerApp über das Smartphone aufnehmen wollen. Dabei haben wir die Tabelle leichtsinnigerweise tblFotos genannt statt einfach Fotos ohne Präfix. Wenn wir nun ein Entity Data Model erstellen, erhalten wir für die Klasse FotoverwaltungContext.db etwa den folgenden Code:

Imports System.ComponentModel.DataAnnotations.Schema
Partial Public Class FotoverwaltungContext
     Inherits DbContext
     Public Sub New()
         MyBase.New("name=FotoverwaltungContext")
     End Sub
     Public Overridable Property tblFotos As DbSet(Of tblFotos)
     Protected Overrides Sub OnModelCreating(ByVal modelBuilder As DbModelBuilder)
     End Sub
End Class

Hier hätten wir gern den Namen der Property für das DbSet so geändert, dass es Fotos statt tblFotos heißt und Elemente des Typs Foto enthält (die auch noch tblFotos heißen). In der Entitätsklasse tblFotos geht es so weiter. Diese hat nach dem Erstellen des Entity Data Models den folgenden Code erhalten:

Imports System.ComponentModel.DataAnnotations
Imports System.ComponentModel.DataAnnotations.Schema
Partial Public Class tblFotos
     Public Property ID As Integer
     <Column(TypeName:="image")>
     <Required>
     Public Property Foto As Byte()
End Class

Wir wollen dies Schritt für Schritt so anpassen, dass wir mit einer Konstruktor-Methode für das Fenster MainWindow wie der folgenden auf die Daten zugreifen können:

Class MainWindow
     Public Sub New()
         Dim dbContext As FotoverwaltungContext
         dbContext = New FotoverwaltungContext
         Dim foto As Foto
         foto = dbContext.Fotos.First()
         MessageBox.Show(foto.ID.ToString())
     End Sub
End Class

Wir wollen also ein DbSet namens Fotos verwenden und damit auf Elemente des Typs Foto zugreifen. Dazu müssen wir dem Entity Data Model auf irgendeine Weise mitteilen, dass es das DbSet namens Fotos auf die Tabelle tblFotos mappen soll und die Klasse Foto auf die einzelnen Datensätze.

Mapping in der Methode OnModelCreating

Der richtige Ort für ein solches Mapping ist die Methode OnModelCreating, die beim Erstellen des Entity Data Models auf Basis der Vorlage Code First aus Datenbank automatisch in der Klasse FotoverwaltungContext angelegt wurde. Der erste Schritt ist das Umbenennen der Klasse tblFotos in Foto. Das erledigen wir ganz einfach, indem wir den entsprechenden Eintrag im Projektmappen-Explorer markieren, diesen nochmals anklicken und dann die Bezeichnung ändern. Danach erscheint noch eine Meldung, die fragt, ob Verweise auf das Codeelement angepasst werden sollen (siehe Bild 1).

Ändern eines Klassennamens

Bild 1: Ändern eines Klassennamens

Wenn Sie hier auf Ja klicken, werden in unserer kleinen Beispielanwendung folgende Änderungen durchgeführt:

  • Die Bezeichnung der Klasse wird ebenfalls in Foto geändert.
  • In der Klasse FotoverwaltungContext wird der Typ der Klasse des DbSets ebenfalls geändert:
Partial Public Class FotoverwaltungContext
     Inherits DbContext
     ...
     Public Overridable Property tblFotos As DbSet(Of Foto)
     ...
End Class

In dieser Klasse sind dann weitere Änderungen nötig. Anschließend sieht die oben bereits teilweise geänderte Zeile mit der Definition des DbSet wie folgt aus:

Public Overridable Property Fotos As DbSet(Of Foto)

Mapping hinzufügen

Damit passen die Deklarationen der Klasse und des DbSet-Elements nun zu dem Code, den wir für die Konstruktor-Methode unseres Fensters MainWindow.xaml erstellt haben. Was geschieht nun, wenn wir die Anwendung starten?

Wir erhalten einen unerwarteten Fehler: Visual Studio bemängelt, dass wir eine Eigenschaft namens Foto in der gleichnamigen Klasse verwenden (siehe Bild 2). Damit erhalten wir also noch ein Problem, das aus der Benennung der Tabellen und Felder der Beispieldatenbank resultiert. Das Feld Foto können wir nicht mit dem Eigenschaftsnamen Foto ansprechen, da eine Klasse keine Eigenschaften besitzen darf, die genauso heißen wir die Klasse selbst. Also ändern wir den Namen der Eigenschaft in der Klasse Foto auf Fotodaten:

Fehler beim Zugriff auf die Klasse

Bild 2: Fehler beim Zugriff auf die Klasse

Partial Public Class Foto
     ...
     Public Property Fotodaten As Byte()
End Class

Nach einem erneuten Start der Anwendung erhalten wir dann die Fehler, mit denen wir gerechnet hätten. Der erste lautet wie folgt und er tritt beim Zugriff auf die Daten über dbContext.Fotos.First auf (siehe auch Bild 3):

Fehler beim Zugriff auf die Tabelle

Bild 3: Fehler beim Zugriff auf die Tabelle

System.InvalidOperationException: "Die Sequenz enthält keine Elemente."

Das ist etwas überraschend, denn wir hatten mit einem Fehler gerechnet, der durch eine fehlende Tabelle ausgelöst wird. Schauen wir uns die Eigenschaften der Auflistung Fotos wie in Bild 4 an, sehen wir, dass es Entity Framework anscheinend versucht, auf eine Tabelle namens Fotoes zuzugreifen. Das ist offensichtlich die Plural-Form der Klasse Foto, die Entity Framework automatisch gebildet hat, um auf die Tabelle zuzugreifen.

Der Zugriff erfolgt auf die nicht vorhandene Tabelle Fotoes

Bild 4: Der Zugriff erfolgt auf die nicht vorhandene Tabelle Fotoes

Damit Entity Framework erkennt, dass wir über die DbSet-Auflistung Fotos auf die Daten der Tabelle tblFotos zugreifen wollen, fügen wir der Methode OnModelCreating die folgende Anweisung hinzu:

Protected Overrides Sub OnModelCreating(ByVal modelBuilder As DbModelBuilder)
     modelBuilder.Entity(Of Foto)().ToTable("tblFotos")
End Sub

Daraufhin erhalten wir die Meldung aus Bild 5, die uns darauf hinweis, dass das Unterstützungsmodell geändert worden sei und wir mit einer Code First-Migration die Änderungen im Datenmodell in die Datenbank übertragen könnten. Was ist damit gemeint?

Unterstützungsmodell geändert?

Bild 5: Unterstützungsmodell geändert?

Schauen wir uns nochmal den Inhalt des Objekts Fotos im Debug-Modus an, sehen wir, dass Entity Framework zwar nun auf die richtige Tabelle namens tblFotos zugreift (siehe Bild 6). Allerdings lautet der Name des Feldes nun Fotodaten. In der Tabelle tblFotos der Datenbank heißt es allerdings Foto. Entity Framework denkt also nun offensichtlich anhand des Unterschiedes zwischen dem in der Abfrage genannten Feldnamen Fotodaten und dem in der Tabelle vorgefundenen Feld Foto, dass der Benutzer das Entity Data Modell geändert hat und bietet eine Möglichkeit an, diese Änderung in die Datenbank zu übertragen. Das wollen wir allerdings nicht, sondern wir möchten das Mapping so anpassen, dass für die Eigenschaft Fotodaten der Entität Foto auf das Feld Foto der Tabelle tblFotos zugegriffen wird.

Die Anwendung versucht zwar, auf die richtige Tabelle zuzugreifen (tblFotos), aber noch nicht auf das richtige Feld.

Bild 6: Die Anwendung versucht zwar, auf die richtige Tabelle zuzugreifen (tblFotos), aber noch nicht auf das richtige Feld.

Also fügen wir noch einen weiteren Teil zur Methode OnModelCreating hinzu, mit der wir das Feld Fotodaten auf das Feld Foto der Tabelle tblFotos mappen. Das sieht dann wie folgt aus:

Protected Overrides Sub OnModelCreating(ByVal modelBuilder As DbModelBuilder)
     modelBuilder.Entity(Of Foto)().
         ToTable("tblFotos").
         Property(Function(t) t.Fotodaten).HasColumnName("Foto")
End Sub

Beim nächsten Start erhalten wir allerdings wieder die gleiche Meldung mit dem Hinweis auf den Einsatz der Code First-Migrationen. Wenn wir uns den Inhalt von dbContect.Fotos ansehen, finden wir allerdings folgende SQL-Anweisung vor:

"SELECT " & vbCrLf & "    [Extent1].[ID] AS [ID], " & vbCrLf & "    [Extent1].[Foto] AS [Foto]" & vbCrLf & " FROM [dbo].[tblFotos] AS [Extent1]"

Wenn wir diese SELECT-Anweisung im SQL Server Management Studio in einer neuen Abfrage für die hier verwendete Datenbank ausführen, erhalten wir allerdings das gewünschte Ergebnis (siehe Bild 7).

Die Abfrage funktioniert wie gewünscht.

Bild 7: Die Abfrage funktioniert wie gewünscht.

Unterschiede per Migration aufdecken

In den Artikeln Datenbank-Initialisierung und Datenbank-Migration haben wir die Migration von Code First-Datenbanken in das jeweilige Datenbanksystem beschrieben. Wie dort erläutert, werden wir nun die Migration aktivieren. Wir wollen allerdings keine Migration ausführen, sondern diese nur nutzen, um herauszufinden, welche Unterschiede zwischen dem Entity Data Model und dem Datenmodell der Datenbank zu dem aufgetretenen Fehler führen. Also öffnen Sie die Paket-Manager-Konsole und geben dort den folgenden Befehl ein:

enable-migrations

Dadurch wird der Migrations-Ordner angelegt, der auch direkt eine Klasse namens ...InitialCreate.vb enthält. Das ist normalerweise nicht der Fall – üblicherweise müssen Sie die erste Migration mit dem Befehl add-migration erstellen.

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.