Razor Pages mit Entity Framework Core in ASP.NET Core – Tutorial 1 bis 8

Von Tom Dykstra, Jeremy Likness und Jon P. Smith

Dies ist das erste einer Reihe von Tutorials, die zeigen, wie Entity Framework (EF) Core für eine Razor Pages-App mit ASP.NET Core verwendet wird. In den Tutorials wird eine Website für eine fiktive Contoso University erstellt. Sie enthält Funktionen wie die Zulassung von Studenten, die Erstellung von Kursen und Aufgaben von Dozenten. In diesem Tutorial wird der Code-First-Ansatz verwendet. Informationen zum Durcharbeiten dieses Tutorials mit dem Database-First-Ansatz finden Sie in diesem GitHub-Issue.

Laden Sie die vollständige App herunter, oder zeigen Sie sie an.Anweisungen zum Herunterladen.

Voraussetzungen

  • Wenn Sie noch nicht mit Razor Pages vertraut sind, arbeiten Sie zuerst die Tutorialreihe Erste Schritte mit Razor Pages durch, bevor Sie mit diesem Tutorial beginnen.

Datenbank-Engines

Die Visual Studio-Anweisungen verwenden SQL Server LocalDB, eine Version von SQL Server Express, die nur unter Windows ausgeführt werden kann.

Problembehandlung

Wenn Sie auf ein Problem stoßen, das Sie nicht lösen können, vergleichen Sie Ihren den Code mit dem vollständigen Projekt. Eine gute Möglichkeit, Hilfe zu erhalten, besteht darin, eine Frage auf StackOverflow.com zu stellen und dabei das ASP.NET Core-Tag oder das EF Core-Tag zu verwenden.

Die Beispiel-App

Bei der App, die mithilfe dieser Tutorials erstellt werden soll, handelt es sich um eine einfache Website einer Universität. Benutzer können Informationen zu den Studenten, Kursen und Dozenten abrufen. Im Folgenden sind einige Anzeigen dargestellt, die mithilfe dieses Tutorials erstellt werden.

Students Index page

Students Edit page

Der Benutzeroberflächenstil dieser Website basiert auf den integrierten Projektvorlagen. Das Tutorial konzentriert sich auf die Verwendung von EF Core mit ASP.NET Core und nicht auf die Anpassung der Benutzeroberfläche.

Optional: Erstellen des Beispieldownloads

Dieser Schritt ist optional. Wenn Probleme vorliegen, die nicht behoben werden können, sollten Sie die vollständige App erstellen. Wenn Sie auf ein Problem stoßen, das Sie nicht lösen können, vergleichen Sie Ihren den Code mit dem vollständigen Projekt. Anweisungen zum Download.

Wählen Sie ContosoUniversity.csproj aus, um das Projekt zu öffnen.

  • Erstellen Sie das Projekt.

  • Führen Sie folgenden Befehl in der Paket-Manager-Konsole aus:

    Update-Database
    

Führen Sie das Projekt aus, um das Seeding der Datenbank auszuführen.

Erstellen des Web-App-Projekts

  1. Starten Sie Visual Studio 2022, und wählen Sie Neues Projekt erstellen aus.

    Create a new project from the start window

  2. Wählen Sie im Dialogfeld Neues Projekt erstellen die Option ASP.NET Core-Web-App aus, und klicken Sie dann auf Weiter.

    Create an ASP.NET Core Web App

  3. Geben Sie ContosoUniversity im Dialogfeld Neues Projekt konfigurieren für Projektname ein. Es ist wichtig, dem Projekt den Namen ContosoUniversity in übereinstimmender Groß-/Kleinschreibung zu geben, damit die Namespaces übereinstimmen, wenn Sie Beispielcode kopieren und einfügen.

  4. Wählen Sie Weiter aus.

  5. Wählen Sie im Dialogfeld Zusätzliche Informationen die Option .NET 6.0 (Langfristiger Support) und dann Erstellen aus.

    Additional information

Einrichten des Websitestils

Kopieren Sie den folgenden Code, und fügen Sie ihn in die Datei Pages/Shared/_Layout.cshtml ein:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>@ViewData["Title"] - Contoso University</title>
    <link rel="stylesheet" href="~/lib/bootstrap/dist/css/bootstrap.css" />
    <link rel="stylesheet" href="~/css/site.css" asp-append-version="true" />
    <link rel="stylesheet" href="~/ContosoUniversity.styles.css" asp-append-version="true" />
</head>
<body>
    <header>
        <nav class="navbar navbar-expand-sm navbar-toggleable-sm navbar-light bg-white border-bottom box-shadow mb-3">
            <div class="container">
                <a class="navbar-brand" asp-area="" asp-page="/Index">Contoso University</a>
                <button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target=".navbar-collapse" aria-controls="navbarSupportedContent"
                        aria-expanded="false" aria-label="Toggle navigation">
                    <span class="navbar-toggler-icon"></span>
                </button>
                <div class="navbar-collapse collapse d-sm-inline-flex justify-content-between">
                    <ul class="navbar-nav flex-grow-1">                        
                        <li class="nav-item">
                            <a class="nav-link text-dark" asp-area="" asp-page="/About">About</a>
                        </li>
                        <li class="nav-item">
                            <a class="nav-link text-dark" asp-area="" asp-page="/Students/Index">Students</a>
                        </li>
                        <li class="nav-item">
                            <a class="nav-link text-dark" asp-area="" asp-page="/Courses/Index">Courses</a>
                        </li>
                        <li class="nav-item">
                            <a class="nav-link text-dark" asp-area="" asp-page="/Instructors/Index">Instructors</a>
                        </li>
                        <li class="nav-item">
                            <a class="nav-link text-dark" asp-area="" asp-page="/Departments/Index">Departments</a>
                        </li>
                    </ul>
                </div>
            </div>
        </nav>
    </header>
    <div class="container">
        <main role="main" class="pb-3">
            @RenderBody()
        </main>
    </div>

    <footer class="border-top footer text-muted">
        <div class="container">
            &copy; 2021 - Contoso University - <a asp-area="" asp-page="/Privacy">Privacy</a>
        </div>
    </footer>

    <script src="~/lib/jquery/dist/jquery.js"></script>
    <script src="~/lib/bootstrap/dist/js/bootstrap.bundle.js"></script>
    <script src="~/js/site.js" asp-append-version="true"></script>

    @await RenderSectionAsync("Scripts", required: false)
</body>
</html>

Mit der Layoutdatei werden die Kopfzeile, die Fußzeile und das Menü der Website festgelegt. Durch den vorangehenden Code werden folgende Änderungen vorgenommen:

  • Jedes Vorkommen von „ContosoUniversity“ wird in „Contoso University“ geändert. Diese Begriffskombination kommt dreimal vor.
  • Die Menüeinträge Home und Privacy werden gelöscht.
  • Es werden Einträge für About, Students, Courses, Instructors und Departmens hinzugefügt.

Ersetzen Sie in Pages/Index.cshtml den Inhalt der Datei durch den folgenden Code:

@page
@model IndexModel
@{
    ViewData["Title"] = "Home page";
}

<div class="row mb-auto">
    <div class="col-md-4">
        <div class="row no-gutters border mb-4">
            <div class="col p-4 mb-4 ">
                <p class="card-text">
                    Contoso University is a sample application that
                    demonstrates how to use Entity Framework Core in an
                    ASP.NET Core Razor Pages web app.
                </p>
            </div>
        </div>
    </div>
    <div class="col-md-4">
        <div class="row no-gutters border mb-4">
            <div class="col p-4 d-flex flex-column position-static">
                <p class="card-text mb-auto">
                    You can build the application by following the steps in a series of tutorials.
                </p>
                <p>
@*                    <a href="https://docs.microsoft.com/aspnet/core/data/ef-rp/intro" class="stretched-link">See the tutorial</a>
*@                </p>
            </div>
        </div>
    </div>
    <div class="col-md-4">
        <div class="row no-gutters border mb-4">
            <div class="col p-4 d-flex flex-column">
                <p class="card-text mb-auto">
                    You can download the completed project from GitHub.
                </p>
                <p>
@*                    <a href="https://github.com/dotnet/AspNetCore.Docs/tree/main/aspnetcore/data/ef-rp/intro/samples" class="stretched-link">See project source code</a>
*@                </p>
            </div>
        </div>
    </div>
</div>

Der obige Code ersetzt den Text über ASP.NET Core durch Text über diese App.

Führen Sie die App aus, um sicherzustellen, dass die Startseite angezeigt wird.

Das Datenmodell

In den folgenden Abschnitten wird ein Datenmodell erstellt:

Course-Enrollment-Student data model diagram

Ein Student kann für beliebig viele Kurse angemeldet sein, und für jeden Kurs kann eine beliebige Anzahl von Studenten angemeldet sein.

Die Entität „Student“

Student entity diagram

  • Erstellen Sie im Projektordner den Ordner Models.
  • Erstellen Sie Models/Student.cs mit dem folgenden Code:
    namespace ContosoUniversity.Models
    {
        public class Student
        {
            public int ID { get; set; }
            public string LastName { get; set; }
            public string FirstMidName { get; set; }
            public DateTime EnrollmentDate { get; set; }
    
            public ICollection<Enrollment> Enrollments { get; set; }
        }
    }
    

Die ID-Eigenschaft fungiert als Primärschlüsselspalte der Datenbanktabelle, die diesem Kurs entspricht. Standardmäßig interpretiert EF Core eine Eigenschaft mit dem Namen ID oder classnameID als Primärschlüssel. Der alternative, automatisch erkannte Name für den Primärschlüssel der Klasse Student lautet also StudentID. Weitere Informationen finden Sie unter EF Core – Schlüssel.

Die Enrollments-Eigenschaft ist eine Navigationseigenschaft. Navigationseigenschaften enthalten andere Entitäten, die dieser Entität zugehörig sind. In diesem Fall enthält die Enrollments-Eigenschaft einer Student-Entität alle Enrollment-Entitäten, die mit diesem Studenten in Zusammenhang stehen. Wenn es z.B. für eine „Student“-Zeile in der Datenbank zwei zugehörige „Enrollment“-Zeilen gibt, enthält die Navigationseigenschaft Enrollments zwei Enrollment-Entitäten.

In der-Datenbank ist eine Enrollment-Zeile mit einer Student-Zeile verknüpft, wenn die StudentID-Spalte den ID-Wert des Studenten enthält. Angenommen, eine Student-Zeile weist die ID=1 auf. Zugehörige Enrollment-Zeilen weisen die StudentID = 1 auf. StudentID ist ein Fremdschlüssel in der Enrollment-Tabelle.

Die Enrollments-Eigenschaft wird als ICollection<Enrollment> definiert, da es möglicherweise mehrere zugehörige Enrollment-Entitäten gibt. Andere Sammlungstypen können verwendet werden, z. B. List<Enrollment> oder HashSet<Enrollment>. Wenn ICollection<Enrollment> verwendet wird, erstellt EF Core standardmäßig eine HashSet<Enrollment>-Auflistung.

Die Entität „Enrollment“

Enrollment entity diagram

Erstellen Sie Models/Enrollment.cs mit dem folgenden Code:

using System.ComponentModel.DataAnnotations;

namespace ContosoUniversity.Models
{
    public enum Grade
    {
        A, B, C, D, F
    }

    public class Enrollment
    {
        public int EnrollmentID { get; set; }
        public int CourseID { get; set; }
        public int StudentID { get; set; }
        [DisplayFormat(NullDisplayText = "No grade")]
        public Grade? Grade { get; set; }

        public Course Course { get; set; }
        public Student Student { get; set; }
    }
}

Die EnrollmentID-Eigenschaft ist der Primärschlüssel. Diese Entität verwendet das classnameID-Muster anstelle nur der ID. Für ein Produktionsdatenmodell wählen viele Entwickler*innen ein Muster aus und verwenden es einheitlich. In diesem Tutorial werden beide Muster verwendet, um zu veranschaulichen, dass beide funktionieren. Wenn Sie ID ohne classname verwenden, ist es einfacher, einige Arten von Datenmodelländerungen zu implementieren.

Bei der Grade-Eigenschaft handelt es sich um eine enum. Das Fragezeichen nach der Grade-Typdeklaration gibt an, dass die Grade-Eigenschaft NULL-Werte zulässt. Eine Grade-Eigenschaft mit dem Wert NULL unterscheidet sich von einer Grade-Eigenschaft mit dem Wert 0 (null). Der Wert NULL bedeutet, dass keine Grade-Eigenschaft bekannt ist oder noch keine zugewiesen wurde.

Bei der StudentID-Eigenschaft handelt es sich um einen Fremdschlüssel, und Student ist die entsprechende Navigationseigenschaft. Die Enrollment-Entität wird einer Student-Entität zugewiesen. Das bedeutet, dass die Eigenschaft genau eine Student-Entität enthält.

Bei der CourseID-Eigenschaft handelt es sich um einen Fremdschlüssel, und Course ist die entsprechende Navigationseigenschaft. Die Enrollment-Entität wird einer Course-Entität zugeordnet.

EF Core interpretiert Eigenschaften als Fremdschlüssel, wenn diese den Namen <navigation property name><primary key property name> haben. Beispielsweise StudentID der Fremdschlüssel für die Student-Navigationseigenschaft, da ID der Primärschlüssel der Student-Entität ist. Fremdschlüsseleigenschaften können ebenfalls den Namen <primary key property name> haben. Beispielsweise CourseID, da CourseID der Primärschlüssel der Course-Entität ist.

Die Entität „Course“

Course entity diagram

Erstellen Sie Models/Course.cs mit dem folgenden Code:

using System.ComponentModel.DataAnnotations.Schema;

namespace ContosoUniversity.Models
{
    public class Course
    {
        [DatabaseGenerated(DatabaseGeneratedOption.None)]
        public int CourseID { get; set; }
        public string Title { get; set; }
        public int Credits { get; set; }

        public ICollection<Enrollment> Enrollments { get; set; }
    }
}

Die Enrollments-Eigenschaft ist eine Navigationseigenschaft. Course-Entitäten können sich auf jede beliebige Anzahl von Enrollment-Entitäten beziehen.

Das DatabaseGenerated-Attribut lässt es zu, dass die App den Primärschlüssel angibt, sodass die Datenbank diesen nicht generieren muss.

Erstellen Sie die App. Der Compiler generiert mehrere Warnungen zur Behandlung von null-Werten. Weitere Informationen finden Sie in diesem GitHub-Issue, unter Nullwerte zulassende Verweistypen und im Tutorial: Besseres Ausdrücken Ihrer Entwurfsabsicht mit Verweistypen, die NULL-Werte zulassen und nicht zulassen.

Entfernen Sie die folgende Zeile aus der Datei ContosoUniversity.csproj, um die Warnungen von Nullwerte zulassenden Verweistypen zu entfernen:

<Nullable>enable</Nullable>

Die Gerüstbau-Engine unterstützt derzeit keine Nullwerte zulassenden Verweistypen. Daher werden diese auch von den im Gerüst verwendeten Modellen nicht unterstützt.

Entfernen Sie die Anmerkung ? zum Nullwerte zulassenden Verweistyp aus public string? RequestId { get; set; } in Pages/Error.cshtml.cs, damit dass Projekt ohne Compilerwarnungen erstellt wird.

Gerüstbau der Student-Seiten

In diesem Abschnitt wird das ASP.Net Core-Gerüstbautool verwendet, um Folgendes zu generieren:

  • Eine EF CoreDbContext-Klasse. „context“ ist die Hauptklasse, die die Entity Framework-Funktionen für ein angegebenes Datenmodell koordiniert. Diese Klasse wird von der Microsoft.EntityFrameworkCore.DbContext-Klasse abgeleitet.
  • Razor Pages-Instanzen, die CRUD-Vorgänge (Create, Read, Update, Delete) für die Student-Entität verarbeiten.
  • Erstellen Sie einen Ordner Pages/Students.
  • Klicken Sie im Projektmappen-Explorer mit der rechten Maustaste auf den Ordner Pages/Students, und wählen Sie Hinzufügen>Neues Gerüstelement aus.
  • Im Dialogfeld Add New Scaffold Item (Neues Gerüstelement hinzufügen):
    • Wählen Sie auf der linken Registerkarte Installiert > Allgemein >Razor Pages aus.
    • Wählen Sie Razor Pages mit Entity Framework (CRUD)>Hinzufügen aus.
  • Gehen Sie im Dialogfeld Razor Pages mit Entity Framework (CRUD) hinzufügen folgendermaßen vor:
    • Wählen Sie im Dropdownmenü ModellklasseStudent (ContosoUniversity.Models) aus.
    • Wählen Sie in der Zeile Datenkontextklasse das Pluszeichen (+) aus.
      • Ändern Sie den Datenkontextnamen so, dass er auf SchoolContext anstatt auf ContosoUniversityContext endet. Der aktualisierte Kontextname lautet ContosoUniversity.Data.SchoolContext.
      • Klicken Sie auf Hinzufügen, um das Hinzufügen der Datenkontextklasse abzuschließen.
      • Klicken Sie auf Hinzufügen, um das Dialogfeld Add Razor Pages (Razor Pages hinzufügen) zu beenden.

Die folgenden Pakete werden automatisch installiert:

  • Microsoft.EntityFrameworkCore.SqlServer
  • Microsoft.EntityFrameworkCore.Tools
  • Microsoft.VisualStudio.Web.CodeGeneration.Design

Wenn der vorherige Schritt zu einem Fehler führt, erstellen Sie das Projekt, und wiederholen Sie den Gerüstbauschritt.

Der Gerüstbauprozess:

  • Erstellt Razor Pages im Ordner Pages/Students:
    • Create.cshtml und Create.cshtml.cs
    • Delete.cshtml und Delete.cshtml.cs
    • Details.cshtml und Details.cshtml.cs
    • Edit.cshtml und Edit.cshtml.cs
    • Index.cshtml und Index.cshtml.cs
  • Erstellt Data/SchoolContext.cs.
  • Fügt der Abhängigkeitsinjektion in Program.cs den Kontext hinzu.
  • legt eine Datenbankverbindungszeichenfolge für appsettings.json fest

Datenbankverbindungszeichenfolge

Das Gerüstbautool generiert eine Verbindungszeichenfolge in der Datei appsettings.json .

Die Verbindungszeichenfolge gibt SQL Server LocalDB an:

{
  "Logging": {
    "LogLevel": {
      "Default": "Information",
      "Microsoft.AspNetCore": "Warning"
    }
  },
  "AllowedHosts": "*",
  "ConnectionStrings": {
    "SchoolContext": "Server=(localdb)\\mssqllocaldb;Database=SchoolContext-0e9;Trusted_Connection=True;MultipleActiveResultSets=true"
  }
}

LocalDB ist eine Basisversion der SQL Server Express-Datenbank-Engine, die zwar für die Anwendungsentwicklung, aber nicht für den Produktionseinsatz bestimmt ist. Standardmäßig erstellt LocalDB MDF-Dateien im Verzeichnis C:/Users/<user>.

Aktualisieren der Datenbankkontextklasse

Bei der Datenbankkontextklasse handelt es sich um die Hauptklasse, die die EF Core-Funktionen für ein angegebenes Datenmodell koordiniert. Der Kontext wird von Microsoft.EntityFrameworkCore.DbContext abgeleitet. Der Kontext gibt an, welche Entitäten im Datenmodell enthalten sind. In diesem Projekt heißt die Klasse SchoolContext.

Aktualisieren Sie Data/SchoolContext.cs mit folgendem Code:

using Microsoft.EntityFrameworkCore;
using ContosoUniversity.Models;

namespace ContosoUniversity.Data
{
    public class SchoolContext : DbContext
    {
        public SchoolContext (DbContextOptions<SchoolContext> options)
            : base(options)
        {
        }

        public DbSet<Student> Students { get; set; }
        public DbSet<Enrollment> Enrollments { get; set; }
        public DbSet<Course> Courses { get; set; }

        protected override void OnModelCreating(ModelBuilder modelBuilder)
        {
            modelBuilder.Entity<Course>().ToTable("Course");
            modelBuilder.Entity<Enrollment>().ToTable("Enrollment");
            modelBuilder.Entity<Student>().ToTable("Student");
        }
    }
}

Der obige Code ändert den Singular DbSet<Student> Student in den Plural DbSet<Student> Students. Nehmen Sie eine globale Änderung vor, damit der Code der Razor-Seite mit dem neuen Namen DBSet übereinstimmt: _context.Student. in: _context.Students.

Es gibt 8 Vorkommen.

Da eine Entitätenmenge mehrere Entitäten enthält, bevorzugen viele Entwickler den Plural für DBSet-Eigenschaftennamen.

Der hervorgehobene Code:

  • Erstellt für jede Entitätenmenge eine DbSet<TEntity>-Eigenschaft. EF Core-Terminologie:
    • Entitätenmengen entsprechen in der Regel einer Datenbanktabelle.
    • Entitäten entsprechen Zeilen in Tabellen.
  • Ruft OnModelCreating. OnModelCreating:
    • Wird aufgerufen, wenn SchoolContext initialisiert wurde, bevor jedoch das Modell gesperrt und zum Initialisieren des Kontexts verwendet wurde.
    • Ist erforderlich, da die Student-Entität später im Tutorial Verweise auf die anderen Entitäten enthält.

Wir gehen davon aus, dass dieses Problem in einem zukünftigen Release behoben wird.

Program.cs

ASP.NET Core wird mit Dependency Injection erstellt. Dienste (z. B. SchoolContext) werden per Abhängigkeitsinjektion während des App-Starts registriert. Komponenten, die diese Dienste erfordern (z. B. Razor-Seiten), werden diese Dienste über Konstruktorparameter bereitgestellt. Der Konstruktorcode, der eine Datenbankkontextinstanz abruft, wird später in diesem Tutorial erläutert.

Das Gerüstbautool hat die context-Klasse automatisch beim Dependency Injection-Container registriert.

Die folgenden hervorgehobenen Zeilen wurden beim Gerüstbau hinzugefügt:

using ContosoUniversity.Data;
using Microsoft.EntityFrameworkCore;
var builder = WebApplication.CreateBuilder(args);

builder.Services.AddRazorPages();

builder.Services.AddDbContext<SchoolContext>(options =>
  options.UseSqlServer(builder.Configuration.GetConnectionString("SchoolContext")));

Der Name der Verbindungszeichenfolge wird an den Kontext übergeben, indem Sie eine Methode auf einem DbContextOptions-Objekt aufrufen. Für die lokale Entwicklung liest das ASP.NET Core-Konfigurationssystem die Verbindungszeichenfolge aus der Datei appsettings.json oder appsettings.Development.json.

Hinzufügen des Filters für die Datenbankausnahme

Fügen Sie AddDatabaseDeveloperPageExceptionFilter und UseMigrationsEndPoint hinzu, wie im folgenden Code dargestellt:

using ContosoUniversity.Data;
using Microsoft.EntityFrameworkCore;
var builder = WebApplication.CreateBuilder(args);

builder.Services.AddRazorPages();

builder.Services.AddDbContext<SchoolContext>(options =>
  options.UseSqlServer(builder.Configuration.GetConnectionString("SchoolContext")));

builder.Services.AddDatabaseDeveloperPageExceptionFilter();

var app = builder.Build();

if (!app.Environment.IsDevelopment())
{
    app.UseExceptionHandler("/Error");
    app.UseHsts();
}
else
{
    app.UseDeveloperExceptionPage();
    app.UseMigrationsEndPoint();
}

Fügen Sie das NuGet-Paket Microsoft.AspNetCore.Diagnostics.EntityFrameworkCore hinzu.

Geben Sie in der Paket-Manager-Konsole Folgendes ein, um das NuGet-Paket hinzuzufügen:

Install-Package Microsoft.AspNetCore.Diagnostics.EntityFrameworkCore

Das NuGet-Paket Microsoft.AspNetCore.Diagnostics.EntityFrameworkCore stellt ASP.NET Core-Middleware für Entity Framework Core-Fehlerseiten bereit. Diese Middleware hilft bei der Erkennung und Diagnose von Fehlern bei Entity Framework Core-Migrationen.

AddDatabaseDeveloperPageExceptionFilter bietet hilfreiche Fehlerinformationen in der Entwicklungsumgebung für EF-Migrationsfehler.

Erstellen der Datenbank

Aktualisieren Sie Program.cs, um die Datenbank zu erstellen, wenn diese nicht vorhanden ist:

using ContosoUniversity.Data;
using Microsoft.EntityFrameworkCore;
var builder = WebApplication.CreateBuilder(args);

builder.Services.AddRazorPages();

builder.Services.AddDbContext<SchoolContext>(options =>
  options.UseSqlServer(builder.Configuration.GetConnectionString("SchoolContext")));

builder.Services.AddDatabaseDeveloperPageExceptionFilter();

var app = builder.Build();

if (!app.Environment.IsDevelopment())
{
    app.UseExceptionHandler("/Error");
    app.UseHsts();
}
else
{
    app.UseDeveloperExceptionPage();
    app.UseMigrationsEndPoint();
}

using (var scope = app.Services.CreateScope())
{
    var services = scope.ServiceProvider;

    var context = services.GetRequiredService<SchoolContext>();
    context.Database.EnsureCreated();
    // DbInitializer.Initialize(context);
}

app.UseHttpsRedirection();
app.UseStaticFiles();

app.UseRouting();

app.UseAuthorization();

app.MapRazorPages();

app.Run();

Die EnsureCreated-Methode führt keine Aktion aus, wenn eine Datenbank für den Kontext vorhanden ist. Wenn keine Datenbank vorhanden ist, werden die Datenbank und das Schema erstellt. EnsureCreated aktiviert den folgenden Workflow für die Verarbeitung von Datenmodelländerungen:

  • Löschen der Datenbank. Alle vorhandenen Daten gehen verloren.
  • Ändern des Datenmodells. Beispielsweise Hinzufügen eines EmailAddress-Felds.
  • Führen Sie die App aus.
  • EnsureCreated erstellt eine Datenbank mit dem neuen Schema.

Dieser Workflow funktioniert früh in der Entwicklungsphase gut, wenn sich das Schema rasch weiterentwickelt, solange keine Daten beibehalten werden müssen. Anders verhält es sich, wenn es darum geht, Daten, die in die Datenbank eingegeben wurden, beizubehalten. Wenn dies der Fall ist, verwenden Sie Migrationen.

Später in der Tutorialreihe wird die durch EnsureCreated erstellte Datenbank gelöscht, und es werden Migrationen verwendet. Eine Datenbank, die von EnsureCreated erstellt wird, kann nicht mithilfe von Migrationen aktualisiert werden.

Testen der App

  • Führen Sie die App aus.
  • Klicken Sie auf den Link Students (Studenten) und anschließend auf Neu erstellen.
  • Testen Sie die Links „Edit“ (Bearbeiten), „Details“ und „Delete“ (Löschen).

Ausführen eines Seedings für die Datenbank

Die EnsureCreated-Methode erstellt eine leere Datenbank. In diesem Abschnitt wird Code hinzugefügt, der die Datenbank mit Testdaten auffüllt.

Erstellen Sie Data/DbInitializer.cs mit dem folgenden Code:

using ContosoUniversity.Models;

namespace ContosoUniversity.Data
{
    public static class DbInitializer
    {
        public static void Initialize(SchoolContext context)
        {
            // Look for any students.
            if (context.Students.Any())
            {
                return;   // DB has been seeded
            }

            var students = new Student[]
            {
                new Student{FirstMidName="Carson",LastName="Alexander",EnrollmentDate=DateTime.Parse("2019-09-01")},
                new Student{FirstMidName="Meredith",LastName="Alonso",EnrollmentDate=DateTime.Parse("2017-09-01")},
                new Student{FirstMidName="Arturo",LastName="Anand",EnrollmentDate=DateTime.Parse("2018-09-01")},
                new Student{FirstMidName="Gytis",LastName="Barzdukas",EnrollmentDate=DateTime.Parse("2017-09-01")},
                new Student{FirstMidName="Yan",LastName="Li",EnrollmentDate=DateTime.Parse("2017-09-01")},
                new Student{FirstMidName="Peggy",LastName="Justice",EnrollmentDate=DateTime.Parse("2016-09-01")},
                new Student{FirstMidName="Laura",LastName="Norman",EnrollmentDate=DateTime.Parse("2018-09-01")},
                new Student{FirstMidName="Nino",LastName="Olivetto",EnrollmentDate=DateTime.Parse("2019-09-01")}
            };

            context.Students.AddRange(students);
            context.SaveChanges();

            var courses = new Course[]
            {
                new Course{CourseID=1050,Title="Chemistry",Credits=3},
                new Course{CourseID=4022,Title="Microeconomics",Credits=3},
                new Course{CourseID=4041,Title="Macroeconomics",Credits=3},
                new Course{CourseID=1045,Title="Calculus",Credits=4},
                new Course{CourseID=3141,Title="Trigonometry",Credits=4},
                new Course{CourseID=2021,Title="Composition",Credits=3},
                new Course{CourseID=2042,Title="Literature",Credits=4}
            };

            context.Courses.AddRange(courses);
            context.SaveChanges();

            var enrollments = new Enrollment[]
            {
                new Enrollment{StudentID=1,CourseID=1050,Grade=Grade.A},
                new Enrollment{StudentID=1,CourseID=4022,Grade=Grade.C},
                new Enrollment{StudentID=1,CourseID=4041,Grade=Grade.B},
                new Enrollment{StudentID=2,CourseID=1045,Grade=Grade.B},
                new Enrollment{StudentID=2,CourseID=3141,Grade=Grade.F},
                new Enrollment{StudentID=2,CourseID=2021,Grade=Grade.F},
                new Enrollment{StudentID=3,CourseID=1050},
                new Enrollment{StudentID=4,CourseID=1050},
                new Enrollment{StudentID=4,CourseID=4022,Grade=Grade.F},
                new Enrollment{StudentID=5,CourseID=4041,Grade=Grade.C},
                new Enrollment{StudentID=6,CourseID=1045},
                new Enrollment{StudentID=7,CourseID=3141,Grade=Grade.A},
            };

            context.Enrollments.AddRange(enrollments);
            context.SaveChanges();
        }
    }
}

Der Code überprüft, ob Studenten in der Datenbank vorhanden sind. Wenn keine Studenten vorhanden sind, werden der Datenbank Testdaten hinzugefügt. Testdaten werden in Arrays anstelle von List<T>-Sammlungen erstellt, um die Leistung zu optimieren.

  • In Program.cs entfernen Sie // aus der Zeile DbInitializer.Initialize:
using (var scope = app.Services.CreateScope())
{
    var services = scope.ServiceProvider;

    var context = services.GetRequiredService<SchoolContext>();
    context.Database.EnsureCreated();
    DbInitializer.Initialize(context);
}
  • Beenden Sie die App, falls sie gerade ausgeführt wird, und führen Sie den folgenden Befehl in der Paket-Manager-Konsole (Package Manager Console, PMC) aus:

    Drop-Database -Confirm
    
    
  • Antworten Sie mit Y, um die Datenbank zu löschen.

  • Starten Sie die App neu.
  • Wählen Sie die Seite „Students“ aus, um die Daten anzuzeigen, mit denen das Seeding ausgeführt wurde.

Zeigen Sie die Datenbank an

  • Öffnen Sie über das Menü Ansicht im Visual Studio SQL Server-Objekt-Explorer (SSOX).
  • Wählen Sie in SSOX (localdb)\MSSQLLocalDB > Databases > SchoolContext-{GUID} aus. Der Datenbankname wird anhand des Kontextnamens, den Sie zuvor angegeben haben, plus Bindestrich und GUID generiert.
  • Erweitern Sie den Knoten Tabellen.
  • Klicken Sie mit der rechten Maustaste auf die Tabelle Student, und klicken Sie auf Daten anzeigen, um die erstellten Spalten und die in die Tabelle eingefügten Zeilen aufzurufen.
  • Klicken Sie mit der rechten Maustaste auf die Tabelle Student, und klicken Sie auf Code anzeigen, um die Zuordnung des Student-Modells zum Student-Tabellenschema anzuzeigen.

Asynchrone EF-Methoden in ASP.NET Core Web-Apps

Die asynchrone Programmierung ist der Standardmodus für ASP.NET Core und EF Core.

Der Webserver verfügt nur über eine begrenzte Anzahl von Threads. Daher werden bei hoher Auslastung möglicherweise alle verfügbaren Threads gleichzeitig verwendet. Wenn dies der Fall ist, kann der Server keine neuen Anforderungen verarbeiten, bis die Threads wieder freigegeben werden. Wenn synchroner Code verwendet wird, kann es sein, dass zwar viele Threads belegt sind, diese aber keine Vorgänge ausführen, da sie auf den Abschluss der E/A-Vorgänge warten. Wenn asynchroner Code verwendet wird, werden Threads für den Server freigegeben, wenn diese nur auf den Abschluss der E/A-Vorgänge warten, damit andere Anforderungen verarbeitet werden können. Das bedeutet, dass es durch asynchronen Code ermöglicht wird, Serverressourcen effizienter zu nutzen, und der Server kann ohne Verzögerungen eine größere Menge von Datenverkehr verarbeiten.

Zur Laufzeit bedeutet die Verwendung von asynchronem Code allerdings, dass ein wenig mehr Aufwand entsteht. Für Situationen mit wenig Datenverkehr haben diese Leistungseinbußen keine negativen Folgen. Wenn es jedoch eine große Menge an Datenverkehr gibt, ist eine potentielle Verbesserung der Leistung von Bedeutung.

Im folgenden Code führen das async-Schlüsselwort, der Task-Rückgabewert, das await-Schlüsselwort und die ToListAsync-Methode dazu, dass der Code asynchron ausgeführt wird.

public async Task OnGetAsync()
{
    Students = await _context.Students.ToListAsync();
}
  • Das async-Schlüsselwort gibt dem Compiler folgende Anweisungen:
    • Er soll Rückrufe für Teile des Methodentexts generieren.
    • Er soll das Task-Objekt erstellen, das zurückgegeben wird.
  • Der Rückgabetyp Task stellt die derzeit ausgeführten Aufgaben dar.
  • Das await-Schlüsselwort hat zur Folge, dass der Compiler die Methode in zwei Teile unterteilt. Der erste Teil endet mit dem Vorgang, der auf asynchrone Weise gestartet wird. Der zweite Teil wird in eine Rückrufmethode übertragen, die aufgerufen wird, wenn der Vorgang abgeschlossen wird.
  • Bei ToListAsync handelt es sich um die asynchrone Version der ToList-Erweiterungsmethode.

Denken Sie an Folgendes, wenn Sie asynchronen Code schreiben, der EF Core verwendet:

  • Es werden nur Anweisungen auf asynchrone Weise ausgeführt, die Abfragen oder Befehle auslösen, die an die Datenbank gesendet werden sollen. Dazu gehören ToListAsync, SingleOrDefaultAsync, FirstOrDefaultAsync und SaveChangesAsync. Anweisungen wie var students = context.Students.Where(s => s.LastName == "Davolio"), die nur eine IQueryable-Instanz ändern, sind davon ausgeschlossen.
  • EF Core-Kontexte sind nicht threadsicher. Versuchen Sie daher nicht, mehrere Vorgänge gleichzeitig auszuführen.
  • Wenn Sie von den Leistungsvorteilen durch asynchronen Code profitieren möchten, überprüfen Sie, ob Bibliothekspakete (z.B. zum Paging) asynchronen Code verwenden, wenn sie EF Core-Methoden aufrufen, die Abfragen an die Datenbank senden.

Weitere Informationen zur asynchronen Programmierung in .NET finden Sie unter Async (Übersicht) und Asynchrone Programmierung mit Async und Await (C#).

Warnung

Die asynchrone Implementierung von Microsoft.Data.SqlClient weist einige bekannte Probleme auf (593, 601 und andere). Wenn unerwartete Leistungsprobleme auftreten, versuchen Sie stattdessen, die synchrone Befehlsausführung zu verwenden. Dies gilt insbesondere bei großen Text- oder Binärwerten.

Überlegungen zur Leistung

Im Allgemeinen sollte eine Webseite keine beliebige Anzahl von Zeilen laden. Eine Abfrage sollte Paging oder ein Verfahren zum Einschränken verwenden. Die vorherige Abfrage könnte z. B. Take verwenden, um die Anzahl zurückgegebener Zeilen einzuschränken:

public async Task OnGetAsync()
{
    Student = await _context.Students.Take(10).ToListAsync();
}

Durch das Auflisten einer großen Tabelle in einer Sicht kann eine teilweise konstruierte HTTP 200-Antwort zurückgegeben werden, wenn während des Durchlaufens eine Datenbankausnahme auftritt.

Das Paging wird später im Tutorial beschrieben.

Weitere Informationen finden Sie unter Überlegungen zur Leistung (EF).

Nächste Schritte

Verwenden von SQLite für die Entwicklung und von SQL Server für die Produktion

Dies ist das erste einer Reihe von Tutorials, die zeigen, wie Entity Framework (EF) Core für eine Razor Pages-App mit ASP.NET Core verwendet wird. In den Tutorials wird eine Website für eine fiktive Contoso University erstellt. Sie enthält Funktionen wie die Zulassung von Studenten, die Erstellung von Kursen und Aufgaben von Dozenten. In diesem Tutorial wird der Code-First-Ansatz verwendet. Informationen zum Durcharbeiten dieses Tutorials mit dem Database-First-Ansatz finden Sie in diesem GitHub-Issue.

Laden Sie die vollständige App herunter, oder zeigen Sie sie an.Anweisungen zum Herunterladen.

Voraussetzungen

  • Wenn Sie noch nicht mit Razor Pages vertraut sind, arbeiten Sie zuerst die Tutorialreihe Erste Schritte mit Razor Pages durch, bevor Sie mit diesem Tutorial beginnen.

Datenbank-Engines

Die Visual Studio-Anweisungen verwenden SQL Server LocalDB, eine Version von SQL Server Express, die nur unter Windows ausgeführt werden kann.

Problembehandlung

Wenn Sie auf ein Problem stoßen, das Sie nicht lösen können, vergleichen Sie Ihren den Code mit dem vollständigen Projekt. Eine gute Möglichkeit, Hilfe zu erhalten, besteht darin, eine Frage auf StackOverflow.com zu stellen und dabei das ASP.NET Core-Tag oder das EF Core-Tag zu verwenden.

Die Beispiel-App

Bei der App, die mithilfe dieser Tutorials erstellt werden soll, handelt es sich um eine einfache Website einer Universität. Benutzer können Informationen zu den Studenten, Kursen und Dozenten abrufen. Im Folgenden sind einige Anzeigen dargestellt, die mithilfe dieses Tutorials erstellt werden.

Students Index page

Students Edit page

Der Benutzeroberflächenstil dieser Website basiert auf den integrierten Projektvorlagen. Das Tutorial konzentriert sich auf die Verwendung von EF Core mit ASP.NET Core und nicht auf die Anpassung der Benutzeroberfläche.

Optional: Erstellen des Beispieldownloads

Dieser Schritt ist optional. Wenn Probleme vorliegen, die nicht behoben werden können, sollten Sie die vollständige App erstellen. Wenn Sie auf ein Problem stoßen, das Sie nicht lösen können, vergleichen Sie Ihren den Code mit dem vollständigen Projekt. Anweisungen zum Download.

Wählen Sie ContosoUniversity.csproj aus, um das Projekt zu öffnen.

  • Erstellen Sie das Projekt.
  • Führen Sie folgenden Befehl in der Paket-Manager-Konsole aus:
Update-Database

Führen Sie das Projekt aus, um das Seeding der Datenbank auszuführen.

Erstellen des Web-App-Projekts

  1. Starten Sie Visual Studio, und wählen Sie Neues Projekt erstellen aus.
  2. Wählen Sie im Dialogfeld Neues Projekt erstellenASP.NET Core-Webanwendung>Weiter aus.
  3. Geben Sie ContosoUniversity im Dialogfeld Neues Projekt konfigurieren für Projektname ein. Es ist wichtig, genau diesen Namen unter Berücksichtigung der Groß-/Kleinschreibung zu verwenden, sodass beim Kopieren von Code jeder namespace übereinstimmt.
  4. Klicken Sie auf Erstellen.
  5. Wählen Sie im Dialogfeld Neue ASP.NET Core-Webanwendung erstellen Folgendes aus:
    1. In den Dropdownmenüs .NET Core und ASP.NET Core 5.0.
    2. ASP.NET Core-Web-App
    3. ErstellenNew ASP.NET Core Project dialog

Einrichten des Websitestils

Kopieren Sie den folgenden Code, und fügen Sie ihn in die Datei Pages/Shared/_Layout.cshtml ein:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>@ViewData["Title"] - Contoso University</title>
    <link rel="stylesheet" href="~/lib/bootstrap/dist/css/bootstrap.css" />
    <link rel="stylesheet" href="~/css/site.css" />
</head>
<body>
    <header>
        <nav class="navbar navbar-expand-sm navbar-toggleable-sm navbar-light bg-white border-bottom box-shadow mb-3">
            <div class="container">
                <a class="navbar-brand" asp-area="" asp-page="/Index">Contoso University</a>
                <button class="navbar-toggler" type="button" data-toggle="collapse" data-target=".navbar-collapse" aria-controls="navbarSupportedContent"
                        aria-expanded="false" aria-label="Toggle navigation">
                    <span class="navbar-toggler-icon"></span>
                </button>
                <div class="navbar-collapse collapse d-sm-inline-flex flex-sm-row-reverse">
                    <ul class="navbar-nav flex-grow-1">
                        <li class="nav-item">
                            <a class="nav-link text-dark" asp-area="" asp-page="/About">About</a>
                        </li>
                        <li class="nav-item">
                            <a class="nav-link text-dark" asp-area="" asp-page="/Students/Index">Students</a>
                        </li>
                        <li class="nav-item">
                            <a class="nav-link text-dark" asp-area="" asp-page="/Courses/Index">Courses</a>
                        </li>
                        <li class="nav-item">
                            <a class="nav-link text-dark" asp-area="" asp-page="/Instructors/Index">Instructors</a>
                        </li>
                        <li class="nav-item">
                            <a class="nav-link text-dark" asp-area="" asp-page="/Departments/Index">Departments</a>
                        </li>
                    </ul>
                </div>
            </div>
        </nav>
    </header>
    <div class="container">
        <main role="main" class="pb-3">
            @RenderBody()
        </main>
    </div>

    <footer class="border-top footer text-muted">
        <div class="container">
            &copy; 2021 - Contoso University - <a asp-area="" asp-page="/Privacy">Privacy</a>
        </div>
    </footer>

    <script src="~/lib/jquery/dist/jquery.js"></script>
    <script src="~/lib/bootstrap/dist/js/bootstrap.bundle.js"></script>
    <script src="~/js/site.js" asp-append-version="true"></script>

    @RenderSection("Scripts", required: false)
</body>
</html>

Mit der Layoutdatei werden die Kopfzeile, die Fußzeile und das Menü der Website festgelegt. Durch den vorangehenden Code werden folgende Änderungen vorgenommen:

  • Jedes Vorkommen von „ContosoUniversity“ wird in „Contoso University“ geändert. Diese Begriffskombination kommt dreimal vor.
  • Die Menüeinträge Home und Privacy werden gelöscht.
  • Es werden Einträge für About, Students, Courses, Instructors und Departmens hinzugefügt.

Ersetzen Sie in Pages/Index.cshtml den Inhalt der Datei durch den folgenden Code:

@page
@model IndexModel
@{
    ViewData["Title"] = "Home page";
}

<div class="row mb-auto">
    <div class="col-md-4">
        <div class="row no-gutters border mb-4">
            <div class="col p-4 mb-4 ">
                <p class="card-text">
                    Contoso University is a sample application that
                    demonstrates how to use Entity Framework Core in an
                    ASP.NET Core Razor Pages web app.
                </p>
            </div>
        </div>
    </div>
    <div class="col-md-4">
        <div class="row no-gutters border mb-4">
            <div class="col p-4 d-flex flex-column position-static">
                <p class="card-text mb-auto">
                    You can build the application by following the steps in a series of tutorials.
                </p>
                <p>
                    <a href="https://docs.microsoft.com/aspnet/core/data/ef-rp/intro" class="stretched-link">See the tutorial</a>
                </p>
            </div>
        </div>
    </div>
    <div class="col-md-4">
        <div class="row no-gutters border mb-4">
            <div class="col p-4 d-flex flex-column">
                <p class="card-text mb-auto">
                    You can download the completed project from GitHub.
                </p>
                <p>
                    <a href="https://github.com/dotnet/AspNetCore.Docs/tree/main/aspnetcore/data/ef-rp/intro/samples" class="stretched-link">See project source code</a>
                </p>
            </div>
        </div>
    </div>
</div>

Der obige Code ersetzt den Text über ASP.NET Core durch Text über diese App.

Führen Sie die App aus, um sicherzustellen, dass die Startseite angezeigt wird.

Das Datenmodell

In den folgenden Abschnitten wird ein Datenmodell erstellt:

Course-Enrollment-Student data model diagram

Ein Student kann für beliebig viele Kurse angemeldet sein, und für jeden Kurs kann eine beliebige Anzahl von Studenten angemeldet sein.

Die Entität „Student“

Student entity diagram

  • Erstellen Sie im Projektordner den Ordner Models.

  • Erstellen Sie Models/Student.cs mit dem folgenden Code:

    using System;
    using System.Collections.Generic;
    
    namespace ContosoUniversity.Models
    {
        public class Student
        {
            public int ID { get; set; }
            public string LastName { get; set; }
            public string FirstMidName { get; set; }
            public DateTime EnrollmentDate { get; set; }
    
            public ICollection<Enrollment> Enrollments { get; set; }
        }
    }
    

Die ID-Eigenschaft fungiert als Primärschlüsselspalte der Datenbanktabelle, die diesem Kurs entspricht. Standardmäßig interpretiert EF Core eine Eigenschaft mit dem Namen ID oder classnameID als Primärschlüssel. Der alternative, automatisch erkannte Name für den Primärschlüssel der Klasse Student lautet also StudentID. Weitere Informationen finden Sie unter EF Core – Schlüssel.

Die Enrollments-Eigenschaft ist eine Navigationseigenschaft. Navigationseigenschaften enthalten andere Entitäten, die dieser Entität zugehörig sind. In diesem Fall enthält die Enrollments-Eigenschaft einer Student-Entität alle Enrollment-Entitäten, die mit diesem Studenten in Zusammenhang stehen. Wenn es z.B. für eine „Student“-Zeile in der Datenbank zwei zugehörige „Enrollment“-Zeilen gibt, enthält die Navigationseigenschaft Enrollments zwei Enrollment-Entitäten.

In der-Datenbank ist eine Enrollment-Zeile mit einer Student-Zeile verknüpft, wenn die StudentID-Spalte den ID-Wert des Studenten enthält. Angenommen, eine Student-Zeile weist die ID=1 auf. Zugehörige Enrollment-Zeilen weisen die StudentID = 1 auf. StudentID ist ein Fremdschlüssel in der Enrollment-Tabelle.

Die Enrollments-Eigenschaft wird als ICollection<Enrollment> definiert, da es möglicherweise mehrere zugehörige Enrollment-Entitäten gibt. Andere Sammlungstypen können verwendet werden, z. B. List<Enrollment> oder HashSet<Enrollment>. Wenn ICollection<Enrollment> verwendet wird, erstellt EF Core standardmäßig eine HashSet<Enrollment>-Auflistung.

Die Entität „Enrollment“

Enrollment entity diagram

Erstellen Sie Models/Enrollment.cs mit dem folgenden Code:

using System.ComponentModel.DataAnnotations;

namespace ContosoUniversity.Models
{
    public enum Grade
    {
        A, B, C, D, F
    }

    public class Enrollment
    {
        public int EnrollmentID { get; set; }
        public int CourseID { get; set; }
        public int StudentID { get; set; }
        [DisplayFormat(NullDisplayText = "No grade")]
        public Grade? Grade { get; set; }

        public Course Course { get; set; }
        public Student Student { get; set; }
    }
}

Die EnrollmentID-Eigenschaft ist der Primärschlüssel. Diese Entität verwendet das classnameID-Muster anstelle nur der ID. Für ein Produktionsdatenmodell wählen viele Entwickler*innen ein Muster aus und verwenden es einheitlich. In diesem Tutorial werden beide Muster verwendet, um zu veranschaulichen, dass beide funktionieren. Wenn Sie ID ohne classname verwenden, ist es einfacher, einige Arten von Datenmodelländerungen zu implementieren.

Bei der Grade-Eigenschaft handelt es sich um eine enum. Das Fragezeichen nach der Grade-Typdeklaration gibt an, dass die Grade-Eigenschaft NULL-Werte zulässt. Eine Grade-Eigenschaft mit dem Wert NULL unterscheidet sich von einer Grade-Eigenschaft mit dem Wert 0 (null). Der Wert NULL bedeutet, dass keine Grade-Eigenschaft bekannt ist oder noch keine zugewiesen wurde.

Bei der StudentID-Eigenschaft handelt es sich um einen Fremdschlüssel, und Student ist die entsprechende Navigationseigenschaft. Die Enrollment-Entität wird einer Student-Entität zugewiesen. Das bedeutet, dass die Eigenschaft genau eine Student-Entität enthält.

Bei der CourseID-Eigenschaft handelt es sich um einen Fremdschlüssel, und Course ist die entsprechende Navigationseigenschaft. Die Enrollment-Entität wird einer Course-Entität zugeordnet.

EF Core interpretiert Eigenschaften als Fremdschlüssel, wenn diese den Namen <navigation property name><primary key property name> haben. Beispielsweise StudentID der Fremdschlüssel für die Student-Navigationseigenschaft, da ID der Primärschlüssel der Student-Entität ist. Fremdschlüsseleigenschaften können ebenfalls den Namen <primary key property name> haben. Beispielsweise CourseID, da CourseID der Primärschlüssel der Course-Entität ist.

Die Entität „Course“

Course entity diagram

Erstellen Sie Models/Course.cs mit dem folgenden Code:

using System.Collections.Generic;
using System.ComponentModel.DataAnnotations.Schema;

namespace ContosoUniversity.Models
{
    public class Course
    {
        [DatabaseGenerated(DatabaseGeneratedOption.None)]
        public int CourseID { get; set; }
        public string Title { get; set; }
        public int Credits { get; set; }

        public ICollection<Enrollment> Enrollments { get; set; }
    }
}

Die Enrollments-Eigenschaft ist eine Navigationseigenschaft. Course-Entitäten können sich auf jede beliebige Anzahl von Enrollment-Entitäten beziehen.

Das DatabaseGenerated-Attribut lässt es zu, dass die App den Primärschlüssel angibt, sodass die Datenbank diesen nicht generieren muss.

Erstellen Sie das Projekt, um sich zu vergewissern, dass keine Compilerfehler vorhanden sind.

Gerüstbau der Student-Seiten

In diesem Abschnitt wird das ASP.Net Core-Gerüstbautool verwendet, um Folgendes zu generieren:

  • Eine EF CoreDbContext-Klasse. „context“ ist die Hauptklasse, die die Entity Framework-Funktionen für ein angegebenes Datenmodell koordiniert. Diese Klasse wird von der Microsoft.EntityFrameworkCore.DbContext-Klasse abgeleitet.
  • Razor Pages-Instanzen, die CRUD-Vorgänge (Create, Read, Update, Delete) für die Student-Entität verarbeiten.
  • Erstellen Sie einen Ordner Pages/Students.
  • Klicken Sie im Projektmappen-Explorer mit der rechten Maustaste auf den Ordner Pages/Students, und wählen Sie Hinzufügen>Neues Gerüstelement aus.
  • Im Dialogfeld Add New Scaffold Item (Neues Gerüstelement hinzufügen):
    • Wählen Sie auf der linken Registerkarte Installiert > Allgemein >Razor Pages aus.
    • Wählen Sie Razor Pages mit Entity Framework (CRUD)>Hinzufügen aus.
  • Gehen Sie im Dialogfeld Razor Pages mit Entity Framework (CRUD) hinzufügen folgendermaßen vor:
    • Wählen Sie im Dropdownmenü ModellklasseStudent (ContosoUniversity.Models) aus.
    • Wählen Sie in der Zeile Datenkontextklasse das Pluszeichen (+) aus.
      • Ändern Sie den Datenkontextnamen so, dass er auf SchoolContext anstatt auf ContosoUniversityContext endet. Der aktualisierte Kontextname lautet ContosoUniversity.Data.SchoolContext.
      • Klicken Sie auf Hinzufügen, um das Hinzufügen der Datenkontextklasse abzuschließen.
      • Klicken Sie auf Hinzufügen, um das Dialogfeld Add Razor Pages (Razor Pages hinzufügen) zu beenden.

Wenn beim Gerüstbau der Fehler 'Install the package Microsoft.VisualStudio.Web.CodeGeneration.Design and try again.' auftritt, führen Sie das Gerüsttool erneut aus. Alternativ finden Sie Informationen in diesem GitHub-Issue.

Die folgenden Pakete werden automatisch installiert:

  • Microsoft.EntityFrameworkCore.SqlServer
  • Microsoft.EntityFrameworkCore.Tools
  • Microsoft.VisualStudio.Web.CodeGeneration.Design

Wenn der vorherige Schritt zu einem Fehler führt, erstellen Sie das Projekt, und wiederholen Sie den Gerüstbauschritt.

Der Gerüstbauprozess:

  • Erstellt Razor Pages im Ordner Pages/Students:
    • Create.cshtml und Create.cshtml.cs
    • Delete.cshtml und Delete.cshtml.cs
    • Details.cshtml und Details.cshtml.cs
    • Edit.cshtml und Edit.cshtml.cs
    • Index.cshtml und Index.cshtml.cs
  • Erstellt Data/SchoolContext.cs.
  • Fügt der Abhängigkeitsinjektion in Startup.cs den Kontext hinzu.
  • legt eine Datenbankverbindungszeichenfolge für appsettings.json fest

Datenbankverbindungszeichenfolge

Das Gerüstbautool generiert eine Verbindungszeichenfolge in der Datei appsettings.json .

Die Verbindungszeichenfolge gibt SQL Server LocalDB an:

{
  "Logging": {
    "LogLevel": {
      "Default": "Information",
      "Microsoft": "Warning",
      "Microsoft.Hosting.Lifetime": "Information"
    }
  },
  "AllowedHosts": "*",
  "ConnectionStrings": {
    "SchoolContext": "Server=(localdb)\\mssqllocaldb;Database=CU-1;Trusted_Connection=True;MultipleActiveResultSets=true"
  }
}

LocalDB ist eine Basisversion der SQL Server Express-Datenbank-Engine, die zwar für die Anwendungsentwicklung, aber nicht für den Produktionseinsatz bestimmt ist. Standardmäßig erstellt LocalDB MDF-Dateien im Verzeichnis C:/Users/<user>.

Aktualisieren der Datenbankkontextklasse

Bei der Datenbankkontextklasse handelt es sich um die Hauptklasse, die die EF Core-Funktionen für ein angegebenes Datenmodell koordiniert. Der Kontext wird von Microsoft.EntityFrameworkCore.DbContext abgeleitet. Der Kontext gibt an, welche Entitäten im Datenmodell enthalten sind. In diesem Projekt heißt die Klasse SchoolContext.

Aktualisieren Sie Data/SchoolContext.cs mit folgendem Code:

using Microsoft.EntityFrameworkCore;
using ContosoUniversity.Models;

namespace ContosoUniversity.Data
{
    public class SchoolContext : DbContext
    {
        public SchoolContext (DbContextOptions<SchoolContext> options)
            : base(options)
        {
        }

        public DbSet<Student> Students { get; set; }
        public DbSet<Enrollment> Enrollments { get; set; }
        public DbSet<Course> Courses { get; set; }

        protected override void OnModelCreating(ModelBuilder modelBuilder)
        {
            modelBuilder.Entity<Course>().ToTable("Course");
            modelBuilder.Entity<Enrollment>().ToTable("Enrollment");
            modelBuilder.Entity<Student>().ToTable("Student");
        }
    }
}

Der obige Code ändert den Singular DbSet<Student> Student in den Plural DbSet<Student> Students. Nehmen Sie eine globale Änderung vor, damit der Code der Razor-Seite mit dem neuen Namen DBSet übereinstimmt: _context.Student. in: _context.Students.

Es gibt 8 Vorkommen.

Da eine Entitätenmenge mehrere Entitäten enthält, bevorzugen viele Entwickler den Plural für DBSet-Eigenschaftennamen.

Der hervorgehobene Code:

  • Erstellt für jede Entitätenmenge eine DbSet<TEntity>-Eigenschaft. EF Core-Terminologie:
    • Entitätenmengen entsprechen in der Regel einer Datenbanktabelle.
    • Entitäten entsprechen Zeilen in Tabellen.
  • Ruft OnModelCreating. OnModelCreating:
    • Wird aufgerufen, wenn SchoolContext initialisiert wurde, bevor jedoch das Modell gesperrt und zum Initialisieren des Kontexts verwendet wurde.
    • Ist erforderlich, da die Student-Entität später im Tutorial Verweise auf die anderen Entitäten enthält.

Erstellen Sie das Projekt, um sich zu vergewissern, dass keine Compilerfehler vorhanden sind.

Startup.cs

ASP.NET Core wird mit Dependency Injection erstellt. Dienste (z. B. SchoolContext) werden per Abhängigkeitsinjektion während des App-Starts registriert. Komponenten, die diese Dienste erfordern (z. B. Razor-Seiten), werden diese Dienste über Konstruktorparameter bereitgestellt. Der Konstruktorcode, der eine Datenbankkontextinstanz abruft, wird später in diesem Tutorial erläutert.

Das Gerüstbautool hat die context-Klasse automatisch beim Dependency Injection-Container registriert.

Die folgenden hervorgehobenen Zeilen wurden beim Gerüstbau hinzugefügt:

public void ConfigureServices(IServiceCollection services)
{
    services.AddRazorPages();

    services.AddDbContext<SchoolContext>(options =>
            options.UseSqlServer(Configuration.GetConnectionString("SchoolContext")));
}

Der Name der Verbindungszeichenfolge wird an den Kontext übergeben, indem Sie eine Methode auf einem DbContextOptions-Objekt aufrufen. Für die lokale Entwicklung liest das ASP.NET Core-Konfigurationssystem die Verbindungszeichenfolge aus der Datei appsettings.json.

Hinzufügen des Filters für die Datenbankausnahme

Fügen Sie AddDatabaseDeveloperPageExceptionFilter und UseMigrationsEndPoint hinzu, wie im folgenden Code dargestellt:

public void ConfigureServices(IServiceCollection services)
{
    services.AddRazorPages();

    services.AddDbContext<SchoolContext>(options =>
       options.UseSqlServer(Configuration.GetConnectionString("SchoolContext")));

    services.AddDatabaseDeveloperPageExceptionFilter();
}

public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
    if (env.IsDevelopment())
    {
        app.UseDeveloperExceptionPage();
        app.UseMigrationsEndPoint();
    }
    else
    {
        app.UseExceptionHandler("/Error");
        app.UseHsts();
    }

    app.UseHttpsRedirection();
    app.UseStaticFiles();

    app.UseRouting();

    app.UseAuthorization();

    app.UseEndpoints(endpoints =>
    {
        endpoints.MapRazorPages();
    });
}

Fügen Sie das NuGet-Paket Microsoft.AspNetCore.Diagnostics.EntityFrameworkCore hinzu.

Geben Sie in der Paket-Manager-Konsole Folgendes ein, um das NuGet-Paket hinzuzufügen:

Install-Package Microsoft.AspNetCore.Diagnostics.EntityFrameworkCore

Das NuGet-Paket Microsoft.AspNetCore.Diagnostics.EntityFrameworkCore stellt ASP.NET Core-Middleware für Entity Framework Core-Fehlerseiten bereit. Diese Middleware hilft bei der Erkennung und Diagnose von Fehlern bei Entity Framework Core-Migrationen.

AddDatabaseDeveloperPageExceptionFilter bietet hilfreiche Fehlerinformationen in der Entwicklungsumgebung für EF-Migrationsfehler.

Erstellen der Datenbank

Aktualisieren Sie Program.cs, um die Datenbank zu erstellen, wenn diese nicht vorhanden ist:

using ContosoUniversity.Data;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using System;

namespace ContosoUniversity
{
    public class Program
    {
        public static void Main(string[] args)
        {
            var host = CreateHostBuilder(args).Build();

            CreateDbIfNotExists(host);

            host.Run();
        }

        private static void CreateDbIfNotExists(IHost host)
        {
            using (var scope = host.Services.CreateScope())
            {
                var services = scope.ServiceProvider;
                try
                {
                    var context = services.GetRequiredService<SchoolContext>();
                    context.Database.EnsureCreated();
                    // DbInitializer.Initialize(context);
                }
                catch (Exception ex)
                {
                    var logger = services.GetRequiredService<ILogger<Program>>();
                    logger.LogError(ex, "An error occurred creating the DB.");
                }
            }
        }

        public static IHostBuilder CreateHostBuilder(string[] args) =>
            Host.CreateDefaultBuilder(args)
                .ConfigureWebHostDefaults(webBuilder =>
                {
                    webBuilder.UseStartup<Startup>();
                });
    }
}

Die EnsureCreated-Methode führt keine Aktion aus, wenn eine Datenbank für den Kontext vorhanden ist. Wenn keine Datenbank vorhanden ist, werden die Datenbank und das Schema erstellt. EnsureCreated aktiviert den folgenden Workflow für die Verarbeitung von Datenmodelländerungen:

  • Löschen der Datenbank. Alle vorhandenen Daten gehen verloren.
  • Ändern des Datenmodells. Beispielsweise Hinzufügen eines EmailAddress-Felds.
  • Führen Sie die App aus.
  • EnsureCreated erstellt eine Datenbank mit dem neuen Schema.

Dieser Workflow funktioniert früh in der Entwicklungsphase gut, wenn sich das Schema rasch weiterentwickelt, solange keine Daten beibehalten werden müssen. Anders verhält es sich, wenn es darum geht, Daten, die in die Datenbank eingegeben wurden, beizubehalten. Wenn dies der Fall ist, verwenden Sie Migrationen.

Später in der Tutorialreihe wird die durch EnsureCreated erstellte Datenbank gelöscht, und es werden Migrationen verwendet. Eine Datenbank, die von EnsureCreated erstellt wird, kann nicht mithilfe von Migrationen aktualisiert werden.

Testen der App

  • Führen Sie die App aus.
  • Klicken Sie auf den Link Students (Studenten) und anschließend auf Neu erstellen.
  • Testen Sie die Links „Edit“ (Bearbeiten), „Details“ und „Delete“ (Löschen).

Ausführen eines Seedings für die Datenbank

Die EnsureCreated-Methode erstellt eine leere Datenbank. In diesem Abschnitt wird Code hinzugefügt, der die Datenbank mit Testdaten auffüllt.

Erstellen Sie Data/DbInitializer.cs mit dem folgenden Code:

using ContosoUniversity.Models;
using System;
using System.Linq;

namespace ContosoUniversity.Data
{
    public static class DbInitializer
    {
        public static void Initialize(SchoolContext context)
        {
            // Look for any students.
            if (context.Students.Any())
            {
                return;   // DB has been seeded
            }

            var students = new Student[]
            {
                new Student{FirstMidName="Carson",LastName="Alexander",EnrollmentDate=DateTime.Parse("2019-09-01")},
                new Student{FirstMidName="Meredith",LastName="Alonso",EnrollmentDate=DateTime.Parse("2017-09-01")},
                new Student{FirstMidName="Arturo",LastName="Anand",EnrollmentDate=DateTime.Parse("2018-09-01")},
                new Student{FirstMidName="Gytis",LastName="Barzdukas",EnrollmentDate=DateTime.Parse("2017-09-01")},
                new Student{FirstMidName="Yan",LastName="Li",EnrollmentDate=DateTime.Parse("2017-09-01")},
                new Student{FirstMidName="Peggy",LastName="Justice",EnrollmentDate=DateTime.Parse("2016-09-01")},
                new Student{FirstMidName="Laura",LastName="Norman",EnrollmentDate=DateTime.Parse("2018-09-01")},
                new Student{FirstMidName="Nino",LastName="Olivetto",EnrollmentDate=DateTime.Parse("2019-09-01")}
            };

            context.Students.AddRange(students);
            context.SaveChanges();

            var courses = new Course[]
            {
                new Course{CourseID=1050,Title="Chemistry",Credits=3},
                new Course{CourseID=4022,Title="Microeconomics",Credits=3},
                new Course{CourseID=4041,Title="Macroeconomics",Credits=3},
                new Course{CourseID=1045,Title="Calculus",Credits=4},
                new Course{CourseID=3141,Title="Trigonometry",Credits=4},
                new Course{CourseID=2021,Title="Composition",Credits=3},
                new Course{CourseID=2042,Title="Literature",Credits=4}
            };

            context.Courses.AddRange(courses);
            context.SaveChanges();

            var enrollments = new Enrollment[]
            {
                new Enrollment{StudentID=1,CourseID=1050,Grade=Grade.A},
                new Enrollment{StudentID=1,CourseID=4022,Grade=Grade.C},
                new Enrollment{StudentID=1,CourseID=4041,Grade=Grade.B},
                new Enrollment{StudentID=2,CourseID=1045,Grade=Grade.B},
                new Enrollment{StudentID=2,CourseID=3141,Grade=Grade.F},
                new Enrollment{StudentID=2,CourseID=2021,Grade=Grade.F},
                new Enrollment{StudentID=3,CourseID=1050},
                new Enrollment{StudentID=4,CourseID=1050},
                new Enrollment{StudentID=4,CourseID=4022,Grade=Grade.F},
                new Enrollment{StudentID=5,CourseID=4041,Grade=Grade.C},
                new Enrollment{StudentID=6,CourseID=1045},
                new Enrollment{StudentID=7,CourseID=3141,Grade=Grade.A},
            };

            context.Enrollments.AddRange(enrollments);
            context.SaveChanges();
        }
    }
}

Der Code überprüft, ob Studenten in der Datenbank vorhanden sind. Wenn keine Studenten vorhanden sind, werden der Datenbank Testdaten hinzugefügt. Testdaten werden in Arrays anstelle von List<T>-Sammlungen erstellt, um die Leistung zu optimieren.

  • In Program.cs entfernen Sie // aus der Zeile DbInitializer.Initialize:

      context.Database.EnsureCreated();
      DbInitializer.Initialize(context);
    
  • Beenden Sie die App, falls sie gerade ausgeführt wird, und führen Sie den folgenden Befehl in der Paket-Manager-Konsole (Package Manager Console, PMC) aus:

    Drop-Database -Confirm
    
    
  • Antworten Sie mit Y, um die Datenbank zu löschen.

  • Starten Sie die App neu.
  • Wählen Sie die Seite „Students“ aus, um die Daten anzuzeigen, mit denen das Seeding ausgeführt wurde.

Zeigen Sie die Datenbank an

  • Öffnen Sie über das Menü Ansicht im Visual Studio SQL Server-Objekt-Explorer (SSOX).
  • Wählen Sie in SSOX (localdb)\MSSQLLocalDB > Databases > SchoolContext-{GUID} aus. Der Datenbankname wird anhand des Kontextnamens, den Sie zuvor angegeben haben, plus Bindestrich und GUID generiert.
  • Erweitern Sie den Knoten Tabellen.
  • Klicken Sie mit der rechten Maustaste auf die Tabelle Student, und klicken Sie auf Daten anzeigen, um die erstellten Spalten und die in die Tabelle eingefügten Zeilen aufzurufen.
  • Klicken Sie mit der rechten Maustaste auf die Tabelle Student, und klicken Sie auf Code anzeigen, um die Zuordnung des Student-Modells zum Student-Tabellenschema anzuzeigen.

Asynchroner Code

Die asynchrone Programmierung ist der Standardmodus für ASP.NET Core und EF Core.

Der Webserver verfügt nur über eine begrenzte Anzahl von Threads. Daher werden bei hoher Auslastung möglicherweise alle verfügbaren Threads gleichzeitig verwendet. Wenn dies der Fall ist, kann der Server keine neuen Anforderungen verarbeiten, bis die Threads wieder freigegeben werden. Wenn synchroner Code verwendet wird, kann es sein, dass zwar viele Threads belegt sind, diese aber keine Vorgänge ausführen, da sie auf den Abschluss der E/A-Vorgänge warten. Wenn asynchroner Code verwendet wird, werden Threads für den Server freigegeben, wenn diese nur auf den Abschluss der E/A-Vorgänge warten, damit andere Anforderungen verarbeitet werden können. Das bedeutet, dass es durch asynchronen Code ermöglicht wird, Serverressourcen effizienter zu nutzen, und der Server kann ohne Verzögerungen eine größere Menge von Datenverkehr verarbeiten.

Zur Laufzeit bedeutet die Verwendung von asynchronem Code allerdings, dass ein wenig mehr Aufwand entsteht. Für Situationen mit wenig Datenverkehr haben diese Leistungseinbußen keine negativen Folgen. Wenn es jedoch eine große Menge an Datenverkehr gibt, ist eine potentielle Verbesserung der Leistung von Bedeutung.

Im folgenden Code führen das async-Schlüsselwort, der Task-Rückgabewert, das await-Schlüsselwort und die ToListAsync-Methode dazu, dass der Code asynchron ausgeführt wird.

public async Task OnGetAsync()
{
    Students = await _context.Students.ToListAsync();
}
  • Das async-Schlüsselwort gibt dem Compiler folgende Anweisungen:
    • Er soll Rückrufe für Teile des Methodentexts generieren.
    • Er soll das Task-Objekt erstellen, das zurückgegeben wird.
  • Der Rückgabetyp Task stellt die derzeit ausgeführten Aufgaben dar.
  • Das await-Schlüsselwort hat zur Folge, dass der Compiler die Methode in zwei Teile unterteilt. Der erste Teil endet mit dem Vorgang, der auf asynchrone Weise gestartet wird. Der zweite Teil wird in eine Rückrufmethode übertragen, die aufgerufen wird, wenn der Vorgang abgeschlossen wird.
  • Bei ToListAsync handelt es sich um die asynchrone Version der ToList-Erweiterungsmethode.

Denken Sie an Folgendes, wenn Sie asynchronen Code schreiben, der EF Core verwendet:

  • Es werden nur Anweisungen auf asynchrone Weise ausgeführt, die Abfragen oder Befehle auslösen, die an die Datenbank gesendet werden sollen. Dazu gehören ToListAsync, SingleOrDefaultAsync, FirstOrDefaultAsync und SaveChangesAsync. Anweisungen wie var students = context.Students.Where(s => s.LastName == "Davolio"), die nur eine IQueryable-Instanz ändern, sind davon ausgeschlossen.
  • EF Core-Kontexte sind nicht threadsicher. Versuchen Sie daher nicht, mehrere Vorgänge gleichzeitig auszuführen.
  • Wenn Sie von den Leistungsvorteilen durch asynchronen Code profitieren möchten, überprüfen Sie, ob Bibliothekspakete (z.B. zum Paging) asynchronen Code verwenden, wenn sie EF Core-Methoden aufrufen, die Abfragen an die Datenbank senden.

Weitere Informationen zur asynchronen Programmierung in .NET finden Sie unter Async (Übersicht) und Asynchrone Programmierung mit Async und Await (C#).

Überlegungen zur Leistung

Im Allgemeinen sollte eine Webseite keine beliebige Anzahl von Zeilen laden. Eine Abfrage sollte Paging oder ein Verfahren zum Einschränken verwenden. Die vorherige Abfrage könnte z. B. Take verwenden, um die Anzahl zurückgegebener Zeilen einzuschränken:

public async Task OnGetAsync()
{
    Student = await _context.Students.Take(10).ToListAsync();
}

Durch das Auflisten einer großen Tabelle in einer Sicht kann eine teilweise konstruierte HTTP 200-Antwort zurückgegeben werden, wenn während des Durchlaufens eine Datenbankausnahme auftritt.

Der Standardwert von MaxModelBindingCollectionSize ist 1.024. Der folgende Code legt MaxModelBindingCollectionSize fest.

public void ConfigureServices(IServiceCollection services)
{
    var myMaxModelBindingCollectionSize = Convert.ToInt32(
                Configuration["MyMaxModelBindingCollectionSize"] ?? "100");

    services.Configure<MvcOptions>(options =>
           options.MaxModelBindingCollectionSize = myMaxModelBindingCollectionSize);

    services.AddRazorPages();

    services.AddDbContext<SchoolContext>(options =>
          options.UseSqlServer(Configuration.GetConnectionString("SchoolContext")));

    services.AddDatabaseDeveloperPageExceptionFilter();
}

Informationen zu Konfigurationseinstellungen wie MyMaxModelBindingCollectionSize finden Sie unter Konfiguration.

Das Paging wird später im Tutorial beschrieben.

Weitere Informationen finden Sie unter Überlegungen zur Leistung (EF).

SQL Protokollierung von Entity Framework Core

Die Konfiguration der Protokollierung wird meistens im Abschnitt Logging der appsettings.{Environment}.json-Dateien angegeben. Um SQL Anweisungen zu protokollieren, fügen Sie "Microsoft.EntityFrameworkCore.Database.Command": "Information" der Datei appsettings.Development.json hinzu:

{
  "ConnectionStrings": {
    "DefaultConnection": "Server=(localdb)\\mssqllocaldb;Database=MyDB-2;Trusted_Connection=True;MultipleActiveResultSets=true"
  },
  "Logging": {
    "LogLevel": {
      "Default": "Information",
      "Microsoft": "Warning",
      "Microsoft.Hosting.Lifetime": "Information"
     ,"Microsoft.EntityFrameworkCore.Database.Command": "Information"
    }
  },
  "AllowedHosts": "*"
}

Mit dem obigen JSON-Code werden SQL Anweisungen in der Befehlszeile und im Visual Studio Ausgabefenster angezeigt.

Weitere Informationen finden Sie im Artikel zum Protokollieren in .NET Core und ASP.NET Core und in diesem GitHub-Issue.

Nächste Schritte

Verwenden von SQLite für die Entwicklung und von SQL Server für die Produktion

Dies ist das erste einer Reihe von Tutorials, die zeigen, wie Entity Framework (EF) Core für eine Razor Pages-App mit ASP.NET Core verwendet wird. In den Tutorials wird eine Website für eine fiktive Contoso University erstellt. Sie enthält Funktionen wie die Zulassung von Studenten, die Erstellung von Kursen und Aufgaben von Dozenten. In diesem Tutorial wird der Code-First-Ansatz verwendet. Informationen zum Durcharbeiten dieses Tutorials mit dem Database-First-Ansatz finden Sie in diesem GitHub-Issue.

Laden Sie die vollständige App herunter, oder zeigen Sie sie an.Anweisungen zum Herunterladen.

Voraussetzungen

  • Wenn Sie noch nicht mit Razor Pages vertraut sind, arbeiten Sie zuerst die Tutorialreihe Erste Schritte mit Razor Pages durch, bevor Sie mit diesem Tutorial beginnen.

Datenbank-Engines

Die Visual Studio-Anweisungen verwenden SQL Server LocalDB, eine Version von SQL Server Express, die nur unter Windows ausgeführt werden kann.

In den Visual Studio Code-Anweisungen wird SQLite verwendet, eine plattformübergreifende Datenbank-Engine.

Wenn Sie SQLite verwenden möchten, laden Sie ein Drittanbietertool zum Verwalten und Anzeigen einer SQLite-Datenbank (z.B. DB Browser für SQLite) herunter, und installieren Sie es.

Problembehandlung

Wenn Sie auf ein Problem stoßen, das Sie nicht lösen können, vergleichen Sie Ihren den Code mit dem vollständigen Projekt. Eine gute Möglichkeit, Hilfe zu erhalten, besteht darin, eine Frage auf StackOverflow.com zu stellen und dabei das ASP.NET Core-Tag oder das EF Core-Tag zu verwenden.

Die Beispiel-App

Bei der App, die mithilfe dieser Tutorials erstellt werden soll, handelt es sich um eine einfache Website einer Universität. Benutzer können Informationen zu den Studenten, Kursen und Dozenten abrufen. Im Folgenden sind einige Anzeigen dargestellt, die mithilfe dieses Tutorials erstellt werden.

Students Index page

Students Edit page

Der Benutzeroberflächenstil dieser Website basiert auf den integrierten Projektvorlagen. Das Tutorial konzentriert sich auf die Verwendung von EF Core und nicht auf die Anpassung der Benutzeroberfläche.

Folgen Sie dem Link am oberen Rand der Seite, um den Quellcode für das vollständige Projekt abzurufen. Der Ordner cu30 enthält den Code für die ASP.NET Core 3.0-Version des Tutorials. Dateien, die den Status des Codes für die Tutorials 1 bis 7 widerspiegeln, finden Sie im Ordner cu30snapshots.

So führen Sie die APP nach dem Herunterladen des vollständigen Projekts aus:

  • Erstellen Sie das Projekt.

  • Führen Sie folgenden Befehl in der Paket-Manager-Konsole aus:

    Update-Database
    
  • Führen Sie das Projekt aus, um das Seeding der Datenbank auszuführen.

Erstellen des Web-App-Projekts

  • Klicken Sie in Visual Studio im Menü Datei auf Neu>Projekt.
  • Wählen Sie ASP.NET Core-Webanwendung aus.
  • Geben Sie dem Projekt den Namen ContosoUniversity. Es ist wichtig, genau diesen Namen unter Beachtung von Groß-/Kleinschreibung zu verwenden, sodass die Namespaces beim Kopieren und Einfügen von Code übereinstimmen.
  • Wählen Sie in den Dropdownlisten .NET Core und ASP.NET Core 3.0 und anschließend Webanwendung aus.

Einrichten des Websitestils

Richten Sie die Kopfzeile, die Fußzeile und das Menü der Website durch Aktualisieren von Pages/Shared/_Layout.cshtml ein:

  • Ändern Sie jedes „ContosoUniversity“ in „Contoso University“. Diese Begriffskombination kommt dreimal vor.

  • Löschen Sie die Menüeinträge Home und Privacy, und fügen Sie Einträge für About (Info), Students (Student*innen), Courses (Kurse), Instructors (Dozent*innen) und Departments (Fachbereiche) hinzu.

Die Änderungen werden hervorgehoben.

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>@ViewData["Title"] - Contoso University</title>
    <link rel="stylesheet" href="~/lib/bootstrap/dist/css/bootstrap.css" />
    <link rel="stylesheet" href="~/css/site.css" />
</head>
<body>
    <header>
        <nav class="navbar navbar-expand-sm navbar-toggleable-sm navbar-light bg-white border-bottom box-shadow mb-3">
            <div class="container">
                <a class="navbar-brand" asp-area="" asp-page="/Index">Contoso University</a>
                <button class="navbar-toggler" type="button" data-toggle="collapse" data-target=".navbar-collapse" aria-controls="navbarSupportedContent"
                        aria-expanded="false" aria-label="Toggle navigation">
                    <span class="navbar-toggler-icon"></span>
                </button>
                <div class="navbar-collapse collapse d-sm-inline-flex flex-sm-row-reverse">
                    <ul class="navbar-nav flex-grow-1">
                        <li class="nav-item">
                            <a class="nav-link text-dark" asp-area="" asp-page="/About">About</a>
                        </li>
                        <li class="nav-item">
                            <a class="nav-link text-dark" asp-area="" asp-page="/Students/Index">Students</a>
                        </li>
                        <li class="nav-item">
                            <a class="nav-link text-dark" asp-area="" asp-page="/Courses/Index">Courses</a>
                        </li>
                        <li class="nav-item">
                            <a class="nav-link text-dark" asp-area="" asp-page="/Instructors/Index">Instructors</a>
                        </li>
                        <li class="nav-item">
                            <a class="nav-link text-dark" asp-area="" asp-page="/Departments/Index">Departments</a>
                        </li>
                    </ul>
                </div>
            </div>
        </nav>
    </header>
    <div class="container">
        <main role="main" class="pb-3">
            @RenderBody()
        </main>
    </div>

    <footer class="border-top footer text-muted">
        <div class="container">
            &copy; 2019 - Contoso University - <a asp-area="" asp-page="/Privacy">Privacy</a>
        </div>
    </footer>

    <script src="~/lib/jquery/dist/jquery.js"></script>
    <script src="~/lib/bootstrap/dist/js/bootstrap.bundle.js"></script>
    <script src="~/js/site.js" asp-append-version="true"></script>

    @RenderSection("Scripts", required: false)
</body>
</html>

Ersetzen Sie in Pages/Index.cshtml die Inhalte der Datei durch den folgenden Code. Dadurch ersetzen Sie den Text zu ASP.NET Core durch Text zu dieser App:

@page
@model IndexModel
@{
    ViewData["Title"] = "Home page";
}

<div class="row mb-auto">
    <div class="col-md-4">
        <div class="row no-gutters border mb-4">
            <div class="col p-4 mb-4 ">
                <p class="card-text">
                    Contoso University is a sample application that
                    demonstrates how to use Entity Framework Core in an
                    ASP.NET Core Razor Pages web app.
                </p>
            </div>
        </div>
    </div>
    <div class="col-md-4">
        <div class="row no-gutters border mb-4">
            <div class="col p-4 d-flex flex-column position-static">
                <p class="card-text mb-auto">
                    You can build the application by following the steps in a series of tutorials.
                </p>
                <p>
                    <a href="https://docs.microsoft.com/aspnet/core/data/ef-rp/intro" class="stretched-link">See the tutorial</a>
                </p>
            </div>
        </div>
    </div>
    <div class="col-md-4">
        <div class="row no-gutters border mb-4">
            <div class="col p-4 d-flex flex-column">
                <p class="card-text mb-auto">
                    You can download the completed project from GitHub.
                </p>
                <p>
                    <a href="https://github.com/dotnet/AspNetCore.Docs/tree/main/aspnetcore/data/ef-rp/intro/samples" class="stretched-link">See project source code</a>
                </p>
            </div>
        </div>
    </div>
</div>

Führen Sie die App aus, um sicherzustellen, dass die Startseite angezeigt wird.

Das Datenmodell

In den folgenden Abschnitten wird ein Datenmodell erstellt:

Course-Enrollment-Student data model diagram

Ein Student kann für beliebig viele Kurse angemeldet sein, und für jeden Kurs kann eine beliebige Anzahl von Studenten angemeldet sein.

Die Entität „Student“

Student entity diagram

  • Erstellen Sie im Projektordner den Ordner Models.

  • Erstellen Sie Models/Student.cs mit dem folgenden Code:

    using System;
    using System.Collections.Generic;
    
    namespace ContosoUniversity.Models
    {
        public class Student
        {
            public int ID { get; set; }
            public string LastName { get; set; }
            public string FirstMidName { get; set; }
            public DateTime EnrollmentDate { get; set; }
    
            public ICollection<Enrollment> Enrollments { get; set; }
        }
    }
    

Die ID-Eigenschaft fungiert als Primärschlüsselspalte der Datenbanktabelle, die diesem Kurs entspricht. Standardmäßig interpretiert EF Core eine Eigenschaft mit dem Namen ID oder classnameID als Primärschlüssel. Der alternative, automatisch erkannte Name für den Primärschlüssel der Klasse Student lautet also StudentID. Weitere Informationen finden Sie unter EF Core – Schlüssel.

Die Enrollments-Eigenschaft ist eine Navigationseigenschaft. Navigationseigenschaften enthalten andere Entitäten, die dieser Entität zugehörig sind. In diesem Fall enthält die Enrollments-Eigenschaft einer Student-Entität alle Enrollment-Entitäten, die mit diesem Studenten in Zusammenhang stehen. Wenn es z.B. für eine „Student“-Zeile in der Datenbank zwei zugehörige „Enrollment“-Zeilen gibt, enthält die Navigationseigenschaft Enrollments zwei Enrollment-Entitäten.

In der-Datenbank ist eine Enrollment-Zeile mit einer Student-Zeile verknüpft, wenn die StudentID-Spalte den ID-Wert des Studenten enthält. Angenommen, eine Student-Zeile weist die ID=1 auf. Zugehörige Enrollment-Zeilen weisen die StudentID = 1 auf. StudentID ist ein Fremdschlüssel in der Enrollment-Tabelle.

Die Enrollments-Eigenschaft wird als ICollection<Enrollment> definiert, da es möglicherweise mehrere zugehörige Enrollment-Entitäten gibt. Sie können andere Sammlungstypen verwenden, z.B. List<Enrollment> oder HashSet<Enrollment>. Wenn ICollection<Enrollment> verwendet wird, erstellt EF Core standardmäßig eine HashSet<Enrollment>-Auflistung.

Die Entität „Enrollment“

Enrollment entity diagram

Erstellen Sie Models/Enrollment.cs mit dem folgenden Code:

namespace ContosoUniversity.Models
{
    public enum Grade
    {
        A, B, C, D, F
    }

    public class Enrollment
    {
        public int EnrollmentID { get; set; }
        public int CourseID { get; set; }
        public int StudentID { get; set; }
        public Grade? Grade { get; set; }

        public Course Course { get; set; }
        public Student Student { get; set; }
    }
}

Die EnrollmentID-Eigenschaft ist der Primärschlüssel. Diese Entität verwendet das classnameID-Muster anstelle nur der ID. Wählen Sie für ein Produktionsdatenmodell ein Muster aus, und verwenden Sie es einheitlich. In diesem Tutorial werden beide Muster verwendet, um zu veranschaulichen, dass beide funktionieren. Wenn Sie ID ohne classname verwenden, ist es einfacher, einige Arten von Datenmodelländerungen zu implementieren.

Bei der Grade-Eigenschaft handelt es sich um eine enum. Das Fragezeichen nach der Grade-Typdeklaration gibt an, dass die Grade-Eigenschaft NULL-Werte zulässt. Eine Grade-Eigenschaft mit dem Wert NULL unterscheidet sich von einer Grade-Eigenschaft mit dem Wert 0 (null). Der Wert NULL bedeutet, dass keine Grade-Eigenschaft bekannt ist oder noch keine zugewiesen wurde.

Bei der StudentID-Eigenschaft handelt es sich um einen Fremdschlüssel, und Student ist die entsprechende Navigationseigenschaft. Die Enrollment-Entität wird einer Student-Entität zugewiesen. Das bedeutet, dass die Eigenschaft genau eine Student-Entität enthält.

Bei der CourseID-Eigenschaft handelt es sich um einen Fremdschlüssel, und Course ist die entsprechende Navigationseigenschaft. Die Enrollment-Entität wird einer Course-Entität zugeordnet.

EF Core interpretiert Eigenschaften als Fremdschlüssel, wenn diese den Namen <navigation property name><primary key property name> haben. Beispielsweise StudentID der Fremdschlüssel für die Student-Navigationseigenschaft, da ID der Primärschlüssel der Student-Entität ist. Fremdschlüsseleigenschaften können ebenfalls den Namen <primary key property name> haben. Beispielsweise CourseID, da CourseID der Primärschlüssel der Course-Entität ist.

Die Entität „Course“

Course entity diagram

Erstellen Sie Models/Course.cs mit dem folgenden Code:

using System.Collections.Generic;
using System.ComponentModel.DataAnnotations.Schema;

namespace ContosoUniversity.Models
{
    public class Course
    {
        [DatabaseGenerated(DatabaseGeneratedOption.None)]
        public int CourseID { get; set; }
        public string Title { get; set; }
        public int Credits { get; set; }

        public ICollection<Enrollment> Enrollments { get; set; }
    }
}

Die Enrollments-Eigenschaft ist eine Navigationseigenschaft. Course-Entitäten können sich auf jede beliebige Anzahl von Enrollment-Entitäten beziehen.

Das DatabaseGenerated-Attribut lässt es zu, dass die App den Primärschlüssel angibt, sodass die Datenbank diesen nicht generieren muss.

Erstellen Sie das Projekt, um sich zu vergewissern, dass keine Compilerfehler vorhanden sind.

Gerüstbau der Student-Seiten

In diesem Abschnitt verwenden Sie das ASP.Net Core-Gerüstbautool, um Folgendes zu generieren:

  • Eine EF Corecontext-Klasse. „context“ ist die Hauptklasse, die die Entity Framework-Funktionen für ein angegebenes Datenmodell koordiniert. Diese Klasse wird von der Microsoft.EntityFrameworkCore.DbContext-Klasse abgeleitet.
  • Razor Pages-Instanzen, die CRUD-Vorgänge (Create, Read, Update, Delete) für die Student-Entität verarbeiten.
  • Erstellen Sie einen Ordner Students im Ordner Pages.
  • Klicken Sie im Projektmappen-Explorer mit der rechten Maustaste auf den Ordner Pages/Students, und wählen Sie Hinzufügen>Neues Gerüstelement aus.
  • Wählen Sie im Dialogfeld Gerüst hinzufügen den Eintrag Razor Pages mit Entity Framework (CRUD)>Hinzufügen aus.
  • Gehen Sie im Dialogfeld Razor Pages mit Entity Framework (CRUD) hinzufügen folgendermaßen vor:
    • Wählen Sie im Dropdownmenü ModellklasseStudent (ContosoUniversity.Models) aus.
    • Wählen Sie in der Zeile Datenkontextklasse das Pluszeichen (+) aus.
    • Ändern Sie den Datenkontextnamen aus ContosoUniversity.Models.ContosoUniversityContext in ContosoUniversity.Data.SchoolContext.
    • Wählen Sie Hinzufügen aus.

Die folgenden Pakete werden automatisch installiert:

  • Microsoft.VisualStudio.Web.CodeGeneration.Design
  • Microsoft.EntityFrameworkCore.SqlServer
  • Microsoft.Extensions.Logging.Debug
  • Microsoft.EntityFrameworkCore.Tools

Wenn Sie ein Problem mit dem vorherigen Schritt haben, erstellen Sie das Projekt, und wiederholen Sie den Gerüstbauschritt.

Der Gerüstbauprozess:

  • Erstellt Razor Pages im Ordner Pages/Students:
    • Create.cshtml und Create.cshtml.cs
    • Delete.cshtml und Delete.cshtml.cs
    • Details.cshtml und Details.cshtml.cs
    • Edit.cshtml und Edit.cshtml.cs
    • Index.cshtml und Index.cshtml.cs
  • Erstellt Data/SchoolContext.cs.
  • Fügt der Abhängigkeitsinjektion in Startup.cs den Kontext hinzu.
  • legt eine Datenbankverbindungszeichenfolge für appsettings.json fest

Datenbankverbindungszeichenfolge

In der Datei appsettings.json wird die Verbindungszeichenfolge SQL Server LocalDB angegeben.

{
  "Logging": {
    "LogLevel": {
      "Default": "Information",
      "Microsoft": "Warning",
      "Microsoft.Hosting.Lifetime": "Information"
    }
  },
  "AllowedHosts": "*",
  "ConnectionStrings": {
    "SchoolContext": "Server=(localdb)\\mssqllocaldb;Database=SchoolContext6;Trusted_Connection=True;MultipleActiveResultSets=true"
  }
}

LocalDB ist eine Basisversion der SQL Server Express-Datenbank-Engine, die zwar für die Anwendungsentwicklung, aber nicht für den Produktionseinsatz bestimmt ist. Standardmäßig erstellt LocalDB MDF-Dateien im Verzeichnis C:/Users/<user>.

Aktualisieren der Datenbankkontextklasse

Bei der Datenbankkontextklasse handelt es sich um die Hauptklasse, die die EF Core-Funktionen für ein angegebenes Datenmodell koordiniert. Der Kontext wird von Microsoft.EntityFrameworkCore.DbContext abgeleitet. Der Kontext gibt an, welche Entitäten im Datenmodell enthalten sind. In diesem Projekt heißt die Klasse SchoolContext.

Aktualisieren Sie Data/SchoolContext.cs mit folgendem Code:

using Microsoft.EntityFrameworkCore;
using ContosoUniversity.Models;

namespace ContosoUniversity.Data
{
    public class SchoolContext : DbContext
    {
        public SchoolContext (DbContextOptions<SchoolContext> options)
            : base(options)
        {
        }

        public DbSet<Student> Students { get; set; }
        public DbSet<Enrollment> Enrollments { get; set; }
        public DbSet<Course> Courses { get; set; }

        protected override void OnModelCreating(ModelBuilder modelBuilder)
        {
            modelBuilder.Entity<Course>().ToTable("Course");
            modelBuilder.Entity<Enrollment>().ToTable("Enrollment");
            modelBuilder.Entity<Student>().ToTable("Student");
        }
    }
}

Der hervorgehobene Code erstellt für jede Entitätenmenge eine DbSet<TEntity>-Eigenschaft. EF Core-Terminologie:

  • Entitätenmengen entsprechen in der Regel einer Datenbanktabelle.
  • Entitäten entsprechen Zeilen in Tabellen.

Da eine Entitätenmenge mehrere Entitäten enthält, sollten die DBSet-Eigenschaften Namen im Plural tragen. Da das Gerüstbautool ein Student-DBSet erstellt hat, ändert dieser Schritt den Namen in den Plural Students.

Damit der Razor Pages-Code mit dem neuen DBSet-Namen übereinstimmt, ändern Sie _context.Student im gesamten Projekt global in _context.Students. Es gibt 8 Vorkommen.

Erstellen Sie das Projekt, um sich zu vergewissern, dass keine Compilerfehler vorhanden sind.

Startup.cs

ASP.NET Core wird mit Dependency Injection erstellt. Dienste (wie der EF Core-Datenbankkontext) werden während des Anwendungsstarts bei der Abhängigkeitsinjektion registriert. Komponenten, die diese Dienste erfordern (z. B. Razor Pages), werden von diesen Diensten über Konstruktorparameter bereitgestellt. Der Konstruktorcode, der eine Datenbankkontextinstanz abruft, wird später in diesem Tutorial erläutert.

Das Gerüstbautool hat die context-Klasse automatisch beim Dependency Injection-Container registriert.

  • In ConfigureServices wurden die hervorgehobenen Zeilen vom Gerüstbau hinzugefügt:

    public void ConfigureServices(IServiceCollection services)
    {
        services.AddRazorPages();
    
        services.AddDbContext<SchoolContext>(options =>
                options.UseSqlServer(Configuration.GetConnectionString("SchoolContext")));
    }
    

Der Name der Verbindungszeichenfolge wird an den Kontext übergeben, indem Sie eine Methode auf einem DbContextOptions-Objekt aufrufen. Für die lokale Entwicklung liest das ASP.NET Core-Konfigurationssystem die Verbindungszeichenfolge aus der Datei appsettings.json.

Erstellen der Datenbank

Aktualisieren Sie Program.cs, um die Datenbank zu erstellen, wenn diese nicht vorhanden ist:

using ContosoUniversity.Data;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using System;

namespace ContosoUniversity
{
    public class Program
    {
        public static void Main(string[] args)
        {
            var host = CreateHostBuilder(args).Build();

            CreateDbIfNotExists(host);

            host.Run();
        }

        private static void CreateDbIfNotExists(IHost host)
        {
            using (var scope = host.Services.CreateScope())
            {
                var services = scope.ServiceProvider;
                try
                {
                    var context = services.GetRequiredService<SchoolContext>();
                    context.Database.EnsureCreated();
                    // DbInitializer.Initialize(context);
                }
                catch (Exception ex)
                {
                    var logger = services.GetRequiredService<ILogger<Program>>();
                    logger.LogError(ex, "An error occurred creating the DB.");
                }
            }
        }

        public static IHostBuilder CreateHostBuilder(string[] args) =>
            Host.CreateDefaultBuilder(args)
                .ConfigureWebHostDefaults(webBuilder =>
                {
                    webBuilder.UseStartup<Startup>();
                });
    }
}

Die EnsureCreated-Methode führt keine Aktion aus, wenn eine Datenbank für den Kontext vorhanden ist. Wenn keine Datenbank vorhanden ist, werden die Datenbank und das Schema erstellt. EnsureCreated aktiviert den folgenden Workflow für die Verarbeitung von Datenmodelländerungen:

  • Löschen der Datenbank. Alle vorhandenen Daten gehen verloren.
  • Ändern des Datenmodells. Beispielsweise Hinzufügen eines EmailAddress-Felds.
  • Führen Sie die App aus.
  • EnsureCreated erstellt eine Datenbank mit dem neuen Schema.

Dieser Workflow funktioniert früh in der Entwicklungsphase gut, wenn sich das Schema rasch weiterentwickelt, solange Sie keine Daten beibehalten müssen. Anders verhält es sich, wenn es darum geht, Daten, die in die Datenbank eingegeben wurden, beizubehalten. Wenn dies der Fall ist, verwenden Sie Migrationen.

Später in der Tutorialserie löschen Sie die Datenbank, die von EnsureCreated erstellt wurde, und verwenden stattdessen Migrationen. Eine Datenbank, die von EnsureCreated erstellt wird, kann nicht mithilfe von Migrationen aktualisiert werden.

Testen der App

  • Führen Sie die App aus.
  • Klicken Sie auf den Link Students (Studenten) und anschließend auf Neu erstellen.
  • Testen Sie die Links „Edit“ (Bearbeiten), „Details“ und „Delete“ (Löschen).

Ausführen eines Seedings für die Datenbank

Die EnsureCreated-Methode erstellt eine leere Datenbank. In diesem Abschnitt wird Code hinzugefügt, der die Datenbank mit Testdaten auffüllt.

Erstellen Sie Data/DbInitializer.cs mit dem folgenden Code:

using ContosoUniversity.Data;
using ContosoUniversity.Models;
using System;
using System.Linq;

namespace ContosoUniversity.Data
{
    public static class DbInitializer
    {
        public static void Initialize(SchoolContext context)
        {
            context.Database.EnsureCreated();

            // Look for any students.
            if (context.Students.Any())
            {
                return;   // DB has been seeded
            }

            var students = new Student[]
            {
                new Student{FirstMidName="Carson",LastName="Alexander",EnrollmentDate=DateTime.Parse("2019-09-01")},
                new Student{FirstMidName="Meredith",LastName="Alonso",EnrollmentDate=DateTime.Parse("2017-09-01")},
                new Student{FirstMidName="Arturo",LastName="Anand",EnrollmentDate=DateTime.Parse("2018-09-01")},
                new Student{FirstMidName="Gytis",LastName="Barzdukas",EnrollmentDate=DateTime.Parse("2017-09-01")},
                new Student{FirstMidName="Yan",LastName="Li",EnrollmentDate=DateTime.Parse("2017-09-01")},
                new Student{FirstMidName="Peggy",LastName="Justice",EnrollmentDate=DateTime.Parse("2016-09-01")},
                new Student{FirstMidName="Laura",LastName="Norman",EnrollmentDate=DateTime.Parse("2018-09-01")},
                new Student{FirstMidName="Nino",LastName="Olivetto",EnrollmentDate=DateTime.Parse("2019-09-01")}
            };

            context.Students.AddRange(students);
            context.SaveChanges();

            var courses = new Course[]
            {
                new Course{CourseID=1050,Title="Chemistry",Credits=3},
                new Course{CourseID=4022,Title="Microeconomics",Credits=3},
                new Course{CourseID=4041,Title="Macroeconomics",Credits=3},
                new Course{CourseID=1045,Title="Calculus",Credits=4},
                new Course{CourseID=3141,Title="Trigonometry",Credits=4},
                new Course{CourseID=2021,Title="Composition",Credits=3},
                new Course{CourseID=2042,Title="Literature",Credits=4}
            };

            context.Courses.AddRange(courses);
            context.SaveChanges();

            var enrollments = new Enrollment[]
            {
                new Enrollment{StudentID=1,CourseID=1050,Grade=Grade.A},
                new Enrollment{StudentID=1,CourseID=4022,Grade=Grade.C},
                new Enrollment{StudentID=1,CourseID=4041,Grade=Grade.B},
                new Enrollment{StudentID=2,CourseID=1045,Grade=Grade.B},
                new Enrollment{StudentID=2,CourseID=3141,Grade=Grade.F},
                new Enrollment{StudentID=2,CourseID=2021,Grade=Grade.F},
                new Enrollment{StudentID=3,CourseID=1050},
                new Enrollment{StudentID=4,CourseID=1050},
                new Enrollment{StudentID=4,CourseID=4022,Grade=Grade.F},
                new Enrollment{StudentID=5,CourseID=4041,Grade=Grade.C},
                new Enrollment{StudentID=6,CourseID=1045},
                new Enrollment{StudentID=7,CourseID=3141,Grade=Grade.A},
            };

            context.Enrollments.AddRange(enrollments);
            context.SaveChanges();
        }
    }
}

Der Code überprüft, ob Studenten in der Datenbank vorhanden sind. Wenn keine Studenten vorhanden sind, werden der Datenbank Testdaten hinzugefügt. Testdaten werden in Arrays anstelle von List<T>-Sammlungen erstellt, um die Leistung zu optimieren.

  • Ersetzen Sie in Program.cs den EnsureCreated-Aufruf durch einen DbInitializer.Initialize-Aufruf:

    // context.Database.EnsureCreated();
    DbInitializer.Initialize(context);
    

Beenden Sie die App, falls sie gerade ausgeführt wird, und führen Sie den folgenden Befehl in der Paket-Manager-Konsole (Package Manager Console, PMC) aus:

Drop-Database
  • Starten Sie die App neu.

  • Wählen Sie die Seite „Students“ aus, um die Daten anzuzeigen, mit denen das Seeding ausgeführt wurde.

Zeigen Sie die Datenbank an

  • Öffnen Sie über das Menü Ansicht im Visual Studio SQL Server-Objekt-Explorer (SSOX).
  • Wählen Sie in SSOX (localdb)\MSSQLLocalDB > Databases > SchoolContext-{GUID} aus. Der Datenbankname wird anhand des Kontextnamens, den Sie zuvor angegeben haben, plus Bindestrich und GUID generiert.
  • Erweitern Sie den Knoten Tabellen.
  • Klicken Sie mit der rechten Maustaste auf die Tabelle Student, und klicken Sie auf Daten anzeigen, um die erstellten Spalten und die in die Tabelle eingefügten Zeilen aufzurufen.
  • Klicken Sie mit der rechten Maustaste auf die Tabelle Student, und klicken Sie auf Code anzeigen, um die Zuordnung des Student-Modells zum Student-Tabellenschema anzuzeigen.

Asynchroner Code

Die asynchrone Programmierung ist der Standardmodus für ASP.NET Core und EF Core.

Der Webserver verfügt nur über eine begrenzte Anzahl von Threads. Daher werden bei hoher Auslastung möglicherweise alle verfügbaren Threads gleichzeitig verwendet. Wenn dies der Fall ist, kann der Server keine neuen Anforderungen verarbeiten, bis die Threads wieder freigegeben werden. Wenn synchroner Code verwendet wird, kann es sein, dass zwar viele Threads belegt sind, diese aber keine Vorgänge ausführen, da sie auf den Abschluss der E/A-Vorgänge warten. Wenn asynchroner Code verwendet wird, werden Threads für den Server freigegeben, wenn diese nur auf den Abschluss der E/A-Vorgänge warten, damit andere Anforderungen verarbeitet werden können. Das bedeutet, dass es durch asynchronen Code ermöglicht wird, Serverressourcen effizienter zu nutzen, und der Server kann ohne Verzögerungen eine größere Menge von Datenverkehr verarbeiten.

Zur Laufzeit bedeutet die Verwendung von asynchronem Code allerdings, dass ein wenig mehr Aufwand entsteht. Für Situationen mit wenig Datenverkehr haben diese Leistungseinbußen keine negativen Folgen. Wenn es jedoch eine große Menge an Datenverkehr gibt, ist eine potentielle Verbesserung der Leistung von Bedeutung.

Im folgenden Code führen das async-Schlüsselwort, der Task<T>-Rückgabewert, das await-Schlüsselwort und die ToListAsync-Methode dazu, dass der Code asynchron ausgeführt wird.

public async Task OnGetAsync()
{
    Students = await _context.Students.ToListAsync();
}
  • Das async-Schlüsselwort gibt dem Compiler folgende Anweisungen:
    • Er soll Rückrufe für Teile des Methodentexts generieren.
    • Er soll das Task-Objekt erstellen, das zurückgegeben wird.
  • Der Rückgabetyp Task<T> stellt die derzeit ausgeführten Aufgaben dar.
  • Das await-Schlüsselwort hat zur Folge, dass der Compiler die Methode in zwei Teile unterteilt. Der erste Teil endet mit dem Vorgang, der auf asynchrone Weise gestartet wird. Der zweite Teil wird in eine Rückrufmethode übertragen, die aufgerufen wird, wenn der Vorgang abgeschlossen wird.
  • Bei ToListAsync handelt es sich um die asynchrone Version der ToList-Erweiterungsmethode.

Denken Sie an Folgendes, wenn Sie asynchronen Code schreiben, der EF Core verwendet:

  • Es werden nur Anweisungen auf asynchrone Weise ausgeführt, die Abfragen oder Befehle auslösen, die an die Datenbank gesendet werden sollen. Dazu gehören ToListAsync, SingleOrDefaultAsync, FirstOrDefaultAsync und SaveChangesAsync. Anweisungen wie var students = context.Students.Where(s => s.LastName == "Davolio"), die nur eine IQueryable-Instanz ändern, sind davon ausgeschlossen.
  • EF Core-Kontexte sind nicht threadsicher. Versuchen Sie daher nicht, mehrere Vorgänge gleichzeitig auszuführen.
  • Wenn Sie von den Leistungsvorteilen durch asynchronen Code profitieren möchten, überprüfen Sie, ob Bibliothekspakete (z.B. zum Paging) asynchronen Code verwenden, wenn sie EF Core-Methoden aufrufen, die Abfragen an die Datenbank senden.

Weitere Informationen zur asynchronen Programmierung in .NET finden Sie unter Async (Übersicht) und Asynchrone Programmierung mit Async und Await (C#).

Nächste Schritte