Nicht alle Entwicklungsprojekte beginnen mit dem initialen Entwurf am Reißbrett. Besteht vielmehr die Anforderung ein bestehendes relationales Datenbanksystem in deine .NET Softwareanwendung zu integrieren, dann stellt dir Entity Framework Core (im Folgenden EF Core) ein nützliches Werkzeug zur Verfügung, den erforderlichen Database-First Approach zu meistern.
Dieser Blogbeitrag zeigt dir, wie die EF Core CLI das Reverse Engineering eines bestehenden relationalen Datenbanksystems vereinfacht und die Integration in deine Softwareanwendung entscheidend beschleunigen kann. EF Core ist das aktuell populärste ORM Framework für Microsoft .NET.
Vorteile
Folgende Vorteile bieten sich dir:
- Du gelangst schnell zu einem objektorientierten Datenbankmodell für deine .NET Softwareanwendung.
- EF Core bietet automatische Codegenerierung von Entity-Klassen (Modelle) und der DbContext-Klasse (Verbindung mit der Datenbank).
- Durch Verwendung von
partial
Klassen undvirtual
Properties ist der generierte Code flexibel durch eigene Logik erweiterbar. - EF Core unterstützt u.a. LINQ-Abfragen und Änderungsverfolgung deiner Datensätze.
Relationale Referenzdatenbank
Das folgende Entity Relationship Diagramm zeigt die Struktur der im Beitrag verwendeten Referenzdatenbank.
Die gezeigte Datenbank könnte einer Kalenderanwendung zugrundeliegen, in der ein Nutzer Termine in seinem Kalender verwalten und diese dann mit Erinnerungen versehen kann. Die Tabellen verbindet jeweils eine 1:N Relation. Die Referenzdatenbank kannst du über das DDL-Skript db_server_conf.sql
erzeugen.
Einstieg
Sofern du Docker auf deinem Rechner installiert hast, kannst du die Referenzdatenbank auf einem MySQL Server innerhalb eines Docker Containers ausführen. Starte dafür einfach den Docker Engine und führe anschließend das Shell-Skript run_mysql_server.sh
aus.
Über den folgenden Connection String kannst du deine Anwendungen dann mit der Datenbank verbinden:
Server=localhost; Port=4200; Username=root; Password=pasSworD; Database=db_scaffold_efcore;
Wenn du auf deinem Rechner einen MySQL Datenbankserver installiert hast, dann kannst du auch diesen verwenden. Stelle dann aber eine entsprechende Konfiguration sicher.
Projekt für Codegenerierung erstellen und einrichten
Zu Beginn musst du EF Core einmalig auf deinem Rechner installieren.
#!/bin/sh dotnet tool install --global dotnet-ef
Für die Referenzdatenbank kann EF Core jetzt den entsprechenden Code generieren. Dafür erstellst du vorab ein classlib
Projekt und fügst anschließend die notwendigen Pakete hinzu.
#!/bin/sh LIB="Scaffold.Library" # Create project. dotnet new classlib -n "$LIB" # Add required packages. cd "$LIB" dotnet add package Microsoft.EntityFrameworkCore dotnet add package Microsoft.EntityFrameworkCore.Design dotnet add package Microsoft.EntityFrameworkCore.Relational dotnet add package Pomelo.EntityFrameworkCore.MySql
Die Pakete geben dir Zugriff auf die EF Core Basis- und Design-Time Komponenten. Weiterhin werden Komponenten für die Einbindung relationaler Datenbanken, sowie Pomelo’s MySQL Datenbank-Provider für EF Core verfügbar.
Shell-Skript für Codegenerierung erstellen
Im nächsten Schritt erstellst du ein Shell-Skript, welches du für die automatische Codegenerierung der Entity-Klassen und der DbContext-Klasse verwenden kannst.
Der folgende Codeblock zeigt dir beispielhaft, welche Möglichkeiten zur Konfiguration des Codegenerators zur Verfügung stehen und wie dieser gestartet wird. Das Projekt kann nach der Codegenerierung direkt kompiliert werden.
#!/bin/sh # File: ef_dbcontext_scaffold.sh # Configure code generator (required). CONNECTION="Server=localhost; Port=4200; Username=root; Password=pasSworD; Database=db_scaffold_efcore;" PROVIDER="Pomelo.EntityFrameworkCore.MySql" # Configure code generator (optional). CONTEXT="DatabaseContext" CONTEXT_DIR="./Data/Generated" CONTEXT_NAMESPACE="Scaffold.Library.Data" OUTPUT_DIR="./Models/Generated" NAMESPACE="Scaffold.Library.Models" # Start code generation. dotnet ef dbcontext scaffold "$CONNECTION" "$PROVIDER" \ --context "$CONTEXT" \ --context-dir "$CONTEXT_DIR" \ --context-namespace "$CONTEXT_NAMESPACE" \ --output-dir "$OUTPUT_DIR" \ --namespace "$NAMESPACE" \ --force # Build project. dotnet build
Die Angabe der Datenbankverbindung $CONNECTION
und des Datenbank-Providers $PROVIDER
sind obligatorisch. Alle weiteren Angaben sind optional und entsprechend deinen Anforderungen frei konfigurierbar.
Die folgende Tabelle beschreibt die gezeigten Optionen. Weitere Optionen sind auf der Webseite Microsoft Docs dokumentiert.
Option | Beschreibung |
--context | Name der DbContext-Klasse. |
--context-dir | Verzeichnis der DbContext-Klasse (relativ zum Projektverzeichnis). |
--context-namespace | Namespace der DbContext-Klasse. |
--output-dir | Verzeichnis der Entity-Klassen (relativ zum Projektverzeichnis). |
--namespace | Namespace der Entity-Klassen. |
--force | Erzwingt das Überschreiben vorhandener Dateien. |
Analyse des generierten Codes
Im Anschluss an die Codegenerierung analysieren wir beispielhaft die Entity-Klasse Meeting
, siehe dazu den folgenden Codeblock.
// Generated file: Meeting.cs using System; using System.Collections.Generic; namespace Scaffold.Library.Models { public partial class Meeting { public Meeting() { Reminders = new HashSet<Reminder>(); } public uint Id { get; set; } public string Title { get; set; } = null!; public string? Description { get; set; } public DateTime? CreatedAt { get; set; } public DateTime? ChangedAt { get; set; } public DateTime StartAt { get; set; } public uint Duration { get; set; } public uint CalendarId { get; set; } public virtual Calendar Calendar { get; set; } = null!; public virtual ICollection<Reminder> Reminders { get; set; } } }
Der Namespace stimmt mit der Konfiguration überein. Die Entity-Klasse Meeting
ist partial
und besitzt einen public
Konstruktor, der das Navigation-Property Reminders
initialisiert. Weiterhin verfügt die Entity-Klasse über auto-implemented public
Property-Accessors. Optionale oder durch die Datenbank automatisch gesetzte Felder entsprechen nullable
Properties, siehe z.B. Description
oder CreatedAt
. Die Navigation-Properties Calendar
und Reminders
sind virtual
. Außerdem wandelt der Codegenerator die SQL konforme Snake-Case Notation changed_at
in .NET konforme Pascal-Case Notation ChangedAt
um.
Den kompletten generierten Code findest du auch im GitHub Repository.
Flexible Erweiterung des generierten Codes
Durch die partial
Modifizierer sind alle generierten Klassen flexibel durch eigenen Code erweiterbar. So kannst du die Entity-Klasse Meeting
leicht um eigene Methoden erweitern und diese in einer separaten Datei implementieren. Bei einer erneuten Codegenerierung gehen eigene Erweiterungen somit nicht verloren.
Der folgende Codeblock zeigt eine Fabrikmethode Create()
, die neue Instanzen erstellen kann. Die Methode IsStartInFuture()
kann bestimmen, ob der Startzeitpunkt StartAt
eines Meeting
in der Zukunft liegt.
// File: Meeting.cs using System; namespace Scaffold.Library.Models; public partial class Meeting { public static Meeting Create( string title, string? description, uint duration, DateTime startAt, uint calendarId) { return new Meeting { Title = title, Description = description, Duration = duration, StartAt = startAt, CalendarId = calendarId }; } public bool IsStartInFuture() { return StartAt > DateTime.UtcNow; } }
Solltest du die Entity-Klasse Meeting
als Eltern-Klasse verwenden wollen, dann kann eine von ihr abgeleitete Kind-Klasse das Verhalten der auto-implemented virtual
Navigation-Properties Calendar
und Reminders
überschreiben. Ein Beispiel dafür findest du auf der Webseite Microsoft Docs.
Anwendungsbeispiel (Proof of Concept)
In einer kleinen Beispielanwendung zeigst du, dass der Code aus dem oben erstellen classlib
Projekt leicht in eine ausführbare Anwendung integriert werden kann.
Dafür erstellst du ein console
Projekt und fügst anschließend die notwendigen Referenzen und Pakete hinzu.
#!/bin/sh LIB="Scaffold.Library" APP="Scaffold.App" # Create project. dotnet new console -n "$APP" # Add required reference. dotnet add "$APP"/"$APP".csproj reference "$LIB"/"$LIB".csproj # Add required packages. cd "$APP" dotnet add package Microsoft.EntityFrameworkCore dotnet add package Microsoft.EntityFrameworkCore.Relational
Die Konsolenanwendung in Program.cs
könnte die folgende Methode StartMinimal()
ausführen. In der Methode werden der Datenbank über den verwendeten DatabaseContext
Datensätze für die Entitäten Calendar
, Meeting
und Reminder
hinzugefügt. Hierbei werden ebenfalls die von uns implementierten Fabrikmethoden Create()
, sowie die Methode IsStartInFuture()
verwendet.
// File: Demo.cs using System; using System.Linq; using Scaffold.Library.Data; using Scaffold.Library.Models; namespace Scaffold.App; public static class Demo { public static void StartMinimal() { /* * Add database context into scope. */ using var context = new DatabaseContext(); /* * Add calendar. */ var calendar = Calendar.Create( owner: "Arthur Dent" ); context.Calendars.Add(calendar); context.SaveChanges(); /* * Add meeting to calendar. */ var meeting = Meeting.Create( title: "Have lunch with Zaphod Beeblebrox", description: "Ford's semi-half-cousin likes tea", duration: 42, startAt: DateTime.UtcNow.AddDays(10), calendarId: calendar.Id ); context.Meetings.Add(meeting); context.SaveChanges(); /* * Check meeting start date. */ Console.WriteLine($"Does meeting '{meeting.Title}' start in future? " + $"{meeting.IsStartInFuture()}."); /* * Add reminders to meeting. */ context.Reminders.AddRange( Reminder.Create( remindBefore: 4, meetingId: meeting.Id), Reminder.Create( remindBefore: 2, meetingId: meeting.Id) ); context.SaveChanges(); } }
Wenn du das Anwendungsbeispiel mit Hilfe von Docker und run_mysql_server.sh
ausführst, dann kannst du in deinem Browser über die URL http://localhost:4300/
den Datenbank Adminer öffnen.
Die hier gezeigte und eine weitere Beispielanwendung findest du auch im GitHub Repository.
Fazit
Das besprochene Beispiel zeigt dir die ersten Schritte auf dem Weg, ein bestehendes relationales Datenbanksystem in deine .NET Softwareanwendung zu integrieren. Der Hauptaufwand liegt in der Einrichtung einer geeigneten Projektstruktur und der Implementierung einiger Shell-Skripte zur automatischen Codegenerierung mit Hilfe der EF Core CLI.
Weitere Schritte werden sicherlich nötig sein, aber der Aufwand wird sich lohnen. Nach erfolgreicher Integration stehen dir mächtige EF Core Funktionalitäten, wie z.B. LINQ-Abfragen und Änderungsverfolgung deiner Datensätze, zur Verfügung.
Den Code zum Blog findest du auch auf GitHub.
Happy Coding!
Die Beitragsbilder wurden zur Verfügung gestellt von Vecteezy.com.