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:
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.
Ö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>
Fügen Sie nach dem
EnrollmentDate
Feld und unmittelbar vor dem schließendenfieldset
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 jedeEnrollment
Entität in der Eigenschaft werden der Kurstitel und die Noten angezeigt. Der Kurstitel wird von derCourse
Entität abgerufen, die in derCourse
Navigationseigenschaft derEnrollments
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 derCourses
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.)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:
Aktualisieren der Seite "Erstellen"
Ersetzen Sie in Controller\StudentController.cs die
HttpPost``Create
Aktionsmethode durch den folgenden Code, um einentry-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, zumStudents
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 eineStudent
Entität für Sie mithilfe von Eigenschaftswerten aus derForm
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*.
Führen Sie die Seite aus, indem Sie die Registerkarte "Kursteilnehmer" auswählen und auf "Neu erstellen" klicken.
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.
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.
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. DieSaveChanges
Methode muss eineINSERT
Anweisung ausgeben.Unchanged
. Die MethodeSaveChanges
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. DieSaveChanges
Methode muss eineUPDATE
Anweisung ausgeben.Deleted
. Die Entität wurde zum Löschen markiert. DieSaveChanges
Methode muss eineDELETE
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.
Ändern Sie einige der Daten, und klicken Sie auf Speichern. Die geänderten Daten werden auf der Indexseite angezeigt.
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.
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 dieHttpGet
Delete
Methode ohne vorherigen Fehler aufgerufen wird. Wenn sie von derHttpPost
Delete
Methode als Reaktion auf einen Datenbankaktualisierungsfehler aufgerufen wird, wirdtrue
der Parameter und eine Fehlermeldung an die Ansicht übergeben.Ersetzen Sie die
HttpPost
Delete
Aktionsmethode (benanntDeleteConfirmed
) 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 vonSaveChanges
wird der SQL-BefehlDELETE
generiert. Ebenfalls haben Sie den Namen der AktionsmethodeDeleteConfirmed
aufDelete
geändert. Der Gerüstcode namens derHttpPost
Delete
Methode, um derHttpPost
MethodeDeleteConfirmed
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 dieHttpPost
Methoden undHttpGet
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
UndRemove
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 aufDeleted
. 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.Fügen Sie in Views\Student\Delete.cshtml eine Fehlermeldung zwischen der
h2
Überschrift und derh3
Ü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:
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.