Поделиться через


Руководство. Реализация функций CRUD с помощью Entity Framework в ASP.NET MVC

В предыдущем руководстве вы создали приложение MVC, которое хранит и отображает данные с помощью Entity Framework (EF) 6 и SQL Server LocalDB. В этом руководстве вы просматриваете и настраиваете код создания, чтения, обновления, удаления (CRUD), который шаблон MVC автоматически создает для вас в контроллерах и представлениях.

Примечание.

Чтобы создать уровень абстракции между контроллером и уровнем доступа к данным, часто реализуют шаблон репозитория. Чтобы сделать эти учебники простыми и ориентированными на обучение использованию EF 6 сам, они не используют репозитории. Сведения о реализации репозиториев см. в схеме содержимого ASP.NET доступа к данным.

Ниже приведены примеры создаваемых веб-страниц:

Снимок экрана страницы сведений о учащемся.

Снимок экрана: страница создания учащегося.

Снимок экрана: страница удаления учащегося.

Изучив это руководство, вы:

  • Создание страницы сведений
  • Обновление страницы Create
  • Обновление метода HttpPost Edit
  • Обновление страницы "Delete" (Удаление)
  • Закрытие подключений к базам данных
  • Обработка транзакций

Необходимые компоненты

Создание страницы сведений

Шаблонный код страницы "Учащиеся" Index оставил свойство Enrollments , так как это свойство содержит коллекцию. Details На странице вы увидите содержимое коллекции в html-таблице.

В Controllers\StudentController.cs метод действия для представления использует метод Find для Details получения одной Student сущности.

public ActionResult Details(int? id)
{
    if (id == null)
    {
        return new HttpStatusCodeResult(HttpStatusCode.BadRequest);
    }
    Student student = db.Students.Find(id);
    if (student == null)
    {
        return HttpNotFound();
    }
    return View(student);
}

Значение ключа передается методу в качестве id параметра и поступает из данных маршрута в гиперссылке "Сведения " на странице индекса.

Совет. Маршрутизация данных

Данные маршрута — это данные, найденные привязщиком модели в сегменте URL-адреса, указанном в таблице маршрутизации. Например, маршрут по умолчанию указывает controllerи actionid сегменты:

routes.MapRoute(
    name: "Default",
    url: "{controller}/{action}/{id}",
    defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional }
);

В следующем URL-адресе маршрут по умолчанию сопоставляется Instructor в виде Index controllerи 1 в качестве action idзначения данных маршрута.

http://localhost:1230/Instructor/Index/1?courseID=2021

?courseID=2021 — это строковое значение запроса. Привязка модели также будет работать, если передать id значение строки запроса в качестве значения строки запроса:

http://localhost:1230/Instructor/Index?id=1&CourseID=2021

URL-адреса создаются операторами ActionLink в представлении Razor. В следующем коде id параметр соответствует маршруту по умолчанию, поэтому id добавляется в данные маршрута.

@Html.ActionLink("Select", "Index", new { id = item.PersonID  })

В следующем коде courseID параметр не соответствует параметру в маршруте по умолчанию, поэтому он добавляется в виде строки запроса.

@Html.ActionLink("Select", "Index", new { courseID = item.CourseID })

Создание страницы сведений

  1. Откройте Views\Student\Details.cshtml.

    Каждое поле отображается с помощью вспомогательного DisplayFor средства, как показано в следующем примере:

    <dt>
        @Html.DisplayNameFor(model => model.LastName)
    </dt>
    <dd>
        @Html.DisplayFor(model => model.LastName)
    </dd>
    
  2. EnrollmentDate После поля и непосредственно перед закрывающим </dl> тегом добавьте выделенный код для отображения списка регистраций, как показано в следующем примере:

    <dt>
                @Html.DisplayNameFor(model => model.EnrollmentDate)
            </dt>
    
            <dd>
                @Html.DisplayFor(model => model.EnrollmentDate)
            </dd>
            <dt>
                @Html.DisplayNameFor(model => model.Enrollments)
            </dt>
            <dd>
                <table class="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>
            </dd>
        </dl>
    </div>
    <p>
        @Html.ActionLink("Edit", "Edit", new { id = Model.ID }) |
        @Html.ActionLink("Back to List", "Index")
    </p>
    

    Если ошибка отступа кода после вставки кода, нажмите клавиши CTRL K, CTRL++D, чтобы отформатировать его.

    Этот код циклически обрабатывает сущности в свойстве навигации Enrollments. Для каждой Enrollment сущности в свойстве отображается название курса и оценка. Название курса извлекается из Course сущности, которая хранится в Course свойстве навигации сущности Enrollments . Все эти данные извлекаются из базы данных автоматически при необходимости. Другими словами, вы используете ленивую загрузку здесь. Вы не указали требуемую загрузку для Courses свойства навигации, поэтому регистрации не были получены в том же запросе, который получил учащихся. Вместо этого при первом попытке доступа к Enrollments свойству навигации новый запрос отправляется в базу данных для получения данных. Дополнительные сведения о отложенной загрузке и активной загрузке см. в руководстве по чтению связанных данных далее в этой серии.

  3. Откройте страницу сведений, запустите программу (CTRL+F5), выбрав вкладку "Учащиеся ", а затем щелкните ссылку "Сведения " для Александра Карсона. (Если вы нажимаете клавишу Ctrl+F5 во время открытия файла Details.cshtml вы получите ошибку HTTP 400. Это связано с тем, что Visual Studio пытается запустить страницу сведений, но она не была достигнута из ссылки, которая указывает учащегося для отображения. Если это произойдет, удалите "Student/Details" из URL-адреса и повторите попытку или закройте браузер, щелкните проект правой кнопкой мыши и нажмите кнопку "Вид>в браузере".)

    Вы увидите список курсов и оценок для выбранного учащегося.

  4. Закройте браузер.

Обновление страницы Create

  1. В Controllers\StudentController.cs замените HttpPostAttribute Create метод действия следующим кодом. Этот код добавляет try-catch блок и удаляется ID из BindAttribute атрибута для шаблона:

    [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 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 свойств из коллекции.

    Вы удалили ID из атрибута Bind, так как ID это значение первичного ключа, которое SQL Server будет устанавливать автоматически при вставке строки. Входные данные пользователя не задают ID значение.

    Предупреждение системы безопасности. Атрибут ValidateAntiForgeryToken помогает предотвратить атаки на подделку межсайтовых запросов. Для этого требуется соответствующая Html.AntiForgeryToken() инструкция в представлении, которую вы увидите позже.

    Атрибут Bind является одним из способов защиты от чрезмерной публикации в сценариях создания. Например, предположим Student , что сущность содержит Secret свойство, которое не требуется задать этой веб-странице.

    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; }
    
        public virtual ICollection<Enrollment> Enrollments { get; set; }
    }
    

    Даже если у вас нет Secret поля на веб-странице, хакер может использовать средство, например fiddler, или написать код JavaScript для публикации Secret значения формы. BindAttribute Без ограничения атрибутов полей, используемых при создании Student экземпляра, привязка модели будет собирать это Secret значение формы и использовать его для создания экземпляра Student сущности. Таким образом, какое бы значение ни задал злоумышленник для поля Secret, оно будет обновлено в базе данных. На следующем рисунке показан инструмент fiddler, добавляющий Secret поле (со значением OverPost) в опубликованные значения формы.

    Снимок экрана, на котором показана вкладка Composer. В правом верхнем углу выполняется кругом красным цветом. В правом нижнем углу секрет равен post в красном углу.

    После этого значение "OverPost" будет успешно добавлено в свойство Secret вставленной строки, хотя вы не разрешали установку этого свойства на веб-странице.

    Рекомендуется использовать Include параметр с атрибутом для явного Bind перечисления полей. Также можно использовать Exclude параметр для блокировки полей, которые необходимо исключить. Include Причина более безопасна в том, что при добавлении нового свойства в сущность новое поле не автоматически защищено спискомExclude.

    Вы можете запретить переуступ в сценариях редактирования, считывая сущность из базы данных, а затем вызывая TryUpdateModel, передавая явный список разрешенных свойств. Это метод, используемый в этих руководствах.

    Альтернативным способом предотвращения перепоступа, предпочитаемого многими разработчиками, является использование моделей представления, а не классов сущностей с привязкой модели. Включайте только те свойства, которые требуется обновлять в модели представления. После завершения привязки модели MVC скопируйте свойства модели представления в экземпляр сущности, при необходимости с помощью такого средства, как AutoMapper. Использование базы данных. Запись в экземпляре сущности, чтобы задать состояние "Без изменений", а затем задать property("PropertyName"). IsModified на true для каждого свойства сущности, включенного в модель представления. Этот метод подходит для сценариев редактирования и создания.

    Кроме try-catch атрибутаBind, блок является единственным изменением, внесенным в шаблонный код. Если во время сохранения изменений перехватывается исключение, производное от DataException, отображается сообщение об общей ошибке. Исключения DataException иногда связаны с внешними факторами, а не с ошибкой при программировании приложения, поэтому рекомендуется попробовать повторить выполненные действия снова. В этом примере такое поведение не реализовано, однако в рабочем приложении, как правило, исключения заносятся в журнал. Дополнительные сведения см. в разделе Ведение журналов для анализа статьи Мониторинг и телеметрия (построение реальных облачных приложений для Azure).

    Код в Views\Student\Create.cshtml похож на то, что вы видели в Details.cshtml, за исключением того, что EditorFor и ValidationMessageFor вспомогательные элементы используются для каждого поля вместо DisplayForнего. Ниже приведен соответствующий код.

    <div class="form-group">
        @Html.LabelFor(model => model.LastName, new { @class = "control-label col-md-2" })
        <div class="col-md-10">
            @Html.EditorFor(model => model.LastName)
            @Html.ValidationMessageFor(model => model.LastName)
        </div>
    </div>
    

    Create.cshtml также включает в себя @Html.AntiForgeryToken()атрибут, который работает с ValidateAntiForgeryToken атрибутом в контроллере, чтобы предотвратить атаки на подделку межсайтовых запросов.

    Изменения не требуются в Create.cshtml.

  2. Запустите страницу, запустите программу, выбрав вкладку "Учащиеся", а затем нажмите кнопку "Создать".

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

    Это проверка на стороне сервера, которую вы получаете по умолчанию. В следующем руководстве вы узнаете, как добавить атрибуты, которые создают код для проверки на стороне клиента. В следующем выделенном коде показана проверка модели в методе Create .

    if (ModelState.IsValid)
    {
        db.Students.Add(student);
        db.SaveChanges();
        return RedirectToAction("Index");
    }
    
  4. Измените дату на допустимую и щелкните Create (Создать), чтобы добавить нового учащегося на страницу Index (Указатель).

  5. Закройте браузер.

Обновление метода HttpPost Edit

  1. Замените HttpPostAttribute Edit метод действия следующим кодом:

    [HttpPost, ActionName("Edit")]
    [ValidateAntiForgeryToken]
    public ActionResult EditPost(int? id)
    {
        if (id == null)
        {
            return new HttpStatusCodeResult(HttpStatusCode.BadRequest);
        }
        var studentToUpdate = db.Students.Find(id);
        if (TryUpdateModel(studentToUpdate, "",
           new string[] { "LastName", "FirstMidName", "EnrollmentDate" }))
        {
            try
            {
                db.SaveChanges();
    
                return RedirectToAction("Index");
            }
            catch (DataException /* dex */)
            {
                //Log the error (uncomment dex variable name 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(studentToUpdate);
    }
    

    Примечание.

    В Controllers\StudentController.cs HttpGet Editметод (один без HttpPost атрибута) использует Find метод для получения выбранной Student сущности, как показано в методеDetails. Изменять этот метод не нужно.

    Эти изменения реализуют рекомендации по обеспечению безопасности, чтобы предотвратить переположение, шаблон создал Bind атрибут и добавил сущность, созданную привязкой модели, к набору сущностей с измененным флагом. Этот код больше не рекомендуется, так как Bind атрибут очищает все предварительно существующие данные в полях, не перечисленных в параметре Include . В будущем шаблон контроллера MVC будет обновлен таким образом, чтобы он не создавал Bind атрибуты для методов Edit.

    Новый код считывает существующую сущность и вызывает TryUpdateModel обновление полей из входных данных пользователя в опубликованных данных формы. Автоматическое отслеживание изменений Entity Framework задает флаг EntityState.Modified для сущности. При вызове Modified метода SaveChanges флаг приводит к созданию инструкций SQL Для создания инструкций SQL для обновления строки базы данных. Конфликты параллелизма игнорируются, а все столбцы строки базы данных обновляются, включая те, которые пользователь не изменил. (В более позднем руководстве показано, как обрабатывать конфликты параллелизма, а если требуется обновить только отдельные поля в базе данных, можно задать для сущности значение EntityState.Без изменений и задайте для отдельных полей значение EntityState.Modified.)

    Чтобы предотвратить переположение, поля, которые требуется обновить на странице "Изменить", перечислены в TryUpdateModel параметрах. На данный момент другие поля не защищаются. Если включить в список поля, которые должен привязывать связыватель модели, это позволяет гарантировать, что при добавлении полей в модель данных в будущем они будут автоматически защищаться до тех пор, пока вы явно не добавите их сюда.

    В результате этих изменений сигнатура метода HttpPost Edit совпадает с методом редактирования HttpGet; поэтому вы переименовали метод EditPost.

    Совет

    Состояния сущностей и методы 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 " и "Локальные данные".

    Код HTML и Razor в Views\Student\Edit.cshtml похож на то, что вы видели в Create.cshtml, и никаких изменений не требуется.

  2. Запустите страницу, запустите программу, выбрав вкладку "Учащиеся ", а затем щелкните гиперссылку "Изменить ".

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

  4. Закройте браузер.

Обновление страницы "Delete" (Удаление)

В Controllers\StudentController.cs код шаблона для HttpGetAttribute Delete метода использует Find метод для получения выбранной Student сущности, как показано в Details и Edit методах. Тем не менее, чтобы реализовать настраиваемое сообщение об ошибке при сбое вызова метода SaveChanges, необходимо добавить некоторые функции в этот метод и соответствующее ему представление.

Как и в случае с операциями обновления и создания, операции удаления требуют двух методов действия. Метод, который вызывается в ответ на запрос GET, отображает представление, которое дает пользователю возможность утвердить или отменить операцию удаления. Если пользователь подтверждает ее, создается запрос POST. Когда это произойдет, метод вызывается, HttpPost Delete а затем этот метод фактически выполняет операцию удаления.

Вы добавите try-catch блок в HttpPostAttribute Delete метод для обработки любых ошибок, которые могут возникнуть при обновлении базы данных. Если возникает ошибка, HttpPostAttribute Delete метод вызывает HttpGetAttribute Delete метод, передавая его параметр, указывающий на то, что произошла ошибка. Затем HttpGetAttribute Delete метод переиграет страницу подтверждения вместе с сообщением об ошибке, что дает пользователю возможность отменить или повторить попытку.

  1. Замените HttpGetAttribute Delete метод действия следующим кодом, который управляет отчетами об ошибках:

    public ActionResult Delete(int? id, bool? saveChangesError=false)
    {
        if (id == null)
        {
            return new HttpStatusCodeResult(HttpStatusCode.BadRequest);
        }
        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 при HttpGet Delete вызове метода без предыдущего сбоя. При вызове HttpPost Delete метода в ответ на ошибку обновления базы данных параметр передается true в представление.

  2. Замените HttpPostAttribute Delete метод действия (именованный DeleteConfirmed) приведенным ниже кодом, который выполняет фактическую операцию удаления и перехватывает ошибки обновления базы данных.

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

    Этот код извлекает выбранную сущность, а затем вызывает метод Remove , чтобы задать состояние Deletedсущности. При вызове метода SaveChanges создается инструкция SQL DELETE. Вы также изменили имя метода действия с DeleteConfirmed на Delete. Шаблонный код с именем HttpPost Delete метода, который дает HttpPost методу DeleteConfirmed уникальную подпись. (Среда CLR требует, чтобы перегруженные методы имели разные параметры метода.) Теперь, когда подписи уникальны, вы можете придерживаться соглашения MVC и использовать то же имя для HttpPost методов и HttpGet удаления.

    Если повышение производительности в приложении с большим объемом является приоритетом, можно избежать ненужного SQL-запроса для получения строки, заменив строки кода, которые вызывают Find и Remove методы следующим кодом:

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

    Этот код создает Student экземпляр сущности, используя только значение первичного ключа, а затем задает состояние Deletedсущности. Это все, что платформе Entity Framework необходимо для удаления сущности.

    Как отмечалось, HttpGet Delete метод не удаляет данные. Выполнение операции удаления в ответ на запрос GET (или для этого, выполнение любой операции редактирования, операции создания или любой другой операции, которая изменяет данные) создает риск безопасности.

  3. В 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>
    
  4. Запустите страницу, запустите программу, выбрав вкладку "Учащиеся ", а затем щелкните гиперссылку "Удалить ".

  5. Выберите "Удалить" на странице, которая говорит , что вы действительно хотите удалить это?.

    Страница индекса отображается без удаленного учащегося. (Вы увидите пример кода обработки ошибок в действии в руководстве по параллелизму.)

Закрытие подключений к базам данных

Чтобы закрыть подключения к базе данных и освободить ресурсы, которые они хранятся, как можно скорее, удалите экземпляр контекста при завершении работы с ним. Именно поэтому шаблонный код предоставляет метод Dispose в конце StudentController класса в StudentController.cs, как показано в следующем примере:

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

Базовый Controller класс уже реализует IDisposable интерфейс, поэтому этот код просто добавляет переопределение в Dispose(bool) метод для явного удаления экземпляра контекста.

Обработка транзакций

По умолчанию платформа Entity Framework реализует транзакции неявно. В сценариях, когда вы вносите изменения в несколько строк или таблиц, а затем вызываете SaveChanges, Entity Framework автоматически гарантирует, что все изменения успешно или все завершаются сбоем. Если ошибка происходит после того, как были выполнены некоторые изменения, эти изменения автоматически откатываются. В сценариях, где требуется больше управления, например, если требуется включить операции вне Entity Framework в транзакцию, см. статью "Работа с транзакциями".

Получение кода

Скачивание завершенного проекта

Дополнительные ресурсы

Теперь у вас есть полный набор страниц, выполняющих простые операции CRUD для Student сущностей. Вы использовали вспомогательные средства MVC для создания элементов пользовательского интерфейса для полей данных. Дополнительные сведения о вспомогательных службах MVC см. в разделе "Отрисовка формы с помощью вспомогательных средств HTML" (статья предназначена для MVC 3, но по-прежнему актуальна для MVC 5).

Ссылки на другие ресурсы EF 6 можно найти в ASP.NET доступ к данным — рекомендуемые ресурсы.

Следующие шаги

Изучив это руководство, вы:

  • Страница "Создание сведений"
  • Обновление страницы создания
  • Обновлен метод HttpPost Edit
  • Обновление страницы удаления
  • Закрытие подключений к базам данных
  • Обрабатываемые транзакции

Перейдите к следующей статье, чтобы узнать, как добавить сортировку, фильтрацию и разбиение по страницам в проект.