Freigeben über


Implementieren der grundlegenden CRUD-Funktionalität mit dem Entity Framework in ASP.NET MVC-Anwendung (2 von 10)

Von Tom Dykstra

Die Contoso University-Beispielwebanwendung veranschaulicht, wie ASP.NET MVC 4-Anwendungen mithilfe von Entity Framework 5 Code First und Visual Studio 2012 erstellt werden. Informationen zu dieser Tutorialreihe finden Sie im ersten Tutorial der Reihe.

Hinweis

Wenn ein Problem auftritt, das Sie nicht beheben können, laden Sie das fertige Kapitel herunter, und versuchen Sie, Ihr Problem zu reproduzieren. In der Regel finden Sie die Lösung für das Problem, indem Sie Ihren Code mit dem fertigen Code vergleichen. Einige häufige Fehler und deren Lösung finden Sie unter "Fehler und Problemumgehungen".

Im vorherigen Lernprogramm haben Sie eine MVC-Anwendung erstellt, die Daten mithilfe von Entity Framework und SQL Server LocalDB speichert und anzeigt. In diesem Lernprogramm überprüfen und anpassen Sie den CRUD-Code (Erstellen, Lesen, Aktualisieren, Löschen), den das MVC-Gerüst automatisch für Sie in Controllern und Ansichten erstellt.

Hinweis

Es ist üblich, dass das Repositorymuster implementiert wird, um eine Abstraktionsebene zwischen Ihrem Controller und der Datenzugriffsebene zu erstellen. Um diese Lernprogramme einfach zu halten, implementieren Sie ein Repository erst nach einem späteren Lernprogramm in dieser Reihe.

In diesem Lernprogramm erstellen Sie die folgenden Webseiten:

Screenshot der Seite

Screenshot der Seite

Screenshot der Seite

Screenshot der Seite

Erstellen einer Detailseite

Der Gerüstcode für die Seite "Kursteilnehmer Index " hat die Enrollments Eigenschaft ausgelassen, da diese Eigenschaft eine Auflistung enthält. Auf der Details Seite zeigen Sie den Inhalt der Auflistung in einer HTML-Tabelle an.

In Controllers\StudentController.cs verwendet die Aktionsmethode für die Details Ansicht die Find Methode, um eine einzelne Student Entität abzurufen.

public ActionResult Details(int id = 0)
{
    Student student = db.Students.Find(id);
    if (student == null)
    {
        return HttpNotFound();
    }
    return View(student);
}

Der Schlüsselwert wird als id Parameter an die Methode übergeben und stammt aus Routendaten im Link "Details " auf der Indexseite.

  1. Öffnen Sie "Views\Student\Details.cshtml". Jedes Feld wird mithilfe eines DisplayFor Hilfsfelds angezeigt, wie im folgenden Beispiel gezeigt:

    <div class="display-label">
             @Html.DisplayNameFor(model => model.LastName)
        </div>
        <div class="display-field">
            @Html.DisplayFor(model => model.LastName)
        </div>
    
  2. Fügen Sie nach dem EnrollmentDate Feld und unmittelbar vor dem schließenden fieldset Tag Code hinzu, um eine Liste der Registrierungen anzuzeigen, wie im folgenden Beispiel gezeigt:

    <div class="display-label">
            @Html.LabelFor(model => model.Enrollments)
        </div>
        <div class="display-field">
            <table>
                <tr>
                    <th>Course Title</th>
                    <th>Grade</th>
                </tr>
                @foreach (var item in Model.Enrollments)
                {
                    <tr>
                        <td>
                            @Html.DisplayFor(modelItem => item.Course.Title)
                        </td>
                        <td>
                            @Html.DisplayFor(modelItem => item.Grade)
                        </td>
                    </tr>
                }
            </table>
        </div>
    </fieldset>
    <p>
        @Html.ActionLink("Edit", "Edit", new { id=Model.StudentID }) |
        @Html.ActionLink("Back to List", "Index")
    </p>
    

    Dieser Code durchläuft die Entitäten in der Navigationseigenschaft Enrollments. Für jede Enrollment Entität in der Eigenschaft werden der Kurstitel und die Noten angezeigt. Der Kurstitel wird von der Course Entität abgerufen, die in der Course Navigationseigenschaft der Enrollments Entität gespeichert ist. Alle diese Daten werden automatisch aus der Datenbank abgerufen, wenn sie benötigt werden. (Mit anderen Worten, Sie verwenden faules Laden hier. Sie haben das Laden der Courses Navigationseigenschaft nicht festgelegt. Wenn Sie also zum ersten Mal versuchen, auf diese Eigenschaft zuzugreifen, wird eine Abfrage an die Datenbank gesendet, um die Daten abzurufen. Weitere Informationen zum faulen Laden und eifrigen Laden finden Sie im Lernprogramm "Lesen verwandter Daten" weiter unten in dieser Reihe.)

  3. Führen Sie die Seite aus, indem Sie die Registerkarte "Kursteilnehmer " auswählen und auf einen Link "Details " für Alexander Carson klicken. Die Liste der Kurse und Klassen für den ausgewählten Studenten wird angezeigt:

    Student_Details_page

Aktualisieren der Seite "Erstellen"

  1. Ersetzen Sie in Controller\StudentController.cs die HttpPost``Create Aktionsmethode durch den folgenden Code, um einen try-catch Block und das Bind-Attribut zur Gerüstmethode hinzuzufügen:

    [HttpPost]
    [ValidateAntiForgeryToken]
    public ActionResult Create(
       [Bind(Include = "LastName, FirstMidName, EnrollmentDate")]
       Student student)
    {
       try
       {
          if (ModelState.IsValid)
          {
             db.Students.Add(student);
             db.SaveChanges();
             return RedirectToAction("Index");
          }
       }
       catch (DataException /* dex */)
       {
          //Log the error (uncomment dex variable name after DataException and add a line here to write a log.
          ModelState.AddModelError("", "Unable to save changes. Try again, and if the problem persists see your system administrator.");
       }
       return View(student);
    }
    

    Dieser Code fügt die Student entität, die vom ASP.NET MVC-Modellbinder erstellt wurde, zum Students Entitätssatz hinzu und speichert dann die Änderungen in der Datenbank. (Modellordner bezieht sich auf die ASP.NET MVC-Funktionalität, die es Ihnen erleichtert, mit daten zu arbeiten, die von einem Formular übermittelt werden. Ein Modellordner konvertiert gepostete Formularwerte in CLR-Typen und übergibt sie an die Aktionsmethode in Parameter. In diesem Fall instanziiert der Modellordner eine Student Entität für Sie mithilfe von Eigenschaftswerten aus der Form Auflistung.)

    Das ValidateAntiForgeryToken Attribut trägt dazu bei, websiteübergreifende Anforderungsverfälschungsangriffe zu verhindern.

> [!WARNING]
    > Security - The `Bind` attribute is added to protect against *over-posting*. For example, suppose the `Student` entity includes a `Secret` property that you don't want this web page to update.
    > 
    > [!code-csharp[Main](implementing-basic-crud-functionality-with-the-entity-framework-in-asp-net-mvc-application/samples/sample5.cs?highlight=7)]
    > 
    > Even if you don't have a `Secret` field on the web page, a hacker could use a tool such as [fiddler](http://fiddler2.com/home), or write some JavaScript, to post a `Secret` form value. Without the [Bind](https://msdn.microsoft.com/library/system.web.mvc.bindattribute(v=vs.108).aspx) attribute limiting the fields that the model binder uses when it creates a `Student` instance*,* the model binder would pick up that `Secret` form value and use it to update the `Student` entity instance. Then whatever value the hacker specified for the `Secret` form field would be updated in your database. The following image shows the fiddler tool adding the `Secret` field (with the value "OverPost") to the posted form values.
    > 
    > ![](implementing-basic-crud-functionality-with-the-entity-framework-in-asp-net-mvc-application/_static/image6.png)  
    > 
    > The value "OverPost" would then be successfully added to the `Secret` property of the inserted row, although you never intended that the web page be able to update that property.
    > 
    > It's a security best practice to use the `Include` parameter with the `Bind` attribute to *allowed attributes* fields. It's also possible to use the `Exclude` parameter to *blocked attributes* fields you want to exclude. The reason `Include` is more secure is that when you add a new property to the entity, the new field is not automatically protected by an `Exclude` list.
    > 
    > Another alternative approach, and one preferred by many, is to use only view models with model binding. The view model contains only the properties you want to bind. Once the MVC model binder has finished, you copy the view model properties to the entity instance.

    Other than the `Bind` attribute, the `try-catch` block is the only change you've made to the scaffolded code. If an exception that derives from [DataException](https://msdn.microsoft.com/library/system.data.dataexception.aspx) is caught while the changes are being saved, a generic error message is displayed. [DataException](https://msdn.microsoft.com/library/system.data.dataexception.aspx) exceptions are sometimes caused by something external to the application rather than a programming error, so the user is advised to try again. Although not implemented in this sample, a production quality application would log the exception (and non-null inner exceptions ) with a logging mechanism such as [ELMAH](https://code.google.com/p/elmah/).

    The code in *Views\Student\Create.cshtml* is similar to what you saw in *Details.cshtml*, except that `EditorFor` and `ValidationMessageFor` helpers are used for each field instead of `DisplayFor`. The following example shows the relevant code:

    [!code-cshtml[Main](implementing-basic-crud-functionality-with-the-entity-framework-in-asp-net-mvc-application/samples/sample6.cshtml)]

    *Create.cshtml* also includes `@Html.AntiForgeryToken()`, which works with the `ValidateAntiForgeryToken` attribute in the controller to help prevent [cross-site request forgery](../../security/xsrfcsrf-prevention-in-aspnet-mvc-and-web-pages.md) attacks.

    No changes are required in *Create.cshtml*.
  1. Führen Sie die Seite aus, indem Sie die Registerkarte "Kursteilnehmer" auswählen und auf "Neu erstellen" klicken.

    Student_Create_page

    Einige Datenüberprüfungen funktionieren standardmäßig. Geben Sie Namen und ein ungültiges Datum ein, und klicken Sie auf " Erstellen ", um die Fehlermeldung anzuzeigen.

    Students_Create_page_error_message

    Der folgende hervorgehobene Code zeigt die Modellüberprüfung.

    [HttpPost]
    [ValidateAntiForgeryToken]
    public ActionResult Create(Student student)
    {
        if (ModelState.IsValid)
        {
            db.Students.Add(student);
            db.SaveChanges();
            return RedirectToAction("Index");
        }
    
        return View(student);
    }
    

    Ändern Sie das Datum in einen gültigen Wert wie 1.09.2005, und klicken Sie auf " Erstellen ", um anzuzeigen, dass der neue Kursteilnehmer auf der Indexseite angezeigt wird.

    Students_Index_page_with_new_student

Aktualisieren der POST-Seite bearbeiten

In Controllers\StudentController.cs verwendet die HttpGet Edit Methode (die methode ohne das HttpPost Attribut) die Find Methode, um die ausgewählte Student Entität abzurufen, wie Sie in der Details Methode gesehen haben. Sie müssen diese Methode nicht ändern.

Ersetzen Sie die HttpPost Edit Aktionsmethode jedoch durch den folgenden Code, um einen try-catch Block und das Bind-Attribut hinzuzufügen:

[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Edit(
   [Bind(Include = "StudentID, LastName, FirstMidName, EnrollmentDate")]
   Student student)
{
   try
   {
      if (ModelState.IsValid)
      {
         db.Entry(student).State = EntityState.Modified;
         db.SaveChanges();
         return RedirectToAction("Index");
      }
   }
   catch (DataException /* dex */)
   {
      //Log the error (uncomment dex variable name after DataException and add a line here to write a log.
      ModelState.AddModelError("", "Unable to save changes. Try again, and if the problem persists see your system administrator.");
   }
   return View(student);
}

Dieser Code ähnelt dem, was Sie in der HttpPost Create Methode gesehen haben. Anstatt jedoch die vom Modellordner erstellte Entität zum Entitätssatz hinzuzufügen, legt dieser Code eine Kennzeichnung für die Entität fest, die angibt, dass sie geändert wurde. Wenn die SaveChanges-Methode aufgerufen wird, bewirkt das Modified-Flag , dass das Entity Framework SQL-Anweisungen erstellt, um die Datenbankzeile zu aktualisieren. Alle Spalten der Datenbankzeile werden aktualisiert, einschließlich der Spalten, die der Benutzer nicht geändert hat, und Parallelitätskonflikte werden ignoriert. (Sie erfahren, wie Sie die Parallelität in einem späteren Lernprogramm in dieser Reihe behandeln.)

Entitätszustände und die Methoden "Attach" und "SaveChanges"

Der Datenbankkontext verfolgt, ob die Entitäten im Arbeitsspeicher mit ihren entsprechenden Zeilen in der Datenbank synchronisiert sind. Diese Information bestimmt, was passiert, wenn Sie die Methode SaveChanges aufrufen. Wenn Sie beispielsweise eine neue Entität an die Add-Methode übergeben, wird der Status dieser Entität auf " Added. Wenn Sie dann die SaveChanges-Methode aufrufen, gibt der Datenbankkontext einen SQL-Befehl INSERT aus.

Eine Entität kann sich in einem derfolgenden Zustände befinden:

  • Added. Die Entität ist in der Datenbank noch nicht vorhanden. Die SaveChanges Methode muss eine INSERT Anweisung ausgeben.
  • Unchanged. Die Methode SaveChanges muss nichts mit dieser Entität tun. Wenn Sie eine Entität aus der Datenbank lesen, beginnt die Entität mit diesem Status.
  • Modified. Einige oder alle Eigenschaftswerte der Entität wurden geändert. Die SaveChanges Methode muss eine UPDATE Anweisung ausgeben.
  • Deleted. Die Entität wurde zum Löschen markiert. Die SaveChanges Methode muss eine DELETE Anweisung ausgeben.
  • Detached. Die Entität wird nicht vom Datenbankkontext nachverfolgt.

Statusänderungen werden in einer Desktop-App in der Regel automatisch festgelegt. In einem Desktoptyp der Anwendung lesen Sie eine Entität und nehmen Änderungen an einigen seiner Eigenschaftswerte vor. Dadurch wird der Entitätsstatus automatisch auf Modified festgelegt. Wenn Sie dann aufrufen SaveChanges, generiert Das Entity Framework eine SQL-Anweisung UPDATE , die nur die tatsächlichen Eigenschaften aktualisiert, die Sie geändert haben.

Die getrennte Art von Web-Apps lässt diese fortlaufende Sequenz nicht zu. Der DbContext , der eine Entität liest, wird gelöscht, nachdem eine Seite gerendert wurde. Wenn die HttpPost Edit Aktionsmethode aufgerufen wird, wird eine neue Anforderung gestellt, und Sie haben eine neue Instanz des DbContext, sodass Sie den Entitätsstatus Modified. manuell auf "Then" festlegen müssen, wenn Sie aufrufen SaveChanges, aktualisiert das Entity Framework alle Spalten der Datenbankzeile, da der Kontext keine Möglichkeit hat, zu wissen, welche Eigenschaften Sie geändert haben.

Wenn die SQL-Anweisung Update nur die Felder aktualisieren soll, die der Benutzer tatsächlich geändert hat, können Sie die ursprünglichen Werte auf irgendeine Weise (z. B. ausgeblendete Felder) speichern, sodass sie beim Aufrufen der HttpPost Edit Methode verfügbar sind. Anschließend können Sie eine Student Entität mit den ursprünglichen Werten erstellen, die Methode mit dieser Attach ursprünglichen Version der Entität aufrufen, die Werte der Entität auf die neuen Werte aktualisieren und dann weitere Informationen aufrufen SaveChanges. , siehe Entitätszustände und SaveChanges und lokale Daten im MSDN Data Developer Center.

Der Code in "Views\Student\Edit.cshtml " ähnelt dem, was Sie in Create.cshtml gesehen haben, und es sind keine Änderungen erforderlich.

Führen Sie die Seite aus, indem Sie die Registerkarte "Kursteilnehmer " auswählen und dann auf einen Link "Bearbeiten " klicken.

Student_Edit_page

Ändern Sie einige der Daten, und klicken Sie auf Speichern. Die geänderten Daten werden auf der Indexseite angezeigt.

Students_Index_page_after_edit

Aktualisieren der Seite "Löschen"

In Controllers\StudentController.cs verwendet der Vorlagencode für die HttpGet Delete Methode die Find Methode, um die ausgewählte Student Entität abzurufen, wie Sie in den Details und Edit den Methoden gesehen haben. Allerdings müssen Sie dieser Methode und der dazugehörigen Ansicht einige Funktionen hinzufügen, um eine benutzerdefinierte Fehlermeldung zu implementieren, wenn der Aufruf von SaveChanges fehlschlägt.

Wie Sie bereits bei den Vorgängen zum Aktualisieren und Erstellen gesehen haben, benötigen Löschvorgänge zwei Aktionsmethoden. Die Methode, die als Reaktion auf eine GET-Anforderung aufgerufen wird, zeigt eine Ansicht an, die dem Benutzer die Möglichkeit gibt, den Löschvorgang zu genehmigen oder abzubrechen. Wenn der Benutzer diesen Löschvorgang genehmigt, wird eine POST-Anforderung erstellt. In diesem Fall wird die HttpPost Delete Methode aufgerufen, und dann führt diese Methode tatsächlich den Löschvorgang aus.

Sie fügen der HttpPost Delete Methode einen try-catch Block hinzu, um alle Fehler zu behandeln, die auftreten können, wenn die Datenbank aktualisiert wird. Wenn ein Fehler auftritt, ruft die HttpPost Delete Methode die HttpGet Delete Methode auf und übergibt einen Parameter, der angibt, dass ein Fehler aufgetreten ist. Die HttpGet Delete Methode zeigt dann die Bestätigungsseite zusammen mit der Fehlermeldung erneut an, sodass der Benutzer die Möglichkeit erhält, den Vorgang abzubrechen oder erneut zu versuchen.

  1. Ersetzen Sie die HttpGet Delete Aktionsmethode durch den folgenden Code, der die Fehlerberichterstattung verwaltet:

    public ActionResult Delete(bool? saveChangesError=false, int id = 0)
    {
        if (saveChangesError.GetValueOrDefault())
        {
            ViewBag.ErrorMessage = "Delete failed. Try again, and if the problem persists see your system administrator.";
        }
        Student student = db.Students.Find(id);
        if (student == null)
        {
            return HttpNotFound();
        }
        return View(student);
    }
    

    Dieser Code akzeptiert einen optionalen booleschen Parameter, der angibt, ob er aufgerufen wurde, nachdem Änderungen nicht gespeichert wurden. Dieser Parameter wird false aufgerufen, wenn die HttpGet Delete Methode ohne vorherigen Fehler aufgerufen wird. Wenn sie von der HttpPost Delete Methode als Reaktion auf einen Datenbankaktualisierungsfehler aufgerufen wird, wird true der Parameter und eine Fehlermeldung an die Ansicht übergeben.

  2. Ersetzen Sie die HttpPost Delete Aktionsmethode (benannt DeleteConfirmed) durch den folgenden Code, der den tatsächlichen Löschvorgang ausführt, und erfasst alle Datenbankaktualisierungsfehler.

    [HttpPost]
    [ValidateAntiForgeryToken]
    public ActionResult Delete(int id)
    {
        try
        {
            Student student = db.Students.Find(id);
            db.Students.Remove(student);
            db.SaveChanges();
        }
        catch (DataException/* dex */)
        {
            // uncomment dex and log error. 
            return RedirectToAction("Delete", new { id = id, saveChangesError = true });
        }
        return RedirectToAction("Index");
    }
    

    Dieser Code ruft die ausgewählte Entität ab und ruft dann die Remove-Methode auf, um den Status der Entität auf festzulegen Deleted. Beim Aufruf von SaveChanges wird der SQL-Befehl DELETE generiert. Ebenfalls haben Sie den Namen der Aktionsmethode DeleteConfirmed auf Delete geändert. Der Gerüstcode namens der HttpPost Delete Methode, um der HttpPost Methode DeleteConfirmed eine eindeutige Signatur zu geben. ( Die CLR erfordert überladene Methoden, um unterschiedliche Methodenparameter zu haben.) Nachdem die Signaturen eindeutig sind, können Sie mit der MVC-Konvention bleiben und denselben Namen für die HttpPost Methoden und HttpGet Löschmethoden verwenden.

    Wenn es sich bei der Verbesserung der Leistung in einer Anwendung mit hohem Volumen um eine Priorität handelt, können Sie eine unnötige SQL-Abfrage vermeiden, um die Zeile abzurufen, indem Sie die Codezeilen ersetzen, die die Find Und Remove Methoden durch den folgenden Code aufrufen, wie in gelber Hervorhebung gezeigt:

    Student studentToDelete = new Student() { StudentID = id };
    db.Entry(studentToDelete).State = EntityState.Deleted;
    

    Dieser Code instanziiert eine Student Entität nur mit dem Primärschlüsselwert und legt dann den Entitätsstatus auf Deleted. Das ist alles, was Entity Framework benötigt, um die Entität löschen zu können.

    Wie bereits erwähnt, löscht die HttpGet Delete Methode die Daten nicht. Das Ausführen eines Löschvorgangs als Reaktion auf eine GET-Anforderung (oder für diese Frage, das Ausführen eines Bearbeitungsvorgangs, eines Erstellungsvorgangs oder eines anderen Vorgangs, der Daten ändert) führt zu einem Sicherheitsrisiko. Weitere Informationen finden Sie unter ASP.NET MVC-Tipp Nr. 46 – Verwenden Sie "Links löschen" nicht, da sie Sicherheitslöcher auf Stephen Administrators Blog erstellen.

  3. Fügen Sie in Views\Student\Delete.cshtml eine Fehlermeldung zwischen der h2 Überschrift und der h3 Überschrift hinzu, wie im folgenden Beispiel gezeigt:

    <h2>Delete</h2>
    <p class="error">@ViewBag.ErrorMessage</p>
    <h3>Are you sure you want to delete this?</h3>
    

    Führen Sie die Seite aus, indem Sie die Registerkarte "Kursteilnehmer " auswählen und auf einen Link "Löschen " klicken:

    Student_Delete_page

  4. Klicken Sie auf Löschen. Die Indexseite wird ohne den gelöschten Student angezeigt. (Sie sehen ein Beispiel für den Fehlerbehandlungscode in Aktion im Behandeln des Parallelitätslernprogramms weiter unten in dieser Reihe.)

Sicherstellen, dass Datenbankverbindungen nicht geöffnet bleiben

Um sicherzustellen, dass Datenbankverbindungen ordnungsgemäß geschlossen sind und die ressourcen, die sie freigeben, sollten Sie sehen, dass die Kontextinstanz verworfen wird. Aus diesem Grund stellt der Gerüstcode am Ende der StudentController Klasse in StudentController.cs eine Dispose-Methode bereit, wie im folgenden Beispiel gezeigt:

protected override void Dispose(bool disposing)
{
    db.Dispose();
    base.Dispose(disposing);
}

Die Basisklasse Controller implementiert bereits die IDisposable Schnittstelle, sodass dieser Code der Methode einfach eine Außerkraftsetzung Dispose(bool) hinzufügt, um die Kontextinstanz explizit zu löschen.

Zusammenfassung

Sie verfügen jetzt über einen vollständigen Satz von Seiten, die einfache CRUD-Vorgänge für Student Entitäten ausführen. Sie haben MVC-Hilfsprogramme verwendet, um UI-Elemente für Datenfelder zu generieren. Weitere Informationen zu MVC-Hilfsern finden Sie unter Rendern eines Formulars mithilfe von HTML-Hilfselementen (die Seite ist für MVC 3, aber weiterhin für MVC 4 relevant).

Im nächsten Lernprogramm erweitern Sie die Funktionalität der Indexseite durch Hinzufügen von Sortier- und Pagingfunktionen.

Links zu anderen Entity Framework-Ressourcen finden Sie in der ASP.NET Datenzugriffs-Inhaltszuordnung.