Заметка
Доступ к этой странице требует авторизации. Вы можете попробовать войти в систему или изменить каталог.
Доступ к этой странице требует авторизации. Вы можете попробовать сменить директорию.
Пример веб-приложения Contoso University демонстрирует создание ASP.NET приложений MVC 4 с помощью Entity Framework 5 Code First и Visual Studio 2012. Сведения о серии руководств см. в первом руководстве серии.
Примечание.
Если вы не сможете устранить проблему, скачайте завершенную главу и попытайтесь воспроизвести проблему. Как правило, решение проблемы можно найти, сравнивая код с завершенным кодом. Некоторые распространенные ошибки и способы их устранения см. в статье об ошибках и обходных решениях.
В предыдущем руководстве вы создали приложение MVC, которое хранит и отображает данные с помощью Entity Framework и SQL Server LocalDB. В этом руководстве вы просмотрите и настройте код CRUD (создание, чтение, обновление, удаление), который шаблон MVC автоматически создает для вас в контроллерах и представлениях.
Примечание.
Чтобы создать уровень абстракции между контроллером и уровнем доступа к данным, часто реализуют шаблон репозитория. Чтобы сделать эти учебники простыми, вы не будете реализовывать репозиторий до тех пор, пока не будет описано в этом руководстве.
В этом руководстве вы создадите следующие веб-страницы:




Создание страницы сведений
Шаблонный код страницы "Учащиеся" Index оставил свойство Enrollments , так как это свойство содержит коллекцию. Details На странице вы увидите содержимое коллекции в HTML-таблице.
В Controllers\StudentController.cs метод действия для Details представления использует Find метод для извлечения одной Student сущности.
public ActionResult Details(int id = 0)
{
Student student = db.Students.Find(id);
if (student == null)
{
return HttpNotFound();
}
return View(student);
}
Значение ключа передается методу в качестве id параметра и поступает из данных маршрута в гиперссылке "Сведения " на странице индекса.
Откройте Views\Student\Details.cshtml. Каждое поле отображается с помощью вспомогательного
DisplayForсредства, как показано в следующем примере:<div class="display-label"> @Html.DisplayNameFor(model => model.LastName) </div> <div class="display-field"> @Html.DisplayFor(model => model.LastName) </div>EnrollmentDateПосле поля и непосредственно перед закрывающимfieldsetтегом добавьте код для отображения списка регистраций, как показано в следующем примере:<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>Этот код циклически обрабатывает сущности в свойстве навигации
Enrollments. Для каждойEnrollmentсущности в свойстве отображается название курса и оценка. Название курса извлекается изCourseсущности, которая хранится вCourseсвойстве навигации сущностиEnrollments. Все эти данные извлекаются из базы данных автоматически при необходимости. (Иными словами, вы используете отложенную загрузку здесь. Вы не указали требуемую загрузку дляCoursesсвойства навигации, поэтому при первом попытке получить эти свойства запрос отправляется в базу данных для получения данных. Дополнительные сведения о отложенной загрузке и активной загрузке см. в руководстве по чтению связанных данных далее в этой серии.)Запустите страницу, выбрав вкладку "Учащиеся" и щелкнув ссылку "Сведения " для Александра Карсона. Откроется список курсов и оценок для выбранного учащегося:

Обновление страницы создания
В Controllers\StudentController.cs замените
HttpPost``Createметод действия следующим кодом, чтобы добавитьtry-catchблок и атрибут Bind в шаблонный метод:[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); }Этот код добавляет
Studentсущность, созданную привязкой модели MVC ASP.NET кStudentsнабору сущностей, а затем сохраняет изменения в базе данных. (Привязыватель модели ссылается на функциональность ASP.NET MVC, которая упрощает работу с данными, отправленными формой; привязка модели преобразует опубликованные значения форм в типы CLR и передает их методу действия в параметрах. В этом случае привязка модели создает экземплярStudentсущности для использования значений свойств изFormколлекции.)Этот
ValidateAntiForgeryTokenатрибут помогает предотвратить атаки на подделку запросов между сайтами.
> [!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.
>
> 
>
> 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*.
Запустите страницу, выбрав вкладку "Учащиеся" и нажав кнопку "Создать".

Некоторые проверки данных работают по умолчанию. Введите имена и недопустимую дату и нажмите кнопку "Создать ", чтобы увидеть сообщение об ошибке.

В следующем выделенном коде показана проверка модели.
[HttpPost] [ValidateAntiForgeryToken] public ActionResult Create(Student student) { if (ModelState.IsValid) { db.Students.Add(student); db.SaveChanges(); return RedirectToAction("Index"); } return View(student); }Измените дату на допустимое значение, например 9.1.2005, и нажмите кнопку "Создать", чтобы увидеть, что новый учащийся появится на странице "Индекс".

Обновление страницы Edit POST
В Controllers\StudentController.cs Edit HttpGetметод (без HttpPost атрибута) использует Find метод для получения выбранной Student сущности, как показано в методе.Details Изменять этот метод не нужно.
Однако замените HttpPost Edit метод действия следующим кодом, чтобы добавить try-catch блок и атрибут 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);
}
Этот код похож на то, что вы видели в методе HttpPost Create . Однако вместо добавления сущности, созданной привязкой модели к набору сущностей, этот код задает флаг сущности, указывающий, что он был изменен. При вызове метода SaveChanges флаг "Изменено" приводит к созданию инструкций SQL Для создания инструкций SQL для обновления строки базы данных. Все столбцы строки базы данных будут обновлены, включая те, которые пользователь не изменил, и конфликты параллелизма игнорируются. (Вы узнаете, как обрабатывать параллелизм в следующем руководстве в этом серии.)
Состояния сущностей и методы Attach и SaveChanges
Контекст базы данных отслеживает состояние синхронизации сущностей в памяти с соответствующими им строками в базе данных. Данные отслеживания определяют, что происходит при вызове метода SaveChanges. Например, при передаче новой сущности методу Add для этой сущности задано Addedсостояние этой сущности. Затем при вызове метода SaveChanges контекст базы данных выдает команду SQL INSERT .
Сущность может находиться в одном из следующих состояний:
Added. Сущность еще не существует в базе данных. МетодSaveChangesдолжен выдавать инструкциюINSERT.Unchanged. С этой сущностью не нужно выполнять никакие действия с помощью методаSaveChanges. Это начальный статус сущности, который она имеет при чтении из базы данных.Modified. Были изменены значения некоторых или всех свойств сущности. МетодSaveChangesдолжен выдавать инструкциюUPDATE.Deleted. Сущность отмечена для удаления. МетодSaveChangesдолжен выдавать инструкциюDELETE.Detached. Сущность не отслеживается контекстом базы данных.
В классическом приложении изменения состояния обычно осуществляются автоматически. В классическом типе приложения вы считываете сущность и вносите изменения в некоторые из его значений свойств. В этом случае состояние сущности автоматически изменится на Modified. Затем при вызове SaveChangesEntity Framework создает инструкцию SQL UPDATE , которая обновляет только фактические свойства, которые вы изменили.
Отключенный характер веб-приложений не разрешает эту непрерывную последовательность. DbContext, который считывает сущность, удаляется после отрисовки страницы. HttpPost Edit При вызове метода действия создается новый запрос, и у вас есть новый экземпляр DbContext, поэтому при вызове SaveChangesнеобходимо вручную задать состояние Modified. сущности, entity Framework обновляет все столбцы строки базы данных, так как контекст не имеет способа знать, какие свойства вы изменили.
Если вы хотите, чтобы инструкция SQL Update обновляла только измененные пользователем поля, можно сохранить исходные значения каким-то образом (например, скрытые поля), чтобы они были доступны при HttpPost Edit вызове метода. Затем можно создать Student сущность с помощью исходных значений, вызвать Attach метод с этой исходной версией сущности, обновить значения сущности до новых значений, а затем вызвать SaveChanges. дополнительные сведения: состояния сущностей и SaveChanges и локальные данные в Центре разработчиков данных MSDN.
Код в Views\Student\Edit.cshtml похож на то, что вы видели в Create.cshtml, и никаких изменений не требуется.
Запустите страницу, выбрав вкладку "Учащиеся" , а затем щелкните гиперссылку "Изменить ".

Измените определенные данные и нажмите кнопку Save (Сохранить). На странице индекса отображаются измененные данные.

Обновление страницы удаления
В Controllers\StudentController.cs код шаблона для HttpGet Delete метода использует Find метод для получения выбранной Student сущности, как показано в Details и Edit методах. Тем не менее, чтобы реализовать настраиваемое сообщение об ошибке при сбое вызова метода SaveChanges, необходимо добавить некоторые функции в этот метод и соответствующее ему представление.
Как и в случае с операциями обновления и создания, операции удаления требуют двух методов действия. Метод, который вызывается в ответ на запрос GET, отображает представление, которое дает пользователю возможность утвердить или отменить операцию удаления. Если пользователь подтверждает ее, создается запрос POST. Когда это произойдет, метод вызывается, HttpPost Delete а затем этот метод фактически выполняет операцию удаления.
Вы добавите try-catch блок в HttpPost Delete метод для обработки любых ошибок, которые могут возникнуть при обновлении базы данных. Если возникает ошибка, HttpPost Delete метод вызывает HttpGet Delete метод, передавая его параметр, указывающий на то, что произошла ошибка. Затем HttpGet Delete метод переиграет страницу подтверждения вместе с сообщением об ошибке, что дает пользователю возможность отменить или повторить попытку.
Замените
HttpGetDeleteметод действия следующим кодом, который управляет отчетами об ошибках: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); }Этот код принимает необязательный логический параметр, указывающий, был ли он вызван после сбоя сохранения изменений. Этот параметр возникает
falseприHttpGetDeleteвызове метода без предыдущего сбоя. При вызовеHttpPostDeleteметода в ответ на ошибку обновления базы данных параметр передаетсяtrueв представление.Замените
HttpPostDeleteметод действия (именованныйDeleteConfirmed) приведенным ниже кодом, который выполняет фактическую операцию удаления и перехватывает ошибки обновления базы данных.[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"); }Этот код извлекает выбранную сущность, а затем вызывает метод Remove , чтобы задать состояние
Deletedсущности. При вызове методаSaveChangesсоздается инструкция SQLDELETE. Вы также изменили имя метода действия сDeleteConfirmedнаDelete. Шаблонный код с именемHttpPostDeleteметода, который даетHttpPostметодуDeleteConfirmedуникальную подпись. ( Среда CLR требует перегруженных методов иметь разные параметры метода.) Теперь, когда подписи уникальны, вы можете придерживаться соглашения MVC и использовать то же имя дляHttpPostметодов иHttpGetудаления.Если повышение производительности в приложении с большим объемом является приоритетом, можно избежать ненужного SQL-запроса, чтобы получить строку, заменив строки кода, вызывающие
FindиRemoveметоды следующим кодом, как показано в желтом выделении:Student studentToDelete = new Student() { StudentID = id }; db.Entry(studentToDelete).State = EntityState.Deleted;Этот код создает
Studentэкземпляр сущности, используя только значение первичного ключа, а затем задает состояниеDeletedсущности. Это все, что платформе Entity Framework необходимо для удаления сущности.Как отмечалось,
HttpGetDeleteметод не удаляет данные. Выполнение операции удаления в ответ на запрос GET (или для этого, выполнение любой операции редактирования, операции создания или любой другой операции, которая изменяет данные) создает риск безопасности. Дополнительные сведения см. в разделе ASP.NET Совет MVC #46. Не используйте ссылки на удаление, так как они создают дыры безопасности в блоге Стивена Уолтера.В Views\Student\Delete.cshtml добавьте сообщение об ошибке между заголовком
h2иh3заголовком, как показано в следующем примере:<h2>Delete</h2> <p class="error">@ViewBag.ErrorMessage</p> <h3>Are you sure you want to delete this?</h3>Запустите страницу, выбрав вкладку "Учащиеся" и щелкнув гиперссылку "Удалить ":

Нажмите Удалить. Отображается страница Index (Указатель), на которой удаленный учащийся будет отсутствовать. (Вы увидите пример кода обработки ошибок в действии Руководство по обработке параллелизма далее в этой серии.)
Обеспечение того, чтобы подключения к базе данных не оставались открытыми
Чтобы убедиться, что подключения к базе данных правильно закрыты и ресурсы, которые они удерживают, должны убедиться, что экземпляр контекста удален. Именно поэтому шаблонный код предоставляет метод Dispose в конце StudentController класса в StudentController.cs, как показано в следующем примере:
protected override void Dispose(bool disposing)
{
db.Dispose();
base.Dispose(disposing);
}
Базовый Controller класс уже реализует IDisposable интерфейс, поэтому этот код просто добавляет переопределение в Dispose(bool) метод для явного удаления экземпляра контекста.
Итоги
Теперь у вас есть полный набор страниц, выполняющих простые операции CRUD для Student сущностей. Вы использовали вспомогательные средства MVC для создания элементов пользовательского интерфейса для полей данных. Дополнительные сведения о вспомогательных элементах MVC см. в разделе "Отрисовка формы с помощью вспомогательных элементов HTML" (страница предназначена для MVC 3, но по-прежнему актуальна для MVC 4).
В следующем руководстве вы развернете функциональные возможности страницы индекса, добавив сортировку и разбиение по страницам.
Ссылки на другие ресурсы Entity Framework можно найти на карте содержимого доступа к данным ASP.NET.