Implementowanie podstawowych funkcji CRUD za pomocą platformy Entity Framework w aplikacji MVC ASP.NET (2 z 10)
Autor: Tom Dykstra
Przykładowa aplikacja internetowa Contoso University pokazuje, jak tworzyć aplikacje ASP.NET MVC 4 przy użyciu programu Entity Framework 5 Code First i Visual Studio 2012. Aby uzyskać informacje na temat serii samouczków, zobacz pierwszy samouczek z serii.
Uwaga
Jeśli napotkasz problem, którego nie możesz rozwiązać, pobierz ukończony rozdział i spróbuj odtworzyć problem. Zazwyczaj rozwiązanie problemu można znaleźć, porównując kod z ukończonym kodem. Aby uzyskać informacje o niektórych typowych błędach i sposobach ich rozwiązywania, zobacz Błędy i obejścia.
W poprzednim samouczku utworzono aplikację MVC, która przechowuje i wyświetla dane przy użyciu programu Entity Framework i programu SQL Server LocalDB. W tym samouczku zapoznasz się i dostosujesz kod CRUD (tworzenie, odczytywanie, aktualizowanie i usuwanie), który szkielet MVC tworzy automatycznie w kontrolerach i widokach.
Uwaga
Typowym rozwiązaniem jest zaimplementowanie wzorca repozytorium w celu utworzenia warstwy abstrakcji między kontrolerem a warstwą dostępu do danych. Aby zachować prostotę tych samouczków, nie zaimplementujesz repozytorium do późniejszego samouczka z tej serii.
W tym samouczku utworzysz następujące strony internetowe:
Tworzenie strony szczegółów
Kod szkieletu strony Uczniowie Index
pominął Enrollments
właściwość, ponieważ ta właściwość zawiera kolekcję. Details
Na stronie zostanie wyświetlona zawartość kolekcji w tabeli HTML.
W obszarze Controllers\StudentController.cs metoda akcji widoku Details
używa Find
metody do pobrania pojedynczej Student
jednostki.
public ActionResult Details(int id = 0)
{
Student student = db.Students.Find(id);
if (student == null)
{
return HttpNotFound();
}
return View(student);
}
Wartość klucza jest przekazywana do metody jako parametru id
i pochodzi z danych trasy w hiperlinku Szczegóły na stronie Indeks.
Otwórz plik Views\Student\Details.cshtml. Każde pole jest wyświetlane przy użyciu
DisplayFor
pomocnika, jak pokazano w poniższym przykładzie:<div class="display-label"> @Html.DisplayNameFor(model => model.LastName) </div> <div class="display-field"> @Html.DisplayFor(model => model.LastName) </div>
EnrollmentDate
Po polu i bezpośrednio przed tagiem zamykającymfieldset
dodaj kod, aby wyświetlić listę rejestracji, jak pokazano w poniższym przykładzie:<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>
Ten kod przechodzi przez jednostki we
Enrollments
właściwości nawigacji. Dla każdejEnrollment
jednostki we właściwości wyświetla tytuł kursu i ocenę. Tytuł kursu jest pobierany zCourse
jednostki przechowywanejCourse
we właściwościEnrollments
nawigacji jednostki. Wszystkie te dane są pobierane z bazy danych automatycznie, gdy są potrzebne. (Innymi słowy, używasz tutaj leniwego ładowania. Nie określono chętnegoCourses
ładowania dla właściwości nawigacji, więc przy pierwszej próbie uzyskania dostępu do tej właściwości zapytanie jest wysyłane do bazy danych w celu pobrania danych. Więcej informacji na temat leniwego ładowania i chętnego ładowania można przeczytać w samouczku Reading Related Data (Odczytywanie powiązanych danych) w dalszej części tej serii).Uruchom stronę, wybierając kartę Uczniowie i klikając link Szczegóły dla Alexandra Carsona. Zostanie wyświetlona lista kursów i ocen dla wybranego ucznia:
Aktualizowanie strony tworzenia
W controllers\StudentController.cs zastąp
HttpPost``Create
metodętry-catch
akcji następującym kodem, aby dodać blok i atrybut Bind do metody szkieletowej:[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); }
Ten kod dodaje
Student
jednostkę utworzoną przez powiązanie modelu MVC ASP.NET z zestawemStudents
jednostek, a następnie zapisuje zmiany w bazie danych. (Binder modelu odnosi się do ASP.NET funkcji MVC, która ułatwia pracę z danymi przesłanymi przez formularz; binder modelu konwertuje opublikowane wartości formularza na typy CLR i przekazuje je do metody akcji w parametrach. W tym przypadku binder modelu tworzy wystąpienieStudent
jednostki za pomocą wartości właściwości zForm
kolekcji.Atrybut
ValidateAntiForgeryToken
pomaga zapobiegać atakom fałszerzmu żądań między witrynami .
> [!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*.
Uruchom stronę, wybierając kartę Uczniowie i klikając pozycję Utwórz nowy.
Niektóre sprawdzanie poprawności danych działa domyślnie. Wprowadź nazwy i nieprawidłową datę, a następnie kliknij przycisk Utwórz , aby wyświetlić komunikat o błędzie.
Poniższy wyróżniony kod przedstawia sprawdzanie poprawności modelu.
[HttpPost] [ValidateAntiForgeryToken] public ActionResult Create(Student student) { if (ModelState.IsValid) { db.Students.Add(student); db.SaveChanges(); return RedirectToAction("Index"); } return View(student); }
Zmień datę na prawidłową wartość, taką jak 1.09.2005, a następnie kliknij przycisk Utwórz , aby zobaczyć, jak nowy student pojawi się na stronie Indeks .
Aktualizowanie strony EDYTUJ POST
W controllers\StudentController.cs HttpGet
Edit
metoda (ta bez atrybutuHttpPost
) używa Find
metody do pobrania wybranej Student
jednostki, jak pokazano w metodzie .Details
Nie musisz zmieniać tej metody.
Zastąp jednak metodę HttpPost
Edit
akcji następującym kodem, aby dodać try-catch
blok i atrybut Bind:
[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);
}
Ten kod jest podobny do tego, co pokazano w metodzie HttpPost
Create
. Jednak zamiast dodawać jednostkę utworzoną przez powiązanie modelu do zestawu jednostek, ten kod ustawia flagę na jednostce wskazującej, że została zmieniona. Po wywołaniu metody SaveChanges flaga Zmodyfikowana powoduje, że program Entity Framework tworzy instrukcje SQL w celu zaktualizowania wiersza bazy danych. Wszystkie kolumny wiersza bazy danych zostaną zaktualizowane, w tym te, które użytkownik nie zmienił, a konflikty współbieżności są ignorowane. (Dowiesz się, jak obsługiwać współbieżność w późniejszym samouczku w tej serii).
Stany jednostek oraz metody Attach i SaveChanges
Kontekst bazy danych śledzi, czy jednostki w pamięci są zsynchronizowane z odpowiednimi wierszami w bazie danych, a te informacje określają, co się stanie po wywołaniu SaveChanges
metody. Na przykład po przekazaniu nowej jednostki do metody Add stan tej jednostki jest ustawiony na Added
wartość . Następnie po wywołaniu metody SaveChanges kontekst bazy danych wystawia polecenie SQL INSERT
.
Jednostka może znajdować się w jednym znastępujących stanów:
Added
. Jednostka nie istnieje jeszcze w bazie danych. MetodaSaveChanges
musi wydać instrukcjęINSERT
.Unchanged
. W tej jednostceSaveChanges
nie trzeba nic robić przy użyciu metody . Podczas odczytywania jednostki z bazy danych jednostka zaczyna się od tego stanu.Modified
. Niektóre lub wszystkie wartości właściwości jednostki zostały zmodyfikowane. MetodaSaveChanges
musi wydać instrukcjęUPDATE
.Deleted
. Jednostka została oznaczona do usunięcia. MetodaSaveChanges
musi wydać instrukcjęDELETE
.Detached
. Jednostka nie jest śledzona przez kontekst bazy danych.
W aplikacji klasycznej zmiany stanu są zwykle ustawiane automatycznie. W typie aplikacji klasycznej odczytujesz jednostkę i wprowadzasz zmiany w niektórych jej wartościach właściwości. Powoduje to automatyczne zmianę stanu jednostki na Modified
. Następnie po wywołaniu SaveChanges
metody program Entity Framework generuje instrukcję SQL UPDATE
, która aktualizuje tylko rzeczywiste właściwości, które uległy zmianie.
Rozłączony charakter aplikacji internetowych nie zezwala na tę sekwencję ciągłą. Obiekt DbContext odczytujący jednostkę jest usuwany po renderowaniu strony. Po wywołaniu HttpPost
Edit
metody akcji zostanie wykonane nowe żądanie i masz nowe wystąpienie obiektu DbContext, dlatego należy ręcznie ustawić stan jednostki na Modified.
Wartość Następnie po wywołaniu SaveChanges
programu Entity Framework aktualizuje wszystkie kolumny wiersza bazy danych, ponieważ kontekst nie ma możliwości poznania, które właściwości zostały zmienione.
Jeśli chcesz, aby instrukcja SQL Update
aktualizowała tylko pola, które użytkownik rzeczywiście zmienił, możesz zapisać oryginalne wartości w jakiś sposób (na przykład ukryte pola), aby były dostępne po wywołaniu HttpPost
Edit
metody. Następnie możesz utworzyć jednostkę przy użyciu oryginalnych wartości, wywołać Attach
metodę Student
z tą oryginalną wersją jednostki, zaktualizować wartości jednostki do nowych wartości, a następnie wywołać metodę SaveChanges.
Aby uzyskać więcej informacji, zobacz Stany jednostki i SaveChanges i Dane lokalne w Centrum deweloperów danych MSDN.
Kod w pliku Views\Student\Edit.cshtml jest podobny do tego, co zostało wyświetlone w pliku Create.cshtml i nie są wymagane żadne zmiany.
Uruchom stronę, wybierając kartę Uczniowie , a następnie klikając hiperlink Edytuj .
Zmień niektóre dane i kliknij przycisk Zapisz. Zmienione dane są widoczne na stronie Indeks.
Aktualizowanie strony usuwania
W obszarze Controllers\StudentController.cs kod szablonu metody HttpGet
Delete
używa Find
metody do pobrania wybranej Student
jednostki, jak pokazano w metodach Details
i Edit
. Jednak aby zaimplementować niestandardowy komunikat o błędzie, gdy wywołanie zakończy się SaveChanges
niepowodzeniem, do tej metody dodasz pewne funkcje i odpowiadający jej widok.
Jak pokazano na potrzeby operacji aktualizacji i tworzenia, operacje usuwania wymagają dwóch metod akcji. Metoda wywoływana w odpowiedzi na żądanie GET wyświetla widok, który daje użytkownikowi szansę zatwierdzenia lub anulowania operacji usuwania. Jeśli użytkownik go zatwierdzi, zostanie utworzone żądanie POST. W takim przypadku metoda jest wywoływana, HttpPost
Delete
a następnie metoda faktycznie wykonuje operację usuwania.
Do metody dodasz try-catch
blok, HttpPost
Delete
aby obsłużyć wszelkie błędy, które mogą wystąpić po zaktualizowaniu bazy danych. Jeśli wystąpi błąd, HttpPost
Delete
metoda wywołuje metodę HttpGet
Delete
, przekazując jej parametr wskazujący, że wystąpił błąd. Następnie HttpGet Delete
metoda redisplays strony potwierdzenia wraz z komunikatem o błędzie, dając użytkownikowi możliwość anulowania lub próby ponownie.
Zastąp metodę
HttpGet
Delete
akcji następującym kodem, który zarządza raportowaniem błędów: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); }
Ten kod akceptuje opcjonalny parametr logiczny wskazujący, czy został wywołany po niepowodzeniu zapisywania zmian. Ten parametr występuje
false
, gdyHttpGet
Delete
metoda jest wywoływana bez wcześniejszego błędu. Gdy jest wywoływana przez metodęHttpPost
Delete
w odpowiedzi na błąd aktualizacji bazy danych, parametr jesttrue
i komunikat o błędzie jest przekazywany do widoku.Zastąp metodę
HttpPost
Delete
akcji (o nazwieDeleteConfirmed
) następującym kodem, który wykonuje rzeczywistą operację usuwania i przechwytuje wszelkie błędy aktualizacji bazy danych.[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"); }
Ten kod pobiera wybraną jednostkę, a następnie wywołuje metodę Remove , aby ustawić stan jednostki na
Deleted
. PoSaveChanges
wywołaniu jest generowane polecenie SQLDELETE
. Zmieniono również nazwę metody akcji zDeleteConfirmed
naDelete
. Kod szkieletowy o nazwieHttpPost
Delete
metodyDeleteConfirmed
w celu nadania metodzieHttpPost
unikatowego podpisu. ( ClR wymaga przeciążonych metod, aby mieć różne parametry metody). Teraz, gdy podpisy są unikatowe, możesz trzymać się konwencji MVC i używać tej samej nazwy dlaHttpPost
metod iHttpGet
usuwania.Jeśli zwiększenie wydajności w aplikacji o dużej ilości jest priorytetem, można uniknąć niepotrzebnego zapytania SQL w celu pobrania wiersza, zastępując wiersze kodu, które wywołają
Find
metody iRemove
następującym kodem, jak pokazano w żółtym wyróżnieniu:Student studentToDelete = new Student() { StudentID = id }; db.Entry(studentToDelete).State = EntityState.Deleted;
Ten kod tworzy wystąpienie
Student
jednostki przy użyciu tylko wartości klucza podstawowego, a następnie ustawia stan jednostki naDeleted
. To wszystko, co wymaga program Entity Framework w celu usunięcia jednostki.Jak wspomniano,
HttpGet
Delete
metoda nie usuwa danych. Wykonanie operacji usuwania w odpowiedzi na żądanie GET (lub w tym przypadku wykonanie dowolnej operacji edycji, operacji tworzenia lub dowolnej innej operacji, która zmienia dane) powoduje zagrożenie bezpieczeństwa. Aby uzyskać więcej informacji, zobacz ASP.NET MVC Tip #46 — Nie używaj linków usuwania, ponieważ tworzą zabezpieczeń na blogu Stephena Walthera.W pliku Views\Student\Delete.cshtml dodaj komunikat o błędzie między nagłówkiem
h2
a nagłówkiemh3
, jak pokazano w poniższym przykładzie:<h2>Delete</h2> <p class="error">@ViewBag.ErrorMessage</p> <h3>Are you sure you want to delete this?</h3>
Uruchom stronę, wybierając kartę Uczniowie i klikając hiperlink Usuń :
Kliknij Usuń. Strona Indeks jest wyświetlana bez usuniętego ucznia. (Zobaczysz przykład kodu obsługi błędów w działaniu w pliku Obsługa samouczka współbieżności w dalszej części tej serii).
Upewnienie się, że połączenia z bazą danych nie są otwarte
Aby upewnić się, że połączenia z bazą danych są prawidłowo zamknięte i zasoby, które zostały zwolnione, należy zobaczyć, że wystąpienie kontekstu jest usuwane. Dlatego kod szkieletowy udostępnia metodę Dispose na końcu StudentController
klasy w StudentController.cs, jak pokazano w poniższym przykładzie:
protected override void Dispose(bool disposing)
{
db.Dispose();
base.Dispose(disposing);
}
Klasa bazowa Controller
już implementuje IDisposable
interfejs, więc ten kod po prostu dodaje przesłonięcia do Dispose(bool)
metody w celu jawnego usunięcia wystąpienia kontekstu.
Podsumowanie
Masz teraz kompletny zestaw stron, które wykonują proste operacje CRUD dla Student
jednostek. Użyto pomocników MVC do generowania elementów interfejsu użytkownika dla pól danych. Aby uzyskać więcej informacji na temat pomocników MVC, zobacz Renderowanie formularza przy użyciu pomocników HTML (strona jest dla MVC 3, ale nadal jest odpowiednia dla MVC 4).
W następnym samouczku rozszerzysz funkcjonalność strony Indeks, dodając sortowanie i stronicowanie.
Linki do innych zasobów programu Entity Framework można znaleźć na mapie zawartości dostępu do danych ASP.NET.