Teil 2: Razor Pages mit EF Core in ASP.NET Core – CRUDPart 2, Razor Pages with EF Core in ASP.NET Core - CRUD

Von Tom Dykstra, Jon P. Smith und Rick AndersonBy Tom Dykstra, Jon P Smith, and Rick Anderson

Die Contoso University-Web-App veranschaulicht, wie Razor Pages-Webanwendungen mithilfe von EF Core und Visual Studio erstellt werden können.The Contoso University web app demonstrates how to create Razor Pages web apps using EF Core and Visual Studio. Informationen zu den Tutorials finden Sie im ersten Tutorial.For information about the tutorial series, see the first tutorial.

Wenn Probleme auftreten, die Sie nicht beheben können, laden Sie die vollständige App herunter, und vergleichen Sie diesen Code mit dem Code, den Sie anhand des Tutorials erstellt haben.If you run into problems you can't solve, download the completed app and compare that code to what you created by following the tutorial.

In diesem Tutorial wird der erstellte CRUD-Code (CRUD = Create, Read, Update, Delete; Erstellen, Lesen, Aktualisieren, Löschen) überprüft und angepasst.In this tutorial, the scaffolded CRUD (create, read, update, delete) code is reviewed and customized.

Kein RepositoryNo repository

Einige Entwickler verwenden eine Dienstschicht oder ein Repositorymuster, um eine Abstraktionsschicht zwischen der Benutzeroberfläche (Razor Pages) und der Datenzugriffsschicht zu erstellen.Some developers use a service layer or repository pattern to create an abstraction layer between the UI (Razor Pages) and the data access layer. In diesem Tutorial ist dies nicht der Fall.This tutorial doesn't do that. Zur Minimierung der Komplexität und damit EF Core im Fokus dieser Tutorials bleibt, wird den Seitenmodellklassen EF Core-Code direkt hinzugefügt.To minimize complexity and keep the tutorial focused on EF Core, EF Core code is added directly to the page model classes.

Aktualisieren der Seite „Details“Update the Details page

Der Gerüstbaucode für die Seite „Students“ enthält keine Registrierungsdaten.The scaffolded code for the Students pages doesn't include enrollment data. In diesem Abschnitt fügen Sie der Seite „Details“ Registrierungen hinzu.In this section, you add enrollments to the Details page.

Lesen von RegistrierungenRead enrollments

Um die Registrierungsdaten eines Studenten auf der Seite anzuzeigen, müssen Sie diese lesen.To display a student's enrollment data on the page, you need to read it. Der Gerüstbaucode in Pages/Students/Details.cshtml.cs liest nur die Student-Daten ohne die Registrierungsdaten:The scaffolded code in Pages/Students/Details.cshtml.cs reads only the Student data, without the Enrollment data:

public async Task<IActionResult> OnGetAsync(int? id)
{
    if (id == null)
    {
        return NotFound();
    }

    Student = await _context.Students.FirstOrDefaultAsync(m => m.ID == id);

    if (Student == null)
    {
        return NotFound();
    }
    return Page();
}

Ersetzen Sie die OnGetAsync-Methode durch den folgenden Code, um Registrierungsdaten für den ausgewählten Studenten zu lesen.Replace the OnGetAsync method with the following code to read enrollment data for the selected student. Die Änderungen werden hervorgehoben.The changes are highlighted.

public async Task<IActionResult> OnGetAsync(int? id)
{
    if (id == null)
    {
        return NotFound();
    }

    Student = await _context.Students
        .Include(s => s.Enrollments)
        .ThenInclude(e => e.Course)
        .AsNoTracking()
        .FirstOrDefaultAsync(m => m.ID == id);

    if (Student == null)
    {
        return NotFound();
    }
    return Page();
}

Aufgrund der Methoden Include und ThenInclude lädt der Kontext die Navigationseigenschaft Student.Enrollments und in jeder Registrierung die Navigationseigenschaft Enrollment.Course.The Include and ThenInclude methods cause the context to load the Student.Enrollments navigation property, and within each enrollment the Enrollment.Course navigation property. Diese Methoden werden im Tutorial zum Lesen relevanter Daten ausführlich untersucht.These methods are examined in detail in the Read related data tutorial.

Die Methode AsNoTracking verbessert die Leistung in Szenarien, in denen die zurückgegebenen Entitäten nicht im aktuellen Kontext aktualisiert werden.The AsNoTracking method improves performance in scenarios where the entities returned are not updated in the current context. AsNoTracking wird später in diesem Tutorial behandelt.AsNoTracking is discussed later in this tutorial.

Anzeigen von RegistrierungenDisplay enrollments

Ersetzen Sie den Code in Pages/Students/Details.cshtml durch den folgenden Code, um eine Liste der Registrierungen anzuzeigen.Replace the code in Pages/Students/Details.cshtml with the following code to display a list of enrollments. Die Änderungen werden hervorgehoben.The changes are highlighted.

@page
@model ContosoUniversity.Pages.Students.DetailsModel

@{
    ViewData["Title"] = "Details";
}

<h1>Details</h1>

<div>
    <h4>Student</h4>
    <hr />
    <dl class="row">
        <dt class="col-sm-2">
            @Html.DisplayNameFor(model => model.Student.LastName)
        </dt>
        <dd class="col-sm-10">
            @Html.DisplayFor(model => model.Student.LastName)
        </dd>
        <dt class="col-sm-2">
            @Html.DisplayNameFor(model => model.Student.FirstMidName)
        </dt>
        <dd class="col-sm-10">
            @Html.DisplayFor(model => model.Student.FirstMidName)
        </dd>
        <dt class="col-sm-2">
            @Html.DisplayNameFor(model => model.Student.EnrollmentDate)
        </dt>
        <dd class="col-sm-10">
            @Html.DisplayFor(model => model.Student.EnrollmentDate)
        </dd>
        <dt class="col-sm-2">
            @Html.DisplayNameFor(model => model.Student.Enrollments)
        </dt>
        <dd class="col-sm-10">
            <table class="table">
                <tr>
                    <th>Course Title</th>
                    <th>Grade</th>
                </tr>
                @foreach (var item in Model.Student.Enrollments)
                {
                    <tr>
                        <td>
                            @Html.DisplayFor(modelItem => item.Course.Title)
                        </td>
                        <td>
                            @Html.DisplayFor(modelItem => item.Grade)
                        </td>
                    </tr>
                }
            </table>
        </dd>
    </dl>
</div>
<div>
    <a asp-page="./Edit" asp-route-id="@Model.Student.ID">Edit</a> |
    <a asp-page="./Index">Back to List</a>
</div>

Der vorangehende Code durchläuft die Entitäten in der Navigationseigenschaft Enrollments.The preceding code loops through the entities in the Enrollments navigation property. Für jede Registrierung werden der Kurstitel und die Klasse angezeigt.For each enrollment, it displays the course title and the grade. Der Kurstitel wird von der Entität „Course“ abgerufen, die in der Navigationseigenschaft Course der Entität „Enrollments“ gespeichert ist.The course title is retrieved from the Course entity that's stored in the Course navigation property of the Enrollments entity.

Führen Sie die App aus, wählen Sie die Registerkarte Studenten aus, und klicken Sie bei einem Studenten auf den Link Details.Run the app, select the Students tab, and click the Details link for a student. Die Liste der Kurse und Klassen für den ausgewählten Studenten wird angezeigt.The list of courses and grades for the selected student is displayed.

Möglichkeiten zum Lesen einer einzelnen EntitätWays to read one entity

Der generierte Code verwendet FirstOrDefaultAsync, um eine Entität zu lesen.The generated code uses FirstOrDefaultAsync to read one entity. Diese Methode gibt NULL zurück, wenn nichts gefunden wurde. Andernfalls wird die erste gefundene Zeile zurückgegeben, die die Abfragefilterkriterien erfüllt.This method returns null if nothing is found; otherwise, it returns the first row found that satisfies the query filter criteria. FirstOrDefaultAsync ist im Allgemeinen eine bessere Wahl als die folgenden Alternativen:FirstOrDefaultAsync is generally a better choice than the following alternatives:

  • SingleOrDefaultAsync: Löst eine Ausnahme aus, wenn mehrere Entitäten vorhanden sind, die dem Abfragefilter entsprechen.SingleOrDefaultAsync - Throws an exception if there's more than one entity that satisfies the query filter. Um zu ermitteln, ob von der Abfrage mehrere Zeilen zurückgegeben werden können, versucht SingleOrDefaultAsync, mehrere Zeilen abzurufen.To determine if more than one row could be returned by the query, SingleOrDefaultAsync tries to fetch multiple rows. Diese zusätzliche Arbeit ist nicht erforderlich, wenn die Abfrage nur eine Entität zurückgeben kann, wenn sie nach einem eindeutigen Schlüssel sucht.This extra work is unnecessary if the query can only return one entity, as when it searches on a unique key.
  • FindAsync: Sucht nach einer Entität mit dem Primärschlüssel.FindAsync - Finds an entity with the primary key (PK). Wenn eine Entität mit dem Primärschlüssel vom Kontext nachverfolgt wird, wird sie ohne eine Anforderung an die Datenbank zurückgegeben.If an entity with the PK is being tracked by the context, it's returned without a request to the database. Diese Methode ist für die Suche nach einer einzelnen Entität optimiert, aber Sie können Include nicht mit FindAsync aufrufen.This method is optimized to look up a single entity, but you can't call Include with FindAsync. Wenn also in Beziehung stehende Daten benötigt werden, ist FirstOrDefaultAsync die bessere Wahl.So if related data is needed, FirstOrDefaultAsync is the better choice.

Routendaten oder AbfragezeichenfolgeRoute data vs. query string

Die URL für die Detailseite lautet https://localhost:<port>/Students/Details?id=1.The URL for the Details page is https://localhost:<port>/Students/Details?id=1. Der Primärschlüsselwert der Entität befindet sich in der Abfragezeichenfolge.The entity's primary key value is in the query string. Einige Entwickler bevorzugen es, den Schlüsselwert in Routendaten zu übergeben: https://localhost:<port>/Students/Details/1.Some developers prefer to pass the key value in route data: https://localhost:<port>/Students/Details/1. Weitere Informationen finden Sie unter Aktualisieren des generierten Codes.For more information, see Update the generated code.

Aktualisieren der Seite „Erstellen“Update the Create page

Der OnPostAsync-Gerüstbaucode für die Seite „Create“ ist anfällig für Overposting.The scaffolded OnPostAsync code for the Create page is vulnerable to overposting. Ersetzen Sie die Methode OnPostAsync in der Datei Pages/Students/Create.cshtml.cs durch den folgenden Code.Replace the OnPostAsync method in Pages/Students/Create.cshtml.cs with the following code.

public async Task<IActionResult> OnPostAsync()
{
    var emptyStudent = new Student();

    if (await TryUpdateModelAsync<Student>(
        emptyStudent,
        "student",   // Prefix for form value.
        s => s.FirstMidName, s => s.LastName, s => s.EnrollmentDate))
    {
        _context.Students.Add(emptyStudent);
        await _context.SaveChangesAsync();
        return RedirectToPage("./Index");
    }

    return Page();
}

TryUpdateModelAsyncTryUpdateModelAsync

Der vorangehende Code erstellt ein Student-Objekt und verwendet dann die bereitgestellten Formularfelder, um die Eigenschaften des Student-Objekts zu aktualisieren.The preceding code creates a Student object and then uses posted form fields to update the Student object's properties. Die TryUpdateModelAsync-Methode:The TryUpdateModelAsync method:

  • Verwendet die bereitgestellten Formularwerte aus der PageContext-Eigenschaft in PageModel.Uses the posted form values from the PageContext property in the PageModel.
  • Aktualisiert nur die aufgeführten Eigenschaften (s => s.FirstMidName, s => s.LastName, s => s.EnrollmentDate).Updates only the properties listed (s => s.FirstMidName, s => s.LastName, s => s.EnrollmentDate).
  • Sucht nach Formularfeldern mit dem Präfix „Student“.Looks for form fields with a "student" prefix. Beispielsweise Student.FirstMidName.For example, Student.FirstMidName. Die Groß-/Kleinschreibung wird hier nicht beachtet.It's not case sensitive.
  • Verwendet das Modellbindungssystem zum Konvertieren von Formularwerten aus Zeichenfolgen in die Typen im Student-Modell.Uses the model binding system to convert form values from strings to the types in the Student model. EnrollmentDate wird z. B. in DateTime konvertiert.For example, EnrollmentDate is converted to DateTime.

Führen Sie die App aus, und erstellen Sie eine Student-Entität, um die Seite „Create“ zu testen.Run the app, and create a student entity to test the Create page.

OverpostingOverposting

Die Verwendung von TryUpdateModel zur Aktualisierung von Feldern mit bereitgestellten Werten ist eine bewährte Sicherheitsmethode, da Overposting verhindert wird.Using TryUpdateModel to update fields with posted values is a security best practice because it prevents overposting. Angenommen, die Student-Entität enthält die Eigenschaft Secret, die auf dieser Webseite nicht aktualisiert oder hinzugefügt werden sollte:For example, suppose the Student entity includes a Secret property that this web page shouldn't update or add:

public class Student
{
    public int ID { get; set; }
    public string LastName { get; set; }
    public string FirstMidName { get; set; }
    public DateTime EnrollmentDate { get; set; }
    public string Secret { get; set; }
}

Selbst wenn die App auf der Razor Page „Create“ oder „Update“ (Erstellen oder Aktualisieren) nicht über das Feld Secret verfügt, könnte ein Hacker den Secret-Wert durch Overposting festlegen.Even if the app doesn't have a Secret field on the create or update Razor Page, a hacker could set the Secret value by overposting. Ein Hacker könnte ein Tool wie Fiddler verwenden oder JavaScript-Code schreiben, um einen Secret-Formularwert bereitzustellen.A hacker could use a tool such as Fiddler, or write some JavaScript, to post a Secret form value. Die Felder, die von der Modellbindung bei der Erstellung einer Student-Instanz verwendet werden, werden nicht durch den ursprünglichen Code begrenzt.The original code doesn't limit the fields that the model binder uses when it creates a Student instance.

Jeder beliebige Wert, der vom Hacker im Secret-Formularfeld angegeben wird, wird in der Datenbank aktualisiert.Whatever value the hacker specified for the Secret form field is updated in the database. Die folgende Abbildung zeigt das Fiddler-Tool beim Hinzufügen des Felds Secret mit dem Wert „OverPost“ zu den bereitgestellten Formularwerten.The following image shows the Fiddler tool adding the Secret field, with the value "OverPost", to the posted form values.

Fiddler beim Hinzufügen des Felds „Secret“

Der Wert „OverPost“ wurde erfolgreich zur Eigenschaft Secret der eingefügten Zeile hinzugefügt.The value "OverPost" is successfully added to the Secret property of the inserted row. Dies geschieht, obwohl der App-Designer nie die Absicht hatte, die Eigenschaft Secret auf der Seite „Create“ festzulegen.That happens even though the app designer never intended the Secret property to be set with the Create page.

ViewModel-ElementView model

Ansichtsmodelle bieten eine alternative Möglichkeit zum Verhindern von Overposting.View models provide an alternative way to prevent overposting.

Das Anwendungsmodell wird häufig als Domänenmodell bezeichnet.The application model is often called the domain model. Das Domänenmodell enthält normalerweise alle Eigenschaften, die die entsprechende Entität in der Datenbank erfordert.The domain model typically contains all the properties required by the corresponding entity in the database. Das Ansichtsmodell enthält nur die Eigenschaften, die für die Benutzeroberflächenseite erforderlich sind, z. B. die Seite „Create“.The view model contains only the properties needed for the UI page, for example, the Create page.

Neben dem Ansichtsmodell verwenden einige Apps ein Bindungsmodell oder Eingabemodell für die Bereitstellung von Daten zwischen der Seitenmodellklasse auf den Razor Pages und dem Browser.In addition to the view model, some apps use a binding model or input model to pass data between the Razor Pages page model class and the browser.

Betrachten Sie das folgende StudentVM-Ansichtsmodell:Consider the following StudentVM view model:

public class StudentVM
{
    public int ID { get; set; }
    public string LastName { get; set; }
    public string FirstMidName { get; set; }
    public DateTime EnrollmentDate { get; set; }
}

Im folgenden Code wird das StudentVM-Ansichtsmodell für die Erstellung eines neuen Studenten verwendet:The following code uses the StudentVM view model to create a new student:

[BindProperty]
public StudentVM StudentVM { get; set; }

public async Task<IActionResult> OnPostAsync()
{
    if (!ModelState.IsValid)
    {
        return Page();
    }

    var entry = _context.Add(new Student());
    entry.CurrentValues.SetValues(StudentVM);
    await _context.SaveChangesAsync();
    return RedirectToPage("./Index");
}

Die Methode SetValues legt die Werte dieses Objekts fest, indem sie Werte aus einem anderen PropertyValues-Objekt liest.The SetValues method sets the values of this object by reading values from another PropertyValues object. SetValues verwendet die Übereinstimmung von Eigenschaftsnamen.SetValues uses property name matching. Der Ansichtsmodelltyp:The view model type:

  • Muss nicht mit dem Modelltyp verwandt sein.Doesn't need to be related to the model type.
  • Muss übereinstimmende Eigenschaften enthalten.Needs to have properties that match.

Die Verwendung von StudentVM erfordert, dass die Seite „Create“ StudentVM anstelle von Student verwendet:Using StudentVM requires the Create page use StudentVM rather than Student:

@page
@model CreateVMModel

@{
    ViewData["Title"] = "Create";
}

<h1>Create</h1>

<h4>Student</h4>
<hr />
<div class="row">
    <div class="col-md-4">
        <form method="post">
            <div asp-validation-summary="ModelOnly" class="text-danger"></div>
            <div class="form-group">
                <label asp-for="StudentVM.LastName" class="control-label"></label>
                <input asp-for="StudentVM.LastName" class="form-control" />
                <span asp-validation-for="StudentVM.LastName" class="text-danger"></span>
            </div>
            <div class="form-group">
                <label asp-for="StudentVM.FirstMidName" class="control-label"></label>
                <input asp-for="StudentVM.FirstMidName" class="form-control" />
                <span asp-validation-for="StudentVM.FirstMidName" class="text-danger"></span>
            </div>
            <div class="form-group">
                <label asp-for="StudentVM.EnrollmentDate" class="control-label"></label>
                <input asp-for="StudentVM.EnrollmentDate" class="form-control" />
                <span asp-validation-for="StudentVM.EnrollmentDate" class="text-danger"></span>
            </div>
            <div class="form-group">
                <input type="submit" value="Create" class="btn btn-primary" />
            </div>
        </form>
    </div>
</div>

<div>
    <a asp-page="Index">Back to List</a>
</div>

@section Scripts {
    @{await Html.RenderPartialAsync("_ValidationScriptsPartial");}
}

Aktualisieren der Seite „Bearbeiten“Update the Edit page

Ersetzen Sie die Methoden OnGetAsync und OnPostAsync in der Datei Pages/Students/Edit.cshtml.cs durch den folgenden Code.In Pages/Students/Edit.cshtml.cs, replace the OnGetAsync and OnPostAsync methods with the following code.

public async Task<IActionResult> OnGetAsync(int? id)
{
    if (id == null)
    {
        return NotFound();
    }

    Student = await _context.Students.FindAsync(id);

    if (Student == null)
    {
        return NotFound();
    }
    return Page();
}

public async Task<IActionResult> OnPostAsync(int id)
{
    var studentToUpdate = await _context.Students.FindAsync(id);

    if (studentToUpdate == null)
    {
        return NotFound();
    }

    if (await TryUpdateModelAsync<Student>(
        studentToUpdate,
        "student",
        s => s.FirstMidName, s => s.LastName, s => s.EnrollmentDate))
    {
        await _context.SaveChangesAsync();
        return RedirectToPage("./Index");
    }

    return Page();
}

Die Codeänderungen sind bis auf wenige Ausnahmen mit denen auf der Seite „Erstellen“ vergleichbar:The code changes are similar to the Create page with a few exceptions:

  • FirstOrDefaultAsync wurde durch FindAsync ersetzt.FirstOrDefaultAsync has been replaced with FindAsync. Wenn Sie keine in Beziehung stehende Daten einschließen müssen, ist FindAsync effizienter.When you don't have to include related data, FindAsync is more efficient.
  • OnPostAsync verfügt über einen id-Parameter.OnPostAsync has an id parameter.
  • Anstatt einen leeren Studentendatensatz zu erstellen, wird der aktuelle Student aus der Datenbank abgerufen.The current student is fetched from the database, rather than creating an empty student.

Führen Sie die App aus, und testen Sie sie, indem Sie einen Studenten erstellen und bearbeiten.Run the app, and test it by creating and editing a student.

EntitätsstatusEntity States

Im Datenbankkontext wird nachverfolgt, ob Entitäten im Arbeitsspeicher mit den zugehörigen Zeilen in der Datenbank synchron sind.The database context keeps track of whether entities in memory are in sync with their corresponding rows in the database. Die Nachverfolgungsinformationen bestimmen, was geschieht, wenn SaveChangesAsync aufgerufen wird.This tracking information determines what happens when SaveChangesAsync is called. Wenn beispielsweise eine neue Entität an die Methode AddAsync übergeben wird, wird der Zustand dieser Entität auf Added festgelegt.For example, when a new entity is passed to the AddAsync method, that entity's state is set to Added. Wenn SaveChangesAsync aufgerufen wird, gibt der Datenbankkontext den SQL-Befehl INSERT aus.When SaveChangesAsync is called, the database context issues a SQL INSERT command.

Eine Entität kann einen der folgenden Status aufweisen:An entity may be in one of the following states:

  • Added: Die Entität ist noch nicht in der Datenbank vorhanden.Added: The entity doesn't yet exist in the database. Die SaveChanges-Methode gibt eine INSERT-Anweisung aus.The SaveChanges method issues an INSERT statement.

  • Unchanged: Für diese Entität müssen keine Änderungen gespeichert werden.Unchanged: No changes need to be saved with this entity. Eine Entität weist diesen Zustand auf, wenn sie von der Datenbank gelesen wird.An entity has this status when it's read from the database.

  • Modified: Einige oder alle Eigenschaftswerte der Entität wurden geändert.Modified: Some or all of the entity's property values have been modified. Die SaveChanges-Methode gibt eine UPDATE-Anweisung aus.The SaveChanges method issues an UPDATE statement.

  • Deleted: Die Entität wurde zum Löschen markiert.Deleted: The entity has been marked for deletion. Die SaveChanges-Methode gibt eine DELETE-Anweisung aus.The SaveChanges method issues a DELETE statement.

  • Detached: Die Entität wird nicht vom Datenbankkontext nachverfolgt.Detached: The entity isn't being tracked by the database context.

Zustandsänderungen werden in einer Desktop-App in der Regel automatisch festgelegt.In a desktop app, state changes are typically set automatically. Eine Entität wird gelesen, Änderungen werden vorgenommen, und der Entitätszustand wird automatisch in Modified geändert.An entity is read, changes are made, and the entity state is automatically changed to Modified. Durch das Aufrufen von SaveChanges wird eine SQL-UPDATE-Anweisung generiert, die nur die geänderten Eigenschaften aktualisiert.Calling SaveChanges generates a SQL UPDATE statement that updates only the changed properties.

In einer Web-App wird der DbContextverfügbar gemacht, der eine Entität liest und die Daten anzeigt, nachdem eine Seite gerendert wurde.In a web app, the DbContext that reads an entity and displays the data is disposed after a page is rendered. Wenn die Methode OnPostAsync einer Seite aufgerufen wird, wird eine neue Webanforderung mit einer neuen Instanz von DbContext gestellt.When a page's OnPostAsync method is called, a new web request is made and with a new instance of the DbContext. Durch erneutes Lesen der Entität in diesem neuen Kontext wird die Desktopverarbeitung simuliert.Rereading the entity in that new context simulates desktop processing.

Aktualisieren der Seite „Delete“ (Löschen)Update the Delete page

In diesem Abschnitt implementieren Sie eine benutzerdefinierte Fehlermeldung für den Fall, dass der Aufruf von SaveChanges zu einem Fehler führt.In this section, a custom error message is implemented when the call to SaveChanges fails.

Ersetzen Sie den Code in Pages/Students/Delete.cshtml.cs durch den folgenden Code.Replace the code in Pages/Students/Delete.cshtml.cs with the following code. Die Änderungen werden hervorgehoben:The changes are highlighted:

public class DeleteModel : PageModel
{
    private readonly ContosoUniversity.Data.SchoolContext _context;

    public DeleteModel(ContosoUniversity.Data.SchoolContext context)
    {
        _context = context;
    }

    [BindProperty]
    public Student Student { get; set; }
    public string ErrorMessage { get; set; }

    public async Task<IActionResult> OnGetAsync(int? id, bool? saveChangesError = false)
    {
        if (id == null)
        {
            return NotFound();
        }

        Student = await _context.Students
            .AsNoTracking()
            .FirstOrDefaultAsync(m => m.ID == id);

        if (Student == null)
        {
            return NotFound();
        }

        if (saveChangesError.GetValueOrDefault())
        {
            ErrorMessage = "Delete failed. Try again";
        }

        return Page();
    }

    public async Task<IActionResult> OnPostAsync(int? id)
    {
        if (id == null)
        {
            return NotFound();
        }

        var student = await _context.Students.FindAsync(id);

        if (student == null)
        {
            return NotFound();
        }

        try
        {
            _context.Students.Remove(student);
            await _context.SaveChangesAsync();
            return RedirectToPage("./Index");
        }
        catch (DbUpdateException /* ex */)
        {
            //Log the error (uncomment ex variable name and write a log.)
            return RedirectToAction("./Delete",
                                 new { id, saveChangesError = true });
        }
    }
}

Der vorangehende Code fügt der OnGetAsync-Methodensignatur den optionalen-Parameter saveChangesError hinzu.The preceding code adds the optional parameter saveChangesError to the OnGetAsync method signature. saveChangesError gibt an, ob die Methode nach einem Fehler beim Löschen des Studentenobjekts aufgerufen wurde.saveChangesError indicates whether the method was called after a failure to delete the student object. Der Löschvorgang schlägt möglicherweise aufgrund von vorübergehenden Netzwerkproblemen fehl.The delete operation might fail because of transient network problems. Vorübergehende Netzwerkfehler treten eher auf, wenn sich die Datenbank in der Cloud befindet.Transient network errors are more likely when the database is in the cloud. Der saveChangesError-Parameter ist false, wenn auf der Seite „Delete“ über die Benutzeroberfläche OnGetAsync aufgerufen wird.The saveChangesError parameter is false when the Delete page OnGetAsync is called from the UI. Wenn OnGetAsync von OnPostAsync aufgerufen wird (da der Löschvorgang fehlerhaft war), ist der saveChangesError-Parameter true.When OnGetAsync is called by OnPostAsync because the delete operation failed, the saveChangesError parameter is true.

Die OnPostAsync-Methode ruft die ausgewählte Entität ab und anschließend die Methode Remove auf, um den Zustand der Entität auf Deleted festzulegen.The OnPostAsync method retrieves the selected entity, then calls the Remove method to set the entity's status to Deleted. Beim Aufruf von SaveChanges wird der SQL-Befehl DELETE generiert.When SaveChanges is called, a SQL DELETE command is generated. Wenn Remove fehlschlägt:If Remove fails:

  • Wird die Datenbankausnahme abgefangen.The database exception is caught.
  • Wird die Methode OnGetAsync auf der Seite „Löschen“ mit saveChangesError=true aufgerufen.The Delete pages OnGetAsync method is called with saveChangesError=true.

Fügen Sie in Pages/Students/Delete.cshtml eine Fehlermeldung hinzu:Add an error message to Pages/Students/Delete.cshtml:

@page
@model ContosoUniversity.Pages.Students.DeleteModel

@{
    ViewData["Title"] = "Delete";
}

<h1>Delete</h1>

<p class="text-danger">@Model.ErrorMessage</p>

<h3>Are you sure you want to delete this?</h3>
<div>
    <h4>Student</h4>
    <hr />
    <dl class="row">
        <dt class="col-sm-2">
            @Html.DisplayNameFor(model => model.Student.LastName)
        </dt>
        <dd class="col-sm-10">
            @Html.DisplayFor(model => model.Student.LastName)
        </dd>
        <dt class="col-sm-2">
            @Html.DisplayNameFor(model => model.Student.FirstMidName)
        </dt>
        <dd class="col-sm-10">
            @Html.DisplayFor(model => model.Student.FirstMidName)
        </dd>
        <dt class="col-sm-2">
            @Html.DisplayNameFor(model => model.Student.EnrollmentDate)
        </dt>
        <dd class="col-sm-10">
            @Html.DisplayFor(model => model.Student.EnrollmentDate)
        </dd>
    </dl>

    <form method="post">
        <input type="hidden" asp-for="Student.ID" />
        <input type="submit" value="Delete" class="btn btn-danger" /> |
        <a asp-page="./Index">Back to List</a>
    </form>
</div>

Führen Sie die App aus, und löschen Sie einen Studenten, um die Seite „Delete“ zu testen.Run the app and delete a student to test the Delete page.

Nächste SchritteNext steps

In diesem Tutorial wird der erstellte CRUD-Code (CRUD = Create, Read, Update, Delete; Erstellen, Lesen, Aktualisieren, Löschen) überprüft und angepasst.In this tutorial, the scaffolded CRUD (create, read, update, delete) code is reviewed and customized.

Kein RepositoryNo repository

Einige Entwickler verwenden eine Dienstschicht oder ein Repositorymuster, um eine Abstraktionsschicht zwischen der Benutzeroberfläche (Razor Pages) und der Datenzugriffsschicht zu erstellen.Some developers use a service layer or repository pattern to create an abstraction layer between the UI (Razor Pages) and the data access layer. In diesem Tutorial ist dies nicht der Fall.This tutorial doesn't do that. Zur Minimierung der Komplexität und damit EF Core im Fokus dieser Tutorials bleibt, wird den Seitenmodellklassen EF Core-Code direkt hinzugefügt.To minimize complexity and keep the tutorial focused on EF Core, EF Core code is added directly to the page model classes.

Aktualisieren der Seite „Details“Update the Details page

Der Gerüstbaucode für die Seite „Students“ enthält keine Registrierungsdaten.The scaffolded code for the Students pages doesn't include enrollment data. In diesem Abschnitt fügen Sie der Seite „Details“ Registrierungen hinzu.In this section, enrollments are added to the Details page.

Lesen von RegistrierungenRead enrollments

Um die Registrierungsdaten eines Kursteilnehmers auf der Seite anzuzeigen, müssen die Registrierungsdaten gelesen werden.To display a student's enrollment data on the page, the enrollement data needs to be read. Der Gerüstbaucode in Pages/Students/Details.cshtml.cs liest nur die Student-Daten ohne die Registrierungsdaten:The scaffolded code in Pages/Students/Details.cshtml.cs reads only the Student data, without the Enrollment data:

public async Task<IActionResult> OnGetAsync(int? id)
{
    if (id == null)
    {
        return NotFound();
    }

    Student = await _context.Students.FirstOrDefaultAsync(m => m.ID == id);

    if (Student == null)
    {
        return NotFound();
    }
    return Page();
}

Ersetzen Sie die OnGetAsync-Methode durch den folgenden Code, um Registrierungsdaten für den ausgewählten Studenten zu lesen.Replace the OnGetAsync method with the following code to read enrollment data for the selected student. Die Änderungen werden hervorgehoben.The changes are highlighted.

public async Task<IActionResult> OnGetAsync(int? id)
{
    if (id == null)
    {
        return NotFound();
    }

    Student = await _context.Students
        .Include(s => s.Enrollments)
        .ThenInclude(e => e.Course)
        .AsNoTracking()
        .FirstOrDefaultAsync(m => m.ID == id);

    if (Student == null)
    {
        return NotFound();
    }
    return Page();
}

Aufgrund der Methoden Include und ThenInclude lädt der Kontext die Navigationseigenschaft Student.Enrollments und in jeder Registrierung die Navigationseigenschaft Enrollment.Course.The Include and ThenInclude methods cause the context to load the Student.Enrollments navigation property, and within each enrollment the Enrollment.Course navigation property. Diese Methoden werden im Tutorial zum Lesen in Beziehung stehender Daten ausführlich untersucht.These methods are examined in detail in the Reading related data tutorial.

Die Methode AsNoTracking verbessert die Leistung in Szenarien, in denen die zurückgegebenen Entitäten nicht im aktuellen Kontext aktualisiert werden.The AsNoTracking method improves performance in scenarios where the entities returned are not updated in the current context. AsNoTracking wird später in diesem Tutorial behandelt.AsNoTracking is discussed later in this tutorial.

Anzeigen von RegistrierungenDisplay enrollments

Ersetzen Sie den Code in Pages/Students/Details.cshtml durch den folgenden Code, um eine Liste der Registrierungen anzuzeigen.Replace the code in Pages/Students/Details.cshtml with the following code to display a list of enrollments. Die Änderungen werden hervorgehoben.The changes are highlighted.

@page
@model ContosoUniversity.Pages.Students.DetailsModel

@{
    ViewData["Title"] = "Details";
}

<h1>Details</h1>

<div>
    <h4>Student</h4>
    <hr />
    <dl class="row">
        <dt class="col-sm-2">
            @Html.DisplayNameFor(model => model.Student.LastName)
        </dt>
        <dd class="col-sm-10">
            @Html.DisplayFor(model => model.Student.LastName)
        </dd>
        <dt class="col-sm-2">
            @Html.DisplayNameFor(model => model.Student.FirstMidName)
        </dt>
        <dd class="col-sm-10">
            @Html.DisplayFor(model => model.Student.FirstMidName)
        </dd>
        <dt class="col-sm-2">
            @Html.DisplayNameFor(model => model.Student.EnrollmentDate)
        </dt>
        <dd class="col-sm-10">
            @Html.DisplayFor(model => model.Student.EnrollmentDate)
        </dd>
        <dt class="col-sm-2">
            @Html.DisplayNameFor(model => model.Student.Enrollments)
        </dt>
        <dd class="col-sm-10">
            <table class="table">
                <tr>
                    <th>Course Title</th>
                    <th>Grade</th>
                </tr>
                @foreach (var item in Model.Student.Enrollments)
                {
                    <tr>
                        <td>
                            @Html.DisplayFor(modelItem => item.Course.Title)
                        </td>
                        <td>
                            @Html.DisplayFor(modelItem => item.Grade)
                        </td>
                    </tr>
                }
            </table>
        </dd>
    </dl>
</div>
<div>
    <a asp-page="./Edit" asp-route-id="@Model.Student.ID">Edit</a> |
    <a asp-page="./Index">Back to List</a>
</div>

Der vorangehende Code durchläuft die Entitäten in der Navigationseigenschaft Enrollments.The preceding code loops through the entities in the Enrollments navigation property. Für jede Registrierung werden der Kurstitel und die Klasse angezeigt.For each enrollment, it displays the course title and the grade. Der Kurstitel wird von der Entität „Course“ abgerufen, die in der Navigationseigenschaft Course der Entität „Enrollments“ gespeichert ist.The course title is retrieved from the Course entity that's stored in the Course navigation property of the Enrollments entity.

Führen Sie die App aus, wählen Sie die Registerkarte Studenten aus, und klicken Sie bei einem Studenten auf den Link Details.Run the app, select the Students tab, and click the Details link for a student. Die Liste der Kurse und Klassen für den ausgewählten Studenten wird angezeigt.The list of courses and grades for the selected student is displayed.

Möglichkeiten zum Lesen einer einzelnen EntitätWays to read one entity

Der generierte Code verwendet FirstOrDefaultAsync, um eine Entität zu lesen.The generated code uses FirstOrDefaultAsync to read one entity. Diese Methode gibt NULL zurück, wenn nichts gefunden wurde. Andernfalls wird die erste gefundene Zeile zurückgegeben, die die Abfragefilterkriterien erfüllt.This method returns null if nothing is found; otherwise, it returns the first row found that satisfies the query filter criteria. FirstOrDefaultAsync ist im Allgemeinen eine bessere Wahl als die folgenden Alternativen:FirstOrDefaultAsync is generally a better choice than the following alternatives:

  • SingleOrDefaultAsync: Löst eine Ausnahme aus, wenn mehrere Entitäten vorhanden sind, die dem Abfragefilter entsprechen.SingleOrDefaultAsync - Throws an exception if there's more than one entity that satisfies the query filter. Um zu ermitteln, ob von der Abfrage mehrere Zeilen zurückgegeben werden können, versucht SingleOrDefaultAsync, mehrere Zeilen abzurufen.To determine if more than one row could be returned by the query, SingleOrDefaultAsync tries to fetch multiple rows. Diese zusätzliche Arbeit ist nicht erforderlich, wenn die Abfrage nur eine Entität zurückgeben kann, wenn sie nach einem eindeutigen Schlüssel sucht.This extra work is unnecessary if the query can only return one entity, as when it searches on a unique key.
  • FindAsync: Sucht nach einer Entität mit dem Primärschlüssel.FindAsync - Finds an entity with the primary key (PK). Wenn eine Entität mit dem Primärschlüssel vom Kontext nachverfolgt wird, wird sie ohne eine Anforderung an die Datenbank zurückgegeben.If an entity with the PK is being tracked by the context, it's returned without a request to the database. Diese Methode ist für die Suche nach einer einzelnen Entität optimiert, aber Sie können Include nicht mit FindAsync aufrufen.This method is optimized to look up a single entity, but you can't call Include with FindAsync. Wenn also in Beziehung stehende Daten benötigt werden, ist FirstOrDefaultAsync die bessere Wahl.So if related data is needed, FirstOrDefaultAsync is the better choice.

Routendaten oder AbfragezeichenfolgeRoute data vs. query string

Die URL für die Detailseite lautet https://localhost:<port>/Students/Details?id=1.The URL for the Details page is https://localhost:<port>/Students/Details?id=1. Der Primärschlüsselwert der Entität befindet sich in der Abfragezeichenfolge.The entity's primary key value is in the query string. Einige Entwickler bevorzugen es, den Schlüsselwert in Routendaten zu übergeben: https://localhost:<port>/Students/Details/1.Some developers prefer to pass the key value in route data: https://localhost:<port>/Students/Details/1. Weitere Informationen finden Sie unter Aktualisieren des generierten Codes.For more information, see Update the generated code.

Aktualisieren der Seite „Erstellen“Update the Create page

Der OnPostAsync-Gerüstbaucode für die Seite „Create“ ist anfällig für Overposting.The scaffolded OnPostAsync code for the Create page is vulnerable to overposting. Ersetzen Sie die Methode OnPostAsync in der Datei Pages/Students/Create.cshtml.cs durch den folgenden Code.Replace the OnPostAsync method in Pages/Students/Create.cshtml.cs with the following code.

public async Task<IActionResult> OnPostAsync()
{
    var emptyStudent = new Student();

    if (await TryUpdateModelAsync<Student>(
        emptyStudent,
        "student",   // Prefix for form value.
        s => s.FirstMidName, s => s.LastName, s => s.EnrollmentDate))
    {
        _context.Students.Add(emptyStudent);
        await _context.SaveChangesAsync();
        return RedirectToPage("./Index");
    }

    return Page();
}

TryUpdateModelAsyncTryUpdateModelAsync

Der vorangehende Code erstellt ein Student-Objekt und verwendet dann die bereitgestellten Formularfelder, um die Eigenschaften des Student-Objekts zu aktualisieren.The preceding code creates a Student object and then uses posted form fields to update the Student object's properties. Die TryUpdateModelAsync-Methode:The TryUpdateModelAsync method:

  • Verwendet die bereitgestellten Formularwerte aus der PageContext-Eigenschaft in PageModel.Uses the posted form values from the PageContext property in the PageModel.
  • Aktualisiert nur die aufgeführten Eigenschaften (s => s.FirstMidName, s => s.LastName, s => s.EnrollmentDate).Updates only the properties listed (s => s.FirstMidName, s => s.LastName, s => s.EnrollmentDate).
  • Sucht nach Formularfeldern mit dem Präfix „Student“.Looks for form fields with a "student" prefix. Beispielsweise Student.FirstMidName.For example, Student.FirstMidName. Die Groß-/Kleinschreibung wird hier nicht beachtet.It's not case sensitive.
  • Verwendet das Modellbindungssystem zum Konvertieren von Formularwerten aus Zeichenfolgen in die Typen im Student-Modell.Uses the model binding system to convert form values from strings to the types in the Student model. EnrollmentDate muss beispielsweise in DateTime konvertiert werden.For example, EnrollmentDate has to be converted to DateTime.

Führen Sie die App aus, und erstellen Sie eine Student-Entität, um die Seite „Create“ zu testen.Run the app, and create a student entity to test the Create page.

OverpostingOverposting

Die Verwendung von TryUpdateModel zur Aktualisierung von Feldern mit bereitgestellten Werten ist eine bewährte Sicherheitsmethode, da Overposting verhindert wird.Using TryUpdateModel to update fields with posted values is a security best practice because it prevents overposting. Angenommen, die Student-Entität enthält die Eigenschaft Secret, die auf dieser Webseite nicht aktualisiert oder hinzugefügt werden sollte:For example, suppose the Student entity includes a Secret property that this web page shouldn't update or add:

public class Student
{
    public int ID { get; set; }
    public string LastName { get; set; }
    public string FirstMidName { get; set; }
    public DateTime EnrollmentDate { get; set; }
    public string Secret { get; set; }
}

Selbst wenn die App auf der Razor Page „Create“ oder „Update“ (Erstellen oder Aktualisieren) nicht über das Feld Secret verfügt, könnte ein Hacker den Secret-Wert durch Overposting festlegen.Even if the app doesn't have a Secret field on the create or update Razor Page, a hacker could set the Secret value by overposting. Ein Hacker könnte ein Tool wie Fiddler verwenden oder JavaScript-Code schreiben, um einen Secret-Formularwert bereitzustellen.A hacker could use a tool such as Fiddler, or write some JavaScript, to post a Secret form value. Die Felder, die von der Modellbindung bei der Erstellung einer Student-Instanz verwendet werden, werden nicht durch den ursprünglichen Code begrenzt.The original code doesn't limit the fields that the model binder uses when it creates a Student instance.

Jeder beliebige Wert, der vom Hacker im Secret-Formularfeld angegeben wird, wird in der Datenbank aktualisiert.Whatever value the hacker specified for the Secret form field is updated in the database. Die folgende Abbildung zeigt das Fiddler-Tool beim Hinzufügen des Felds Secret (mit dem Wert „OverPost“) zu den bereitgestellten Formularwerten.The following image shows the Fiddler tool adding the Secret field (with the value "OverPost") to the posted form values.

Fiddler beim Hinzufügen des Felds „Secret“

Der Wert „OverPost“ wurde erfolgreich zur Eigenschaft Secret der eingefügten Zeile hinzugefügt.The value "OverPost" is successfully added to the Secret property of the inserted row. Dies geschieht, obwohl der App-Designer nie die Absicht hatte, die Eigenschaft Secret auf der Seite „Create“ festzulegen.That happens even though the app designer never intended the Secret property to be set with the Create page.

ViewModel-ElementView model

Ansichtsmodelle bieten eine alternative Möglichkeit zum Verhindern von Overposting.View models provide an alternative way to prevent overposting.

Das Anwendungsmodell wird häufig als Domänenmodell bezeichnet.The application model is often called the domain model. Das Domänenmodell enthält normalerweise alle Eigenschaften, die die entsprechende Entität in der Datenbank erfordert.The domain model typically contains all the properties required by the corresponding entity in the database. Das Ansichtsmodell enthält nur die Eigenschaften, die für die Benutzeroberfläche (z.B. die Seite „Create“) erforderlich sind, für die es verwendet wird.The view model contains only the properties needed for the UI that it is used for (for example, the Create page).

Neben dem Ansichtsmodell verwenden einige Apps ein Bindungsmodell oder Eingabemodell für die Bereitstellung von Daten zwischen der Seitenmodellklasse auf den Razor Pages und dem Browser.In addition to the view model, some apps use a binding model or input model to pass data between the Razor Pages page model class and the browser.

Betrachten Sie das folgende Student-Ansichtsmodell:Consider the following Student view model:

using System;

namespace ContosoUniversity.Models
{
    public class StudentVM
    {
        public int ID { get; set; }
        public string LastName { get; set; }
        public string FirstMidName { get; set; }
        public DateTime EnrollmentDate { get; set; }
    }
}

Im folgenden Code wird das StudentVM-Ansichtsmodell für die Erstellung eines neuen Studenten verwendet:The following code uses the StudentVM view model to create a new student:

[BindProperty]
public StudentVM StudentVM { get; set; }

public async Task<IActionResult> OnPostAsync()
{
    if (!ModelState.IsValid)
    {
        return Page();
    }

    var entry = _context.Add(new Student());
    entry.CurrentValues.SetValues(StudentVM);
    await _context.SaveChangesAsync();
    return RedirectToPage("./Index");
}

Die Methode SetValues legt die Werte dieses Objekts fest, indem sie Werte aus einem anderen PropertyValues-Objekt liest.The SetValues method sets the values of this object by reading values from another PropertyValues object. SetValues verwendet die Übereinstimmung von Eigenschaftsnamen.SetValues uses property name matching. Der Ansichtsmodelltyp muss nicht mit dem Modelltyp verknüpft sein. Er muss lediglich über übereinstimmende Eigenschaften verfügen.The view model type doesn't need to be related to the model type, it just needs to have properties that match.

Für die Verwendung von StudentVM muss Create.cshtml aktualisiert werden, damit StudentVM anstelle von Student verwendet wird.Using StudentVM requires Create.cshtml be updated to use StudentVM rather than Student.

Aktualisieren der Seite „Bearbeiten“Update the Edit page

Ersetzen Sie die Methoden OnGetAsync und OnPostAsync in der Datei Pages/Students/Edit.cshtml.cs durch den folgenden Code.In Pages/Students/Edit.cshtml.cs, replace the OnGetAsync and OnPostAsync methods with the following code.

public async Task<IActionResult> OnGetAsync(int? id)
{
    if (id == null)
    {
        return NotFound();
    }

    Student = await _context.Students.FindAsync(id);

    if (Student == null)
    {
        return NotFound();
    }
    return Page();
}

public async Task<IActionResult> OnPostAsync(int id)
{
    var studentToUpdate = await _context.Students.FindAsync(id);

    if (studentToUpdate == null)
    {
        return NotFound();
    }

    if (await TryUpdateModelAsync<Student>(
        studentToUpdate,
        "student",
        s => s.FirstMidName, s => s.LastName, s => s.EnrollmentDate))
    {
        await _context.SaveChangesAsync();
        return RedirectToPage("./Index");
    }

    return Page();
}

Die Codeänderungen sind bis auf wenige Ausnahmen mit denen auf der Seite „Erstellen“ vergleichbar:The code changes are similar to the Create page with a few exceptions:

  • FirstOrDefaultAsync wurde durch FindAsync ersetzt.FirstOrDefaultAsync has been replaced with FindAsync. Wenn keine enthaltenen zugehörigen Daten benötigt werden, ist FindAsync effizienter.When included related data is not needed, FindAsync is more efficient.
  • OnPostAsync verfügt über einen id-Parameter.OnPostAsync has an id parameter.
  • Anstatt einen leeren Studentendatensatz zu erstellen, wird der aktuelle Student aus der Datenbank abgerufen.The current student is fetched from the database, rather than creating an empty student.

Führen Sie die App aus, und testen Sie sie, indem Sie einen Studenten erstellen und bearbeiten.Run the app, and test it by creating and editing a student.

EntitätsstatusEntity States

Im Datenbankkontext wird nachverfolgt, ob Entitäten im Arbeitsspeicher mit den zugehörigen Zeilen in der Datenbank synchron sind.The database context keeps track of whether entities in memory are in sync with their corresponding rows in the database. Die Nachverfolgungsinformationen bestimmen, was geschieht, wenn SaveChangesAsync aufgerufen wird.This tracking information determines what happens when SaveChangesAsync is called. Wenn beispielsweise eine neue Entität an die Methode AddAsync übergeben wird, wird der Zustand dieser Entität auf Added festgelegt.For example, when a new entity is passed to the AddAsync method, that entity's state is set to Added. Wenn SaveChangesAsync aufgerufen wird, gibt der Datenbankkontext den SQL-Befehl INSERT aus.When SaveChangesAsync is called, the database context issues a SQL INSERT command.

Eine Entität kann einen der folgenden Status aufweisen:An entity may be in one of the following states:

  • Added: Die Entität ist noch nicht in der Datenbank vorhanden.Added: The entity doesn't yet exist in the database. Die Methode SaveChanges gibt eine INSERT-Anweisung aus.The SaveChanges method issues an INSERT statement.

  • Unchanged: Für diese Entität müssen keine Änderungen gespeichert werden.Unchanged: No changes need to be saved with this entity. Eine Entität weist diesen Zustand auf, wenn sie von der Datenbank gelesen wird.An entity has this status when it's read from the database.

  • Modified: Einige oder alle Eigenschaftswerte der Entität wurden geändert.Modified: Some or all of the entity's property values have been modified. Die Methode SaveChanges gibt eine UPDATE-Anweisung aus.The SaveChanges method issues an UPDATE statement.

  • Deleted: Die Entität wurde zum Löschen markiert.Deleted: The entity has been marked for deletion. Die Methode SaveChanges gibt eine DELETE-Anweisung aus.The SaveChanges method issues a DELETE statement.

  • Detached: Die Entität wird nicht vom Datenbankkontext nachverfolgt.Detached: The entity isn't being tracked by the database context.

Zustandsänderungen werden in einer Desktop-App in der Regel automatisch festgelegt.In a desktop app, state changes are typically set automatically. Eine Entität wird gelesen, Änderungen werden vorgenommen, und der Entitätszustand wird automatisch in Modified geändert.An entity is read, changes are made, and the entity state is automatically changed to Modified. Durch das Aufrufen von SaveChanges wird eine SQL UPDATE-Anweisung generiert, die nur die geänderten Eigenschaften aktualisiert.Calling SaveChanges generates a SQL UPDATE statement that updates only the changed properties.

In einer Web-App wird der DbContextverfügbar gemacht, der eine Entität liest und die Daten anzeigt, nachdem eine Seite gerendert wurde.In a web app, the DbContext that reads an entity and displays the data is disposed after a page is rendered. Wenn die Methode OnPostAsync einer Seite aufgerufen wird, wird eine neue Webanforderung mit einer neuen Instanz von DbContext gestellt.When a page's OnPostAsync method is called, a new web request is made and with a new instance of the DbContext. Durch erneutes Lesen der Entität in diesem neuen Kontext wird die Desktopverarbeitung simuliert.Rereading the entity in that new context simulates desktop processing.

Aktualisieren der Seite „Delete“ (Löschen)Update the Delete page

In diesem Abschnitt implementieren Sie eine benutzerdefinierte Fehlermeldung für den Fall, dass der Aufruf von SaveChanges fehlschlägt.In this section, you implement a custom error message when the call to SaveChanges fails.

Ersetzen Sie den Code in Pages/Students/Delete.cshtml.cs durch den folgenden Code.Replace the code in Pages/Students/Delete.cshtml.cs with the following code. Die Änderungen werden hervorgehoben (mit Ausnahme der Bereinigung von using-Anweisungen).The changes are highlighted (other than cleanup of using statements).

using ContosoUniversity.Models;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
using Microsoft.EntityFrameworkCore;
using System.Threading.Tasks;

namespace ContosoUniversity.Pages.Students
{
    public class DeleteModel : PageModel
    {
        private readonly ContosoUniversity.Data.SchoolContext _context;

        public DeleteModel(ContosoUniversity.Data.SchoolContext context)
        {
            _context = context;
        }

        [BindProperty]
        public Student Student { get; set; }
        public string ErrorMessage { get; set; }

        public async Task<IActionResult> OnGetAsync(int? id, bool? saveChangesError = false)
        {
            if (id == null)
            {
                return NotFound();
            }

            Student = await _context.Students
                .AsNoTracking()
                .FirstOrDefaultAsync(m => m.ID == id);

            if (Student == null)
            {
                return NotFound();
            }

            if (saveChangesError.GetValueOrDefault())
            {
                ErrorMessage = "Delete failed. Try again";
            }

            return Page();
        }

        public async Task<IActionResult> OnPostAsync(int? id)
        {
            if (id == null)
            {
                return NotFound();
            }

            var student = await _context.Students.FindAsync(id);

            if (student == null)
            {
                return NotFound();
            }

            try
            {
                _context.Students.Remove(student);
                await _context.SaveChangesAsync();
                return RedirectToPage("./Index");
            }
            catch (DbUpdateException /* ex */)
            {
                //Log the error (uncomment ex variable name and write a log.)
                return RedirectToAction("./Delete",
                                     new { id, saveChangesError = true });
            }
        }
    }
}

Der vorangehende Code fügt der OnGetAsync-Methodensignatur den optionalen-Parameter saveChangesError hinzu.The preceding code adds the optional parameter saveChangesError to the OnGetAsync method signature. saveChangesError gibt an, ob die Methode nach einem Fehler beim Löschen des Studentenobjekts aufgerufen wurde.saveChangesError indicates whether the method was called after a failure to delete the student object. Der Löschvorgang schlägt möglicherweise aufgrund von vorübergehenden Netzwerkproblemen fehl.The delete operation might fail because of transient network problems. Vorübergehende Netzwerkfehler treten eher auf, wenn sich die Datenbank in der Cloud befindet.Transient network errors are more likely when the database is in the cloud. Der saveChangesError-Parameter ist FALSE, wenn OnGetAsync auf der Seite „Delete“ über die Benutzeroberfläche aufgerufen wird.The saveChangesError parameter is false when the Delete page OnGetAsync is called from the UI. Wenn OnGetAsync von OnPostAsync aufgerufen wird (aufgrund des fehlgeschlagenen Löschvorgangs), ist der Parameter saveChangesError „TRUE“.When OnGetAsync is called by OnPostAsync (because the delete operation failed), the saveChangesError parameter is true.

Die OnPostAsync-Methode ruft die ausgewählte Entität ab und anschließend die Methode Remove auf, um den Zustand der Entität auf Deleted festzulegen.The OnPostAsync method retrieves the selected entity, then calls the Remove method to set the entity's status to Deleted. Wenn SaveChanges aufgerufen wird, wird der SQL-Befehl DELETE generiert.When SaveChanges is called, a SQL DELETE command is generated. Wenn Remove fehlschlägt:If Remove fails:

  • Wird die Datenbankausnahme abgefangen.The database exception is caught.
  • Die OnGetAsync-Methode der Löschseite wird mit saveChangesError=true aufgerufen.The Delete page's OnGetAsync method is called with saveChangesError=true.

Fügen Sie der Razor Page „Delete“ (Pages/Students/Delete.cshtml) eine Fehlermeldung hinzu:Add an error message to the Delete Razor Page (Pages/Students/Delete.cshtml):

@page
@model ContosoUniversity.Pages.Students.DeleteModel

@{
    ViewData["Title"] = "Delete";
}

<h1>Delete</h1>

<p class="text-danger">@Model.ErrorMessage</p>

<h3>Are you sure you want to delete this?</h3>
<div>
    <h4>Student</h4>
    <hr />
    <dl class="row">
        <dt class="col-sm-2">
            @Html.DisplayNameFor(model => model.Student.LastName)
        </dt>
        <dd class="col-sm-10">
            @Html.DisplayFor(model => model.Student.LastName)
        </dd>
        <dt class="col-sm-2">
            @Html.DisplayNameFor(model => model.Student.FirstMidName)
        </dt>
        <dd class="col-sm-10">
            @Html.DisplayFor(model => model.Student.FirstMidName)
        </dd>
        <dt class="col-sm-2">
            @Html.DisplayNameFor(model => model.Student.EnrollmentDate)
        </dt>
        <dd class="col-sm-10">
            @Html.DisplayFor(model => model.Student.EnrollmentDate)
        </dd>
    </dl>

    <form method="post">
        <input type="hidden" asp-for="Student.ID" />
        <input type="submit" value="Delete" class="btn btn-danger" /> |
        <a asp-page="./Index">Back to List</a>
    </form>
</div>

Führen Sie die App aus, und löschen Sie einen Studenten, um die Seite „Delete“ zu testen.Run the app and delete a student to test the Delete page.

Nächste SchritteNext steps

In diesem Tutorial wird der erstellte CRUD-Code (CRUD = Create, Read, Update, Delete; Erstellen, Lesen, Aktualisieren, Löschen) überprüft und angepasst.In this tutorial, the scaffolded CRUD (create, read, update, delete) code is reviewed and customized.

Zur Minimierung der Komplexität und damit EF Core im Fokus dieser Tutorials bleibt, wird in den Seitenmodellen EF Core-Code verwendet.To minimize complexity and keep these tutorials focused on EF Core, EF Core code is used in the page models. Einige Entwickler verwenden eine Dienstschicht oder ein Repositorymuster, um eine Abstraktionsschicht zwischen der Benutzeroberfläche (Razor Pages) und der Datenzugriffsschicht zu erstellen.Some developers use a service layer or repository pattern in to create an abstraction layer between the UI (Razor Pages) and the data access layer.

In diesem Tutorial werden die Razor Pages „Create“ (Erstellen), „Edit“ (Bearbeiten), „Delete“ (Löschen) und „Details“ im Ordner Students erläutert.In this tutorial, the Create, Edit, Delete, and Details Razor Pages in the Students folder are examined.

Der erstellte Code verwendet folgendes Muster für die Seiten „Create“ (Erstellen), „Edit“ (Bearbeiten) und „Delete“ (Löschen):The scaffolded code uses the following pattern for Create, Edit, and Delete pages:

  • Abrufen und Anzeigen der angeforderten Daten mit der HTTP GET-Methode OnGetAsync.Get and display the requested data with the HTTP GET method OnGetAsync.
  • Speichern der an Daten vorgenommenen Änderungen mit der HTTP POST-Methode OnPostAsync.Save changes to the data with the HTTP POST method OnPostAsync.

Auf den Seiten „Index“ und „Details“ werden die angeforderten Daten mit der HTTP GET-Methode OnGetAsync abgerufen und angezeigt.The Index and Details pages get and display the requested data with the HTTP GET method OnGetAsync

„SingleOrDefaultAsync“ im Vergleich zu „FirstOrDefaultAsync“SingleOrDefaultAsync vs. FirstOrDefaultAsync

Der generierte Code verwendet FirstOrDefaultAsync. Im Allgemeinen wird dies SingleOrDefaultAsync vorgezogen.The generated code uses FirstOrDefaultAsync, which is generally preferred over SingleOrDefaultAsync.

FirstOrDefaultAsync ist beim Abrufen einer Entität effizienter als SingleOrDefaultAsync:FirstOrDefaultAsync is more efficient than SingleOrDefaultAsync at fetching one entity:

  • Sofern mit dem Code nicht überprüft werden muss, ob von der Abfrage mehrere Entitäten zurückgegeben wurden.Unless the code needs to verify that there's not more than one entity returned from the query.
  • SingleOrDefaultAsync ruft weitere Daten ab und führt unnötige Arbeiten aus.SingleOrDefaultAsync fetches more data and does unnecessary work.
  • SingleOrDefaultAsync löst eine Ausnahme aus, wenn mehrere Entitäten in den Filterabschnitt passen.SingleOrDefaultAsync throws an exception if there's more than one entity that fits the filter part.
  • FirstOrDefaultAsync löst keine Ausnahme aus, wenn mehrere Entitäten in den Filterabschnitt passen.FirstOrDefaultAsync doesn't throw if there's more than one entity that fits the filter part.

FindAsyncFindAsync

In einem Großteil des Codes kann FindAsync anstelle von FirstOrDefaultAsync verwendet werden.In much of the scaffolded code, FindAsync can be used in place of FirstOrDefaultAsync.

FindAsync:FindAsync:

  • Sucht eine Entität mit dem Primärschlüssel (PS).Finds an entity with the primary key (PK). Wenn eine Entität mit dem PS vom Kontext nachverfolgt wird, wird sie ohne eine Anforderung an die Datenbank zurückgegeben.If an entity with the PK is being tracked by the context, it's returned without a request to the DB.
  • Ist einfach und präzise.Is simple and concise.
  • Wurde für die Suche nach einer einzelnen Entität optimiert.Is optimized to look up a single entity.
  • Kann manchmal Leistungsvorteile haben, für normale Web-Apps ist dies jedoch selten der Fall.Can have perf benefits in some situations, but that rarely happens for typical web apps.
  • Verwendet implizit FirstAsync anstelle von SingleAsync.Implicitly uses FirstAsync instead of SingleAsync.

Wenn Sie jedoch weitere Entitäten mithilfe von Include einschließen möchten, ist FindAsync jedoch nicht mehr sinnvoll.But if you want to Include other entities, then FindAsync is no longer appropriate. Dies bedeutet, dass Sie FindAsync möglicherweise abbrechen und in eine Abfrage verschieben müssen, während Ihre App weiter ausgeführt wird.This means that you may need to abandon FindAsync and move to a query as your app progresses.

Anpassen der Seite „Details“Customize the Details page

Navigieren Sie zur Seite Pages/Students.Browse to Pages/Students page. Die Links Bearbeiten, Details und Löschen werden mithilfe des Anchor-Taghilfsprogramms in der Datei Pages/Movies/Index.cshtml generiert.The Edit, Details, and Delete links are generated by the Anchor Tag Helper in the Pages/Students/Index.cshtml file.

<td>
    <a asp-page="./Edit" asp-route-id="@item.ID">Edit</a> |
    <a asp-page="./Details" asp-route-id="@item.ID">Details</a> |
    <a asp-page="./Delete" asp-route-id="@item.ID">Delete</a>
</td>

Führen Sie die App aus, und klicken Sie auf den Link Details.Run the app and select a Details link. Die URL weist das Format http://localhost:5000/Students/Details?id=2 auf.The URL is of the form http://localhost:5000/Students/Details?id=2. Die Studenten-ID wird mit einer Abfragezeichenfolge (?id=2) übergeben.The Student ID is passed using a query string (?id=2).

Aktualisieren Sie die Razor Pages „Edit“ (Bearbeiten), „Details“ und „Delete“ (Löschen) so, dass die Routenvorlage "{id:int}" verwendet wird.Update the Edit, Details, and Delete Razor Pages to use the "{id:int}" route template. Ändern Sie die „page“-Direktive für jede dieser Seiten aus @page in @page "{id:int}".Change the page directive for each of these pages from @page to @page "{id:int}".

Eine Anforderung an die Seite mit der Routenvorlage „{id:int}“, die nicht den Integer enthält, gibt den HTTP-Fehler „404 – Nicht gefunden“ zurück.A request to the page with the "{id:int}" route template that does not include a integer route value returns an HTTP 404 (not found) error. http://localhost:5000/Students/Details gibt beispielsweise den Fehler 404 zurück.For example, http://localhost:5000/Students/Details returns a 404 error. Um die ID optional zu machen, fügen Sie ? an die Routeneinschränkung an:To make the ID optional, append ? to the route constraint:

@page "{id:int?}"

Führen Sie die App aus, klicken Sie auf einen Details-Link, und überprüfen Sie, ob die ID in der URL in Form von Routendaten übergeben wird (http://localhost:5000/Students/Details/2).Run the app, click on a Details link, and verify the URL is passing the ID as route data (http://localhost:5000/Students/Details/2).

Ändern Sie @page nicht global in @page "{id:int}". Dadurch werden die Links zu den Seiten „Home“ und „Create“ (Erstellen) getrennt.Don't globally change @page to @page "{id:int}", doing so breaks the links to the Home and Create pages.

Der für die Indexseite „Students“ erstellte Code umfasst nicht die Eigenschaft Enrollments.The scaffolded code for the Students Index page doesn't include the Enrollments property. In diesem Abschnitt werden die Inhalte der Enrollments-Auflistung auf der Seite „Details“ angezeigt.In this section, the contents of the Enrollments collection is displayed in the Details page.

Die Methode OnGetAsync der Datei Pages/Students/Details.cshtml.cs verwendet die Methode FirstOrDefaultAsync zum Abrufen einer einzelnen Student-Entität.The OnGetAsync method of Pages/Students/Details.cshtml.cs uses the FirstOrDefaultAsync method to retrieve a single Student entity. Fügen Sie folgenden hervorgehobenen Code hinzu:Add the following highlighted code:

public async Task<IActionResult> OnGetAsync(int? id)
{
    if (id == null)
    {
        return NotFound();
    }

    Student = await _context.Student
                        .Include(s => s.Enrollments)
                            .ThenInclude(e => e.Course)
                        .AsNoTracking()
                        .FirstOrDefaultAsync(m => m.ID == id);

    if (Student == null)
    {
        return NotFound();
    }
    return Page();
}

Aufgrund der Methoden Include und ThenInclude lädt der Kontext die Navigationseigenschaft Student.Enrollments und in jeder Registrierung die Navigationseigenschaft Enrollment.Course.The Include and ThenInclude methods cause the context to load the Student.Enrollments navigation property, and within each enrollment the Enrollment.Course navigation property. Diese Methoden werden im lesebezogenen Datentutorial ausführlich überprüft.These methods are examined in detail in the reading-related data tutorial.

Die Methode AsNoTracking verbessert die Leistung in Szenarios, wenn die zurückgegebenen Entitäten nicht im aktuellen Kontext aktualisiert werden.The AsNoTracking method improves performance in scenarios when the entities returned are not updated in the current context. AsNoTracking wird später in diesem Tutorial behandelt.AsNoTracking is discussed later in this tutorial.

Öffnen Sie die Datei Pages/Students/Details.cshtml.Open Pages/Students/Details.cshtml. Fügen Sie folgenden hervorgehobenen Code hinzu, um eine Liste mit Registrierungen anzuzeigen:Add the following highlighted code to display a list of enrollments:

@page "{id:int}"
@model ContosoUniversity.Pages.Students.DetailsModel

@{
    ViewData["Title"] = "Details";
}

<h2>Details</h2>

<div>
    <h4>Student</h4>
    <hr />
    <dl class="dl-horizontal">
        <dt>
            @Html.DisplayNameFor(model => model.Student.LastName)
        </dt>
        <dd>
            @Html.DisplayFor(model => model.Student.LastName)
        </dd>
        <dt>
            @Html.DisplayNameFor(model => model.Student.FirstMidName)
        </dt>
        <dd>
            @Html.DisplayFor(model => model.Student.FirstMidName)
        </dd>
        <dt>
            @Html.DisplayNameFor(model => model.Student.EnrollmentDate)
        </dt>
        <dd>
            @Html.DisplayFor(model => model.Student.EnrollmentDate)
        </dd>
        <dt>
            @Html.DisplayNameFor(model => model.Student.Enrollments)
        </dt>
        <dd>
            <table class="table">
                <tr>
                    <th>Course Title</th>
                    <th>Grade</th>
                </tr>
                @foreach (var item in Model.Student.Enrollments)
                {
                    <tr>
                        <td>
                            @Html.DisplayFor(modelItem => item.Course.Title)
                        </td>
                        <td>
                            @Html.DisplayFor(modelItem => item.Grade)
                        </td>
                    </tr>
                }
            </table>
        </dd>
    </dl>
</div>
<div>
    <a asp-page="./Edit" asp-route-id="@Model.Student.ID">Edit</a> |
    <a asp-page="./Index">Back to List</a>
</div>

Wenn der Codeeinzug nach dem Einfügen des Codes falsch ist, drücken Sie Strg+K+D, um diesen zu korrigieren.If code indentation is wrong after the code is pasted, press CTRL-K-D to correct it.

Der vorangehende Code durchläuft die Entitäten in der Navigationseigenschaft Enrollments.The preceding code loops through the entities in the Enrollments navigation property. Für jede Registrierung werden der Kurstitel und die Klasse angezeigt.For each enrollment, it displays the course title and the grade. Der Kurstitel wird von der Entität „Course“ abgerufen, die in der Navigationseigenschaft Course der Entität „Enrollments“ gespeichert ist.The course title is retrieved from the Course entity that's stored in the Course navigation property of the Enrollments entity.

Führen Sie die App aus, wählen Sie die Registerkarte Studenten aus, und klicken Sie bei einem Studenten auf den Link Details.Run the app, select the Students tab, and click the Details link for a student. Die Liste der Kurse und Klassen für den ausgewählten Studenten wird angezeigt.The list of courses and grades for the selected student is displayed.

Aktualisieren der Seite „Erstellen“Update the Create page

Aktualisieren Sie die Methode OnPostAsync in der Datei Pages/Students/Create.cshtml.cs mit dem folgenden Code:Update the OnPostAsync method in Pages/Students/Create.cshtml.cs with the following code:

public async Task<IActionResult> OnPostAsync()
{
    if (!ModelState.IsValid)
    {
        return Page();
    }

    var emptyStudent = new Student();

    if (await TryUpdateModelAsync<Student>(
        emptyStudent,
        "student",   // Prefix for form value.
        s => s.FirstMidName, s => s.LastName, s => s.EnrollmentDate))
    {
        _context.Student.Add(emptyStudent);
        await _context.SaveChangesAsync();
        return RedirectToPage("./Index");
    }

    return null;
}

TryUpdateModelAsyncTryUpdateModelAsync

Überprüfen Sie den TryUpdateModelAsync-Code:Examine the TryUpdateModelAsync code:


var emptyStudent = new Student();

if (await TryUpdateModelAsync<Student>(
    emptyStudent,
    "student",   // Prefix for form value.
    s => s.FirstMidName, s => s.LastName, s => s.EnrollmentDate))
{

Im vorangehenden Code versucht TryUpdateModelAsync<Student>, das Objekt emptyStudent mithilfe der bereitgestellten Formularwerte aus der Eigenschaft PageContext im PageModel zu aktualisieren.In the preceding code, TryUpdateModelAsync<Student> tries to update the emptyStudent object using the posted form values from the PageContext property in the PageModel. TryUpdateModelAsync aktualisiert nur die aufgeführten Eigenschaften (s => s.FirstMidName, s => s.LastName, s => s.EnrollmentDate).TryUpdateModelAsync only updates the properties listed (s => s.FirstMidName, s => s.LastName, s => s.EnrollmentDate).

Im vorgehenden Beispiel:In the preceding sample:

  • Ist das zweite Argument ("student", // Prefix) das Präfix, das für die Suche nach Werten verwendet wird.The second argument ("student", // Prefix) is the prefix uses to look up values. Die Groß-/Kleinschreibung wird hier nicht beachtet.It's not case sensitive.
  • Werden die bereitgestellten Formularwerte mithilfe der Modellbindung in die Typen im Student-Modell konvertiert.The posted form values are converted to the types in the Student model using model binding.

OverpostingOverposting

Die Verwendung von TryUpdateModel zur Aktualisierung von Feldern mit bereitgestellten Werten ist eine bewährte Sicherheitsmethode, da Overposting verhindert wird.Using TryUpdateModel to update fields with posted values is a security best practice because it prevents overposting. Angenommen, die Student-Entität enthält die Eigenschaft Secret, die auf dieser Webseite nicht aktualisiert oder hinzugefügt werden sollte:For example, suppose the Student entity includes a Secret property that this web page shouldn't update or add:

public class Student
{
    public int ID { get; set; }
    public string LastName { get; set; }
    public string FirstMidName { get; set; }
    public DateTime EnrollmentDate { get; set; }
    public string Secret { get; set; }
}

Selbst wenn die App auf der Razor Page „Create“ oder „Update“ (Erstellen oder Aktualisieren) nicht über das Feld Secret verfügt, könnte ein Hacker den Secret-Wert durch Overposting festlegen.Even if the app doesn't have a Secret field on the create/update Razor Page, a hacker could set the Secret value by overposting. Ein Hacker könnte ein Tool wie Fiddler verwenden oder JavaScript-Code schreiben, um einen Secret-Formularwert bereitzustellen.A hacker could use a tool such as Fiddler, or write some JavaScript, to post a Secret form value. Die Felder, die von der Modellbindung bei der Erstellung einer Student-Instanz verwendet werden, werden nicht durch den ursprünglichen Code begrenzt.The original code doesn't limit the fields that the model binder uses when it creates a Student instance.

Jeder beliebige, vom Hacker in den Secret-Formularfeldern angegebene Wert wird in der Datenbank aktualisiert.Whatever value the hacker specified for the Secret form field is updated in the DB. Die folgende Abbildung zeigt das Fiddler-Tool beim Hinzufügen des Felds Secret (mit dem Wert „OverPost“) zu den bereitgestellten Formularwerten.The following image shows the Fiddler tool adding the Secret field (with the value "OverPost") to the posted form values.

Fiddler beim Hinzufügen des Felds „Secret“

Der Wert „OverPost“ wurde erfolgreich zur Eigenschaft Secret der eingefügten Zeile hinzugefügt.The value "OverPost" is successfully added to the Secret property of the inserted row. Der App-Designer hatte nie die Absicht, die Eigenschaft Secret auf der Seite „Erstellen“ festzulegen.The app designer never intended the Secret property to be set with the Create page.

ViewModel-ElementView model

Ein Ansichtsmodell enthält in der Regel eine Teilmenge der im Modell enthaltenen Eigenschaften, die von der App verwendet werden.A view model typically contains a subset of the properties included in the model used by the application. Das Anwendungsmodell wird häufig als Domänenmodell bezeichnet.The application model is often called the domain model. Das Domänenmodell enthält normalerweise alle Eigenschaften, die die entsprechende Entität in der Datenbank erfordert.The domain model typically contains all the properties required by the corresponding entity in the DB. Das Ansichtsmodell enthält nur die Eigenschaften, die für die Benutzeroberflächenebene (z.B. die Seite „Erstellen“) erforderlich sind.The view model contains only the properties needed for the UI layer (for example, the Create page). Neben dem Ansichtsmodell verwenden einige Apps ein Bindungsmodell oder Eingabemodell für die Bereitstellung von Daten zwischen der Seitenmodellklasse auf den Razor Pages und dem Browser.In addition to the view model, some apps use a binding model or input model to pass data between the Razor Pages page model class and the browser. Betrachten Sie das folgende Student-Ansichtsmodell:Consider the following Student view model:

using System;

namespace ContosoUniversity.Models
{
    public class StudentVM
    {
        public int ID { get; set; }
        public string LastName { get; set; }
        public string FirstMidName { get; set; }
        public DateTime EnrollmentDate { get; set; }
    }
}

Ansichtsmodelle bieten eine alternative Möglichkeit zum Verhindern von Overposting.View models provide an alternative way to prevent overposting. Das Ansichtsmodell enthält nur die Eigenschaften, die angezeigt oder aktualisiert werden sollen.The view model contains only the properties to view (display) or update.

Im folgenden Code wird das StudentVM-Ansichtsmodell für die Erstellung eines neuen Studenten verwendet:The following code uses the StudentVM view model to create a new student:

[BindProperty]
public StudentVM StudentVM { get; set; }

public async Task<IActionResult> OnPostAsync()
{
    if (!ModelState.IsValid)
    {
        return Page();
    }

    var entry = _context.Add(new Student());
    entry.CurrentValues.SetValues(StudentVM);
    await _context.SaveChangesAsync();
    return RedirectToPage("./Index");
}

Die Methode SetValues legt die Werte dieses Objekts fest, indem sie Werte aus einem anderen PropertyValues-Objekt liest.The SetValues method sets the values of this object by reading values from another PropertyValues object. SetValues verwendet die Übereinstimmung von Eigenschaftsnamen.SetValues uses property name matching. Der Ansichtsmodelltyp muss nicht mit dem Modelltyp verknüpft sein. Er muss lediglich über übereinstimmende Eigenschaften verfügen.The view model type doesn't need to be related to the model type, it just needs to have properties that match.

Für die Verwendung von StudentVM muss CreateVM.cshtml aktualisiert werden, damit StudentVM statt Student verwendet wird.Using StudentVM requires CreateVM.cshtml be updated to use StudentVM rather than Student.

Auf Razor Pages stellt die abgeleitete PageModel-Klasse das Ansichtsmodell dar.In Razor Pages, the PageModel derived class is the view model.

Aktualisieren der Seite „Bearbeiten“Update the Edit page

Aktualisieren Sie das Seitenmodell für die Seite „Bearbeiten“:Update the page model for the Edit page. Die wichtigsten Änderungen werden hervorgehoben.The major changes are highlighted:

public class EditModel : PageModel
{
    private readonly SchoolContext _context;

    public EditModel(SchoolContext context)
    {
        _context = context;
    }

    [BindProperty]
    public Student Student { get; set; }

    public async Task<IActionResult> OnGetAsync(int? id)
    {
        if (id == null)
        {
            return NotFound();
        }

        Student = await _context.Student.FindAsync(id);

        if (Student == null)
        {
            return NotFound();
        }
        return Page();
    }

    public async Task<IActionResult> OnPostAsync(int? id)
    {
        if (!ModelState.IsValid)
        {
            return Page();
        }

        var studentToUpdate = await _context.Student.FindAsync(id);

        if (await TryUpdateModelAsync<Student>(
            studentToUpdate,
            "student",
            s => s.FirstMidName, s => s.LastName, s => s.EnrollmentDate))
        {
            await _context.SaveChangesAsync();
            return RedirectToPage("./Index");
        }

        return Page();
    }
}

Die Codeänderungen sind bis auf wenige Ausnahmen mit denen auf der Seite „Erstellen“ vergleichbar:The code changes are similar to the Create page with a few exceptions:

  • OnPostAsync weist den optionalen Parameter id auf.OnPostAsync has an optional id parameter.
  • Anstatt einen leeren Studenten zu erstellen, wird der aktuelle Student aus der Datenbank abgerufen.The current student is fetched from the DB, rather than creating an empty student.
  • FirstOrDefaultAsync wurde durch FindAsync ersetzt.FirstOrDefaultAsync has been replaced with FindAsync. FindAsync eignet sich für die Auswahl einer Entität über den Primärschlüssel.FindAsync is a good choice when selecting an entity from the primary key. Weitere Informationen hierzu finden Sie unter FindAsync.See FindAsync for more information.

Testen der Seiten „Bearbeiten“ und „Erstellen“Test the Edit and Create pages

Erstellen und bearbeiten Sie einige Student-Entitäten.Create and edit a few student entities.

EntitätsstatusEntity States

Im Datenbankkontext wird nachverfolgt, ob Entitäten im Arbeitsspeicher mit den zugehörigen Zeilen in der Datenbank synchron sind.The DB context keeps track of whether entities in memory are in sync with their corresponding rows in the DB. Die Informationen zur Datenbanksynchronisierung bestimmen, was geschieht, wenn SaveChangesAsync aufgerufen wird.The DB context sync information determines what happens when SaveChangesAsync is called. Wenn beispielsweise eine neue Entität an die Methode AddAsync übergeben wird, wird der Zustand dieser Entität auf Added festgelegt.For example, when a new entity is passed to the AddAsync method, that entity's state is set to Added. Wenn SaveChangesAsync aufgerufen wird, gibt der Datenbankkontext den SQL-Befehl INSERT aus.When SaveChangesAsync is called, the DB context issues a SQL INSERT command.

Eine Entität kann einen der folgenden Status aufweisen:An entity may be in one of the following states:

  • Added: Die Entität ist noch nicht in der Datenbank vorhanden.Added: The entity doesn't yet exist in the DB. Die Methode SaveChanges gibt eine INSERT-Anweisung aus.The SaveChanges method issues an INSERT statement.

  • Unchanged: Für diese Entität müssen keine Änderungen gespeichert werden.Unchanged: No changes need to be saved with this entity. Eine Entität weist diesen Zustand auf, wenn sie von der Datenbank gelesen wird.An entity has this status when it's read from the DB.

  • Modified: Einige oder alle Eigenschaftswerte der Entität wurden geändert.Modified: Some or all of the entity's property values have been modified. Die Methode SaveChanges gibt eine UPDATE-Anweisung aus.The SaveChanges method issues an UPDATE statement.

  • Deleted: Die Entität wurde zum Löschen markiert.Deleted: The entity has been marked for deletion. Die Methode SaveChanges gibt eine DELETE-Anweisung aus.The SaveChanges method issues a DELETE statement.

  • Detached: Die Entität wird nicht vom Datenbankkontext nachverfolgt.Detached: The entity isn't being tracked by the DB context.

Zustandsänderungen werden in einer Desktop-App in der Regel automatisch festgelegt.In a desktop app, state changes are typically set automatically. Eine Entität wird gelesen, Änderungen werden vorgenommen, und der Entitätszustand wird automatisch in Modified geändert.An entity is read, changes are made, and the entity state to automatically be changed to Modified. Durch das Aufrufen von SaveChanges wird eine SQL UPDATE-Anweisung generiert, die nur die geänderten Eigenschaften aktualisiert.Calling SaveChanges generates a SQL UPDATE statement that updates only the changed properties.

In einer Web-App wird der DbContextverfügbar gemacht, der eine Entität liest und die Daten anzeigt, nachdem eine Seite gerendert wurde.In a web app, the DbContext that reads an entity and displays the data is disposed after a page is rendered. Wenn die Methode OnPostAsync einer Seite aufgerufen wird, wird eine neue Webanforderung mit einer neuen Instanz von DbContext gestellt.When a page's OnPostAsync method is called, a new web request is made and with a new instance of the DbContext. Durch erneutes Lesen der Entität in diesem neuen Kontext wird die Desktopverarbeitung simuliert.Re-reading the entity in that new context simulates desktop processing.

Aktualisieren der Seite „Delete“ (Löschen)Update the Delete page

In diesem Abschnitt wird Code hinzugefügt, um eine benutzerdefinierte Fehlermeldung zu implementieren, wenn der Aufruf von SaveChanges fehlschlägt.In this section, code is added to implement a custom error message when the call to SaveChanges fails. Fügen Sie eine Zeichenfolge hinzu, die mögliche Fehlermeldungen enthalten soll:Add a string to contain possible error messages:

public class DeleteModel : PageModel
{
    private readonly SchoolContext _context;

    public DeleteModel(SchoolContext context)
    {
        _context = context;
    }

    [BindProperty]
    public Student Student { get; set; }
    public string ErrorMessage { get; set; }

Ersetzen Sie die OnGetAsync-Methode durch folgenden Code:Replace the OnGetAsync method with the following code:

public async Task<IActionResult> OnGetAsync(int? id, bool? saveChangesError = false)
{
    if (id == null)
    {
        return NotFound();
    }

    Student = await _context.Student
        .AsNoTracking()
        .FirstOrDefaultAsync(m => m.ID == id);

    if (Student == null)
    {
        return NotFound();
    }

    if (saveChangesError.GetValueOrDefault())
    {
        ErrorMessage = "Delete failed. Try again";
    }

    return Page();
}

Der vorangehende Code enthält den optionalen Parameter saveChangesError.The preceding code contains the optional parameter saveChangesError. saveChangesError gibt an, ob die Methode nach einem Fehler beim Löschen des Studentenobjekts aufgerufen wurde.saveChangesError indicates whether the method was called after a failure to delete the student object. Der Löschvorgang schlägt möglicherweise aufgrund von vorübergehenden Netzwerkproblemen fehl.The delete operation might fail because of transient network problems. Vorübergehende Netzwerkfehler treten eher in der Cloud auf.Transient network errors are more likely in the cloud. saveChangesError ist „FALSE“, wenn die Methode OnGetAsync auf der Seite „Löschen“ über die Benutzeroberfläche aufgerufen wird.saveChangesErroris false when the Delete page OnGetAsync is called from the UI. Wenn OnGetAsync von OnPostAsync aufgerufen wird (aufgrund des fehlgeschlagenen Löschvorgangs), ist der Parameter saveChangesError „TRUE“.When OnGetAsync is called by OnPostAsync (because the delete operation failed), the saveChangesError parameter is true.

Die Methode „OnPostAsync“ auf „Löschen“-SeitenThe Delete pages OnPostAsync method

Ersetzen Sie OnPostAsync durch den folgenden Code:Replace the OnPostAsync with the following code:

public async Task<IActionResult> OnPostAsync(int? id)
{
    if (id == null)
    {
        return NotFound();
    }

    var student = await _context.Student
                    .AsNoTracking()
                    .FirstOrDefaultAsync(m => m.ID == id);

    if (student == null)
    {
        return NotFound();
    }

    try
    {
        _context.Student.Remove(student);
        await _context.SaveChangesAsync();
        return RedirectToPage("./Index");
    }
    catch (DbUpdateException /* ex */)
    {
        //Log the error (uncomment ex variable name and write a log.)
        return RedirectToAction("./Delete",
                             new { id, saveChangesError = true });
    }
}

Der vorangehende Code ruft die ausgewählte Entität ab und anschließend die Methode Remove auf, um den Zustand der Entität auf Deleted festzulegen.The preceding code retrieves the selected entity, then calls the Remove method to set the entity's status to Deleted. Wenn SaveChanges aufgerufen wird, wird der SQL-Befehl DELETE generiert.When SaveChanges is called, a SQL DELETE command is generated. Wenn Remove fehlschlägt:If Remove fails:

  • Wird die Datenbankausnahme abgefangen.The DB exception is caught.
  • Wird die Methode OnGetAsync auf der Seite „Löschen“ mit saveChangesError=true aufgerufen.The Delete pages OnGetAsync method is called with saveChangesError=true.

Aktualisieren der Razor Page „Delete“ (Löschen)Update the Delete Razor Page

Fügen Sie folgende hervorgehobene Fehlermeldung zur Razor Page „Löschen“ hinzu.Add the following highlighted error message to the Delete Razor Page.

@page "{id:int}"
@model ContosoUniversity.Pages.Students.DeleteModel

@{
    ViewData["Title"] = "Delete";
}

<h2>Delete</h2>

<p class="text-danger">@Model.ErrorMessage</p>

<h3>Are you sure you want to delete this?</h3>
<div>

Testen Sie die Seite „Löschen“.Test Delete.

Häufige FehlerCommon errors

„Students/Index“ oder andere Links funktionieren nicht:Students/Index or other links don't work:

Überprüfen Sie, ob die Razor Page die richtige @page-Anweisung enthält.Verify the Razor Page contains the correct @page directive. Die Razor Page „Students/Index“ sollte beispielsweise keine Routenvorlage enthalten:For example, The Students/Index Razor Page should not contain a route template:

@page "{id:int}"

Die @page-Anweisung muss auf jeder Razor Page vorhanden sein.Each Razor Page must include the @page directive.

Zusätzliche RessourcenAdditional resources