Gerüstbau (Reverse Engineering)
Reverse Engineering ist der Prozess der Gerüsterstellung von Entitätstypklassen und einer DbContext-Klasse basierend auf einem Datenbankschema. Er kann mithilfe des Befehls Scaffold-DbContext
der Tools der EF Core-Paket-Manager-Konsole (PMC) oder des Befehls dotnet ef dbcontext scaffold
der Tools der .NET-Befehlszeilenschnittstelle (CLI) ausgeführt werden.
Hinweis
Der hier dokumentierte Gerüstbau von DbContext
und Entitätstypen unterscheidet sich von dem Gerüstbau von Controllern in ASP.NET Core über Visual Studio, welcher hier nicht dokumentiert wird.
Tipp
Wenn Sie Visual Studio verwenden, probieren Sie die EF Core Power Tools-Communityerweiterung aus. Diese Tools umfassen ein grafisches Tool, das auf den EF Core-Befehlszeilentools basiert und zusätzliche Workflow- und Anpassungsoptionen bietet.
Voraussetzungen
- Vor dem Gerüstbau müssen Sie entweder die PMC-Tools installieren, die nur in Visual Studio funktionieren, oder die .NET CLI-Tools, die auf allen von .NET unterstützten Plattformen verwendet werden können.
- Installieren Sie das NuGet-Paket für
Microsoft.EntityFrameworkCore.Design
in dem Projekt, in dem der Gerüstbau erfolgt. - Installieren Sie das NuGet-Paket für den Datenbankanbieter, der dem Datenbankschema entspricht, über das Sie ein Gerüst erstellen möchten.
Erforderliche Argumente
Sowohl für den PMC- als auch für den .NET CLI-Befehl sind zwei Argumente erforderlich: die Verbindungszeichenfolge für die Datenbank und der zu verwendende EF Core-Datenbankanbieter.
Verbindungszeichenfolge
Warnung
In diesem Artikel wird eine lokale Datenbank verwendet, für die keine Authentifizierung des Benutzers erforderlich ist. Produktions-Apps sollten den sichersten verfügbaren Ablauf für die Authentifizierung verwenden. Weitere Informationen zur Authentifizierung für bereitgestellte Test- und Produktions-Apps finden Sie unter Sichere Authentifizierungsflows.
Das erste Argument für den Befehl ist eine Verbindungszeichenfolge mit der Datenbank. Die Tools verwenden diese Verbindungszeichenfolge zum Lesen des Datenbankschemas.
Wie die Verbindungszeichenfolge zitiert und escaped ist, hängt von der Shell ab, die zum Ausführen des Befehls verwendet wird. Weitere Informationen finden Sie in der Dokumentation der Shell. PowerShell erfordert z. B. escaping $
, aber nicht \
.
Im folgenden Beispiel erfolgt der Gerüstbau für Entitätstypen und DbContext
aus der Datenbank Chinook
, die sich auf der SQL Server-LocalDB-Instanz des Computers befindet. Dabei wird der Microsoft.EntityFrameworkCore.SqlServer
-Datenbankanbieter verwendet.
dotnet ef dbcontext scaffold "Data Source=(localdb)\MSSQLLocalDB;Initial Catalog=Chinook" Microsoft.EntityFrameworkCore.SqlServer
Tipp
Sie können die Konfiguration verwenden, um die Verbindungszeichenfolge zu speichern und abzurufen.
Verbindungszeichenfolgen im Gerüstcode
Standardmäßig wird die Verbindungszeichenfolge beim Gerüstbau in den Gerüstcode aufgenommen, jedoch mit einer Warnung versehen. Beispiel:
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
#warning To protect potentially sensitive information in your connection string, you should move it out of source code. You can avoid scaffolding the connection string by using the Name= syntax to read it from configuration - see https://go.microsoft.com/fwlink/?linkid=2131148. For more guidance on storing connection strings, see http://go.microsoft.com/fwlink/?LinkId=723263.
=> optionsBuilder.UseSqlServer("Data Source=(LocalDb)\\MSSQLLocalDB;Database=AllTogetherNow");
Dies geschieht, damit der generierte Code bei der ersten Verwendung nicht abstürzt, was für die Lernzwecke nicht zuträglich wäre. Wie die Warnung besagt, sollten jedoch keine Verbindungszeichenfolgen im Produktionscode enthalten sein. Informationen zu den verschiedenen Möglichkeiten zum Verwalten von Verbindungszeichenfolgen finden Sie unter Lebensdauer, Konfiguration und Initialisierung von DbContext.
Tipp
Die Option -NoOnConfiguring
(Visual Studio-PMC) oder --no-onconfiguring
(.NET CLI) kann übergeben werden, um die Erstellung der Methode OnConfiguring
, die die Verbindungszeichenfolge enthält, zu unterdrücken.
Anbietername
Das zweite Argument ist der Anbietername. Der Anbietername entspricht in der Regel dem NuGet-Paketnamen des Anbieters. Verwenden Sie beispielsweise Microsoft.EntityFrameworkCore.SqlServer
für SQL Server oder Azure SQL.
Befehlszeilenoptionen
Der Gerüstbauprozess kann über verschiedene Befehlszeilenoptionen gesteuert werden.
Angeben von Tabellen und Sichten
Standardmäßig werden alle Tabellen und Sichten im Datenbankschema beim Gerüstbau als Entitätstypen erstellt. Sie können einschränken, welche Tabellen und Sichten beim Gerüstbau berücksichtigt werden, indem Sie Schemas und Tabellen angeben.
Das Argument -Schemas
(Visual Studio-PMC) oder --schema
(.NET CLI) gibt die Schemas von Tabellen und Sichten an, für die Entitätstypen generiert werden. Wenn dieses Argument nicht angegeben wird, werden alle Schemas einbezogen. Bei Verwendung dieser Option werden alle Tabellen und Sichten in den Schemas auch dann in das Modell einbezogen, wenn sie nicht explizit mit -Tables
oder --table
eingeschlossen werden.
Über das Argument -Tables
(Visual Studio-PMC) oder --table
(.NET CLI) wurden die Tabellen und Sichten angegeben, für die Entitätstypen generiert werden. Tabellen oder Sichten in einem bestimmten Schema können im Format „schema.table“ bzw. „schema.view“ eingefügt werden. Wenn diese Option nicht angegeben wird, werden alle Tabellen und Sichten einbezogen. |
So führen Sie den Gerüstbau beispielsweise nur für die Tabellen Artists
und Albums
durch:
dotnet ef dbcontext scaffold ... --table Artist --table Album
So führen Sie den Gerüstbau für alle Tabellen und Sichten aus den Schemas Customer
und Contractor
durch:
dotnet ef dbcontext scaffold ... --schema Customer --schema Contractor
So führen Sie den Gerüstbau beispielsweise für die Tabelle Purchases
aus dem Schema Customer
und die Tabellen Accounts
und Contracts
aus dem Schema Contractor
durch:
dotnet ef dbcontext scaffold ... --table Customer.Purchases --table Contractor.Accounts --table Contractor.Contracts
Beibehalten von Datenbanknamen
Tabellen- und Spaltennamen wurden korrigiert, damit sie den .NET-Namenskonventionen für Typen und Eigenschaften standardmäßig besser entsprechen. Wenn Sie -UseDatabaseNames
(Visual Studio-PMC) oder --use-database-names
(.NET CLI) angeben, wird dieses Verhalten deaktiviert, und die ursprünglichen Datenbanknamen werden so weit wie möglich beibehalten. Ungültige .NET-Bezeichner werden weiterhin korrigiert und synthetisierte Namen wie Navigationseigenschaften werden weiterhin den .NET-Namenskonventionen entsprechen.
Sehen Sie sich beispielsweise die folgenden Tabellen an:
CREATE TABLE [BLOGS] (
[ID] int NOT NULL IDENTITY,
[Blog_Name] nvarchar(max) NOT NULL,
CONSTRAINT [PK_Blogs] PRIMARY KEY ([ID]));
CREATE TABLE [posts] (
[id] int NOT NULL IDENTITY,
[postTitle] nvarchar(max) NOT NULL,
[post content] nvarchar(max) NOT NULL,
[1 PublishedON] datetime2 NOT NULL,
[2 DeletedON] datetime2 NULL,
[BlogID] int NOT NULL,
CONSTRAINT [PK_Posts] PRIMARY KEY ([id]),
CONSTRAINT [FK_Posts_Blogs_BlogId] FOREIGN KEY ([BlogID]) REFERENCES [Blogs] ([ID]) ON DELETE CASCADE);
Standardmäßig werden beim Gerüstbau aus diesen Tabellen die folgenden Entitätstypen erstellt:
public partial class Blog
{
public int Id { get; set; }
public string BlogName { get; set; } = null!;
public virtual ICollection<Post> Posts { get; set; } = new List<Post>();
}
public partial class Post
{
public int Id { get; set; }
public string PostTitle { get; set; } = null!;
public string PostContent { get; set; } = null!;
public DateTime _1PublishedOn { get; set; }
public DateTime? _2DeletedOn { get; set; }
public int BlogId { get; set; }
public virtual Blog Blog { get; set; } = null!;
public virtual ICollection<Tag> Tags { get; set; } = new List<Tag>();
}
Die Verwendung von -UseDatabaseNames
oder --use-database-names
führt jedoch zu den folgenden Entitätstypen:
public partial class BLOG
{
public int ID { get; set; }
public string Blog_Name { get; set; } = null!;
public virtual ICollection<post> posts { get; set; } = new List<post>();
}
public partial class post
{
public int id { get; set; }
public string postTitle { get; set; } = null!;
public string post_content { get; set; } = null!;
public DateTime _1_PublishedON { get; set; }
public DateTime? _2_DeletedON { get; set; }
public int BlogID { get; set; }
public virtual BLOG Blog { get; set; } = null!;
}
Verwenden von Zuordnungsattributen (auch als Datenanmerkungen bezeichnet)
Entitätstypen werden standardmäßig mithilfe der ModelBuilder
-API in OnModelCreating
konfiguriert. Geben Sie -DataAnnotations
(PMC) oder --data-annotations
(.NET Core CLI) an, um stattdessen nach Möglichkeit Zuordnungsattribute zu verwenden.
Wenn Sie beispielsweise die Fluent-API verwenden, wird folgendes Gerüst aufgebaut:
entity.Property(e => e.Title)
.IsRequired()
.HasMaxLength(160);
Während der Verwendung von Datenanmerkungen wird folgendes Gerüst aufgebaut:
[Required]
[StringLength(160)]
public string Title { get; set; }
Tipp
Einige Aspekte des Modells können nicht mithilfe von Zuordnungsattributen konfiguriert werden. Beim Gerüstbau wird weiterhin die Modellerstellungs-API verwendet, um diese Fälle zu behandeln.
DbContext-Name
Der Name der per Gerüstbau erstellten DbContext
-Klasse ist standardmäßig der Name der Datenbank mit dem Suffix Context. Verwenden Sie -Context
in PMC und --context
in der .NET Core CLI, um einen anderen anzugeben.
Zielverzeichnisse und -namespaces
Die Entitätsklassen und eine DbContext-Klasse werden per Gerüstbau im Stammverzeichnis des Projekts erstellt und verwenden den Standardnamespace des Projekts.
Sie können das Verzeichnis angeben, in dem Klassen per Gerüstbau mithilfe von --output-dir
erstellt werden, und --context-dir
kann zum Erstellen der DbContext-Klasse per Gerüstbau in einem separaten Verzeichnis aus den Entitätstypklassen verwendet werden:
dotnet ef dbcontext scaffold ... --context-dir Data --output-dir Models
Standardmäßig sind der Namespace der Stammnamespace sowie die Namen aller Unterverzeichnisse im Stammverzeichnis des Projekts. Sie können jedoch den Namespace für alle Ausgabeklassen mit --namespace
überschreiben. Sie können den Namespace auch nur für die DbContext-Klasse überschreiben, indem Sie --context-namespace
verwenden:
dotnet ef dbcontext scaffold ... --namespace Your.Namespace --context-namespace Your.DbContext.Namespace
Der Gerüstcode
Beim Gerüstbau aus einer vorhandenen Datenbank wird Folgendes erstellt:
- Eine Datei mit einer Klasse, die von
DbContext
erbt - Eine Datei für jeden Entitätstyp
Tipp
Ab EF7 können Sie auch T4-Textvorlagen verwenden, um den generierten Code anzupassen. Weitere Details finden Sie unter Benutzerdefinierte Reverse Engineering-Vorlagen.
Nullable-Verweistypen in C#
Beim Gerüstbau können ein EF-Modell und Entitätstypen eingerichtet werden, die C#-Nullable-Verweistypen (Nullable Reference Types, NRTs) verwenden. Die NRT-Nutzung wird automatisch erstellt, wenn die NRT-Unterstützung in dem C#-Projekt aktiviert ist, in dem der Code erstellt wird.
Die folgende Tags
-Tabelle enthält z. B. beide Nullwerte zulassende Non-Nullable-Zeichenfolgenspalten:
CREATE TABLE [Tags] (
[Id] int NOT NULL IDENTITY,
[Name] nvarchar(max) NOT NULL,
[Description] nvarchar(max) NULL,
CONSTRAINT [PK_Tags] PRIMARY KEY ([Id]));
Dies führt in der generierten Klasse zu entsprechenden Nullwerte zulassenden und Non-Nullable-Zeichenfolgeneigenschaften:
public partial class Tag
{
public Tag()
{
Posts = new HashSet<Post>();
}
public int Id { get; set; }
public string Name { get; set; } = null!;
public string? Description { get; set; }
public virtual ICollection<Post> Posts { get; set; }
}
Auf ähnliche Weise enthalten die folgenden Posts
-Tabellen eine erforderliche Beziehung zur Blogs
-Tabelle:
CREATE TABLE [Posts] (
[Id] int NOT NULL IDENTITY,
[Title] nvarchar(max) NOT NULL,
[Contents] nvarchar(max) NOT NULL,
[PostedOn] datetime2 NOT NULL,
[UpdatedOn] datetime2 NULL,
[BlogId] int NOT NULL,
CONSTRAINT [PK_Posts] PRIMARY KEY ([Id]),
CONSTRAINT [FK_Posts_Blogs_BlogId] FOREIGN KEY ([BlogId]) REFERENCES [Blogs] ([Id]));
Dies führt zum Gerüstbau einer Non-Nullable--Beziehung (erforderlich) zwischen Blogs:
public partial class Blog
{
public Blog()
{
Posts = new HashSet<Post>();
}
public int Id { get; set; }
public string Name { get; set; } = null!;
public virtual ICollection<Post> Posts { get; set; }
}
Sowie zwischen Beiträgen:
public partial class Post
{
public Post()
{
Tags = new HashSet<Tag>();
}
public int Id { get; set; }
public string Title { get; set; } = null!;
public string Contents { get; set; } = null!;
public DateTime PostedOn { get; set; }
public DateTime? UpdatedOn { get; set; }
public int BlogId { get; set; }
public virtual Blog Blog { get; set; } = null!;
public virtual ICollection<Tag> Tags { get; set; }
}
M:n-Beziehungen
Beim Gerüstbauprozess werden einfache Jointabellen erkannt, und es wird automatisch eine m:n-Zuordnung für sie generiert. Betrachten Sie beispielsweise Tabellen für Posts
und Tags
und eine Jointabelle, PostTag
, die sie verbindet:
CREATE TABLE [Tags] (
[Id] int NOT NULL IDENTITY,
[Name] nvarchar(max) NOT NULL,
[Description] nvarchar(max) NULL,
CONSTRAINT [PK_Tags] PRIMARY KEY ([Id]));
CREATE TABLE [Posts] (
[Id] int NOT NULL IDENTITY,
[Title] nvarchar(max) NOT NULL,
[Contents] nvarchar(max) NOT NULL,
[PostedOn] datetime2 NOT NULL,
[UpdatedOn] datetime2 NULL,
CONSTRAINT [PK_Posts] PRIMARY KEY ([Id]));
CREATE TABLE [PostTag] (
[PostsId] int NOT NULL,
[TagsId] int NOT NULL,
CONSTRAINT [PK_PostTag] PRIMARY KEY ([PostsId], [TagsId]),
CONSTRAINT [FK_PostTag_Posts_TagsId] FOREIGN KEY ([TagsId]) REFERENCES [Tags] ([Id]) ON DELETE CASCADE,
CONSTRAINT [FK_PostTag_Tags_PostsId] FOREIGN KEY ([PostsId]) REFERENCES [Posts] ([Id]) ON DELETE CASCADE);
Beim Gerüstbau wird dadurch eine Klasse für „Post“ erstellt:
public partial class Post
{
public Post()
{
Tags = new HashSet<Tag>();
}
public int Id { get; set; }
public string Title { get; set; } = null!;
public string Contents { get; set; } = null!;
public DateTime PostedOn { get; set; }
public DateTime? UpdatedOn { get; set; }
public int BlogId { get; set; }
public virtual Blog Blog { get; set; } = null!;
public virtual ICollection<Tag> Tags { get; set; }
}
Dazu kommt eine Klasse für Tag:
public partial class Tag
{
public Tag()
{
Posts = new HashSet<Post>();
}
public int Id { get; set; }
public string Name { get; set; } = null!;
public string? Description { get; set; }
public virtual ICollection<Post> Posts { get; set; }
}
Es gibt jedoch keine Klasse für die PostTag
-Tabelle. Stattdessen wird ein Gerüst für die Konfiguration für eine n:n-Beziehung erstellt:
entity.HasMany(d => d.Tags)
.WithMany(p => p.Posts)
.UsingEntity<Dictionary<string, object>>(
"PostTag",
l => l.HasOne<Tag>().WithMany().HasForeignKey("PostsId"),
r => r.HasOne<Post>().WithMany().HasForeignKey("TagsId"),
j =>
{
j.HasKey("PostsId", "TagsId");
j.ToTable("PostTag");
j.HasIndex(new[] { "TagsId" }, "IX_PostTag_TagsId");
});
Weitere Programmiersprachen
Die von Microsoft veröffentlichten EF Core-Pakete generieren C#-Code beim Gerüstbau. Das zugrunde liegende Gerüstbausystem unterstützt jedoch ein Plug-In-Modell für den Gerüstbau in anderen Sprachen. Dieses Plug-In-Modell wird beispielsweise in folgenden von der Community ausgeführten Projekten eingesetzt:
- EntityFrameworkCore.VisualBasic bietet Unterstützung für Visual Basic.
- EFCore.FSharp bietet Unterstützung für F#.
Anpassen des Codes
Ab EF7 besteht eine der besten Möglichkeiten zum Anpassen des generierten Codes darin, die zum Generieren verwendeten T4-Vorlagen anzupassen.
Der Code kann auch nach dem Generieren noch geändert werden. Die beste Vorgehensweise hierzu hängt jedoch davon ab, ob Sie den Gerüstbauprozess erneut ausführen möchten, sobald sich das Datenbankmodell ändert.
Einmaliges Durchführen des Gerüstbaus
Bei diesem Ansatz bietet der Gerüstcode einen Ausgangspunkt für die weitere codebasierte Zuordnung. Alle Änderungen am generierten Code können wie gewünscht vorgenommen werden – er wird wie jeder andere Code in Ihrem Projekt verwendet.
Die Synchronisierung der Datenbank und des EF-Modells kann auf zwei Arten erfolgen:
- Stellen Sie auf die Verwendung von EF Core-Datenbankmigrationen um, und verwenden Sie die Entitätstypen und die EF-Modellkonfiguration als alleinige verlässliche Quelle, indem Sie Migrationen zum Steuern des Schemas verwenden.
- Aktualisieren Sie die Entitätstypen und die EF-Konfiguration manuell, wenn sich die Datenbank ändert. Wenn einer Tabelle beispielsweise eine neue Spalte hinzugefügt wird, fügen Sie dem zugeordneten Entitätstyp eine Eigenschaft für die Spalte hinzu, und nehmen Sie die erforderliche Konfiguration mithilfe von Zuordnungsattributen und/oder Code in
OnModelCreating
vor. Dieser Ansatz ist relativ einfach. Die einzige Herausforderung besteht darin sicherzustellen, dass Datenbankänderungen aufgezeichnet oder in irgendeiner Weise erkannt werden, damit die für den Code verantwortlichen Entwickler reagieren können.
Wiederholter Gerüstbau
Ein alternativer Ansatz zum einmaligen Gerüstbau besteht darin, jedes Mal ein neues Gerüst zu erstellen, wenn sich die Datenbank ändert. Dadurch wird der gesamte zuvor erstellte Gerüstcode überschrieben, sodass alle Änderungen, die an Entitätstypen oder der EF-Konfiguration in diesem Code vorgenommen werden, verloren gehen.
[TIPP] Um vor versehentlichem Codeverlust zu schützen, wird vorhandener Code durch die EF-Befehle standardmäßig nicht überschrieben. Das Argument
-Force
(Visual Studio-PMC) oder--force
(.NET CLI) kann verwendet werden, um das Überschreiben vorhandener Dateien zu erzwingen.
Da der Gerüstcode überschrieben wird, empfiehlt es sich, ihn nicht direkt zu ändern, sondern stattdessen auf partielle Klassen und Methoden sowie die Mechanismen in EF Core zurückzugreifen, mit denen die Konfiguration überschrieben werden kann. Dies gilt insbesondere in folgenden Fällen:
- Sowohl die
DbContext
-Klasse als auch die Entitätsklassen werden als partielle Klassen generiert. Dadurch können zusätzliche Member und Code in einer separaten Datei eingefügt werden, die beim Ausführen des Gerüstbaus nicht überschrieben wird. - Die
DbContext
-Klasse enthält eine partielle Methode namensOnModelCreatingPartial
. Eine Implementierung dieser Methode kann der partiellen Klasse fürDbContext
hinzugefügt werden. Sie wird dann nach dem Aufruf vonOnModelCreating
aufgerufen. - Die mithilfe der
ModelBuilder
-APIs durchgeführte Modellkonfiguration überschreibt alle Konfigurationen, die durch Konventionen oder Zuordnungsattribute vorgenommen wurden, sowie frühere Konfigurationen, die für den Modell-Generator durchgeführt wurden. Dies bedeutet, dass Code inOnModelCreatingPartial
verwendet werden kann, um die vom Gerüstbauprozess generierte Konfiguration außer Kraft zu setzen, ohne dass diese Konfiguration entfernt werden muss.
Zur Erinnerung: Ab EF7 können die zum Generieren von Code verwendeten T4-Vorlagen angepasst werden. Dies ist häufig ein effektiverer Ansatz als der Gerüstbau mit den Standardwerten und die anschließende Bearbeitung mit partiellen Klassen und/oder Methoden.
Funktionsweise
Reverse Engineering beginnt mit dem Lesen des Datenbankschemas. Es liest Informationen über Tabellen, Spalten, Einschränkungen und Indizes.
Anschließend werden die Schemainformationen verwendet, um ein EF Core-Modell zu erstellen. Tabellen werden zum Erstellen von Entitätstypen verwendet; Spalten werden zum Erstellen von Eigenschaften verwendet; und Fremdschlüssel werden zum Erstellen von Beziehungen verwendet.
Schließlich wird das Modell verwendet, um Code zu generieren. Die entsprechenden Entitätstypklassen, Fluent-API und Datenanmerkungen werden per Gerüstbau erstellt, um dasselbe Modell aus Ihrer App neu zu erstellen.
Einschränkungen
- Nicht alles über ein Modell kann mithilfe eines Datenbankschemas dargestellt werden. Informationen über Vererbungshierarchien, eigene Typen und Tabellenteilungen sind beispielsweise nicht im Datenbankschema vorhanden. Aus diesem Grund wird für diese Konstrukte niemals ein Gerüstbau durchgeführt.
- Darüber hinaus werden einige Spaltentypen möglicherweise nicht vom EF Core-Anbieter unterstützt. Diese Spalten werden nicht im Modell enthalten sein.
- Sie können Parallelitätstoken in einem EF Core-Modell definieren, um zu verhindern, dass zwei Benutzer*innen gleichzeitig dieselbe Entität aktualisieren. Einige Datenbanken weisen einen speziellen Typ zur Darstellung dieses Spaltentyps auf (z. B. „rowversion“ in SQL Server). In diesem Fall kann für diese Informationen ein Reverse Engineering erfolgen. Für andere Parallelitätstoken wird jedoch kein Gerüstbau durchgeführt.