This is an extension of a series of articles devoted to developing using the Entity Framework and ASP.NET MVC 3. The first chapter can be found at the following link:
Creating the Entity Framework data model for an ASP.NET MVC application .
In the previous tutorial, we created an MVC application that can store and display data using the Entity Framework and SQL Server Compact. In this lesson, we will look at creating and configuring a CRUD (create, read, update, delete) functionality that MVC scaffolding automatically creates for you in controllers and views.
Note A common practice is to implement a “repository” pattern to create an abstraction layer between the controller and the data access layer. But it will be later, in the later lessons (
Implementing the Repository and the Unit of Work Patterns ).
')
The following pages will be created in this lesson:




Creating the Details page
The Details page will display the contents of the Enrollements collection in an HTML table.
In
Controllers \ StudentController . The cs method for the Details view is the following code:
public ViewResult Details(int id) { Student student = db.Students.Find(id); return View(student); }
The Find method is used to retrieve a single Student entity corresponding to the id parameter passed to the method. The id value is taken from the query string of the link on the Details page.
Open
Views \ Student \ Details . cshtml Each field is displayed by the DisplayFor helper:
<div class="display-label">LastName</div> <div class="display-field"> @Html.DisplayFor(model => model.LastName) </div>
To display a list of enrollments, add the following code after the EnrollmentDate field, before the closing tag of the 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>
This code loops through the entities in the Enrollments navigation property. For each Enrollment entity, it displays the course name and grade. The name of the course is taken from the Course entity contained in the Course navigation property of the entity Enrollments. All this data is automatically unloaded from the database as needed (in other words, lazy loading is used. You did not specify an
eager loading for the Courses navigation property property, so when you first access this property, a database request will be executed to retrieve data. Read more lazy loading and eager loading can be here
Reading Related Data .)
Click on the
Students tab and click on the
Details link.

Creating a Create Page
In
Controllers \ StudentController.cs , replace the code for the HttpPost Create method:
[HttpPost] public ActionResult Create(Student student) { try { if (ModelState.IsValid) { db.Students.Add(student); db.SaveChanges(); return RedirectToAction("Index"); } } catch (DataException) { //Log the error (add a variable name after DataException) ModelState.AddModelError("", "Unable to save changes. Try again, and if the problem persists see your system administrator."); } return View(student); }
Thus, we add the Student entity created by the ASP.NET MVC Model Binder to the corresponding set of entities and then save the changes to the base. (
Model binder - ASP.NET MVC functionality that makes it easier to work with data coming from a form. Model binder converts data from a form into the corresponding .NET Framework data types and passes it as parameters to the desired method. In this case, model binder instantiates the Student entity using property values from the Form collection.)
The try-catch block is the only difference between our version and what was automatically created. If an exception inherited from DataException is caught while saving changes, a standard error message is displayed. Such errors are usually caused by something external rather than a programmer error, so the user is simply asked to try again. The code in
Views \ Student \ Create . cshtml is similar to the
Details code
. cshtml except EditorFor and ValidationMessageFor, used for each field instead of the DisplayFor helper. The following code is an example:
<div class="editor-label"> @Html.LabelFor(model => model.LastName) </div> <div class="editor-field"> @Html.EditorFor(model => model.LastName) @Html.ValidationMessageFor(model => model.LastName) </div>
In
Create . cshtml no need to make changes.
Click on the
Students tab and on
Create New .

Data validation is enabled by default. Enter names and an incorrect date and click Create to see the error.

In this case, you see client-side data validation implemented using JavaScript. Data verification on the server side is also implemented, and even if the data verification on the client side misses bad data, they will be intercepted on the server side and an exception will be thrown.
Change the date to the correct one, for example, on 9/1/2005 and click
Create to see the new student on the
Index page.

Creating an Edit Page
In
Controllers \ StudentController.cs, the HttpGet Edit method (one that without the HttpPost attribute) uses the Find method to retrieve the selected Student entity. There is no need to change the code of this method.
Replace the code for the HttpPost Edit method with the following code:
[HttpPost] public ActionResult Edit(Student student) { try { if (ModelState.IsValid) { db.Entry(student).State = EntityState.Modified; db.SaveChanges(); return RedirectToAction("Index"); } } catch (DataException) {
The code is similar to what was in the HttpPost Create method, but instead of adding an entity to the set, this code sets an entity property that determines whether it was changed. When you call SaveChanges, the Modified property instructs the Entity Framework to create a SQL query to update the record in the database. All columns of the record will be updated, including those that the user did not touch. Concurrency issues are ignored. (You can read about concurrency issues in
Handling Concurrency .)
Entity States and Attach and SaveChanges Methods
The database context monitors the synchronization of the entities in memory with the corresponding entries in the database, and this information determines what happens when the SaveChanges method is called. For example, when a new entity is transferred to the Add method, the state of this entity changes to Added. Then, when you call the SaveChanges method, the database context initiates the execution of an INSERT SQL query.
The state of an entity can be defined as:
- Added. Entities are not yet in the database. The SaveChanges method initiates the execution of an INSERT request.
- Unchanged. When you call SaveChanges, nothing happens. This state of the entity when retrieving it from the database.
- Modified. Entity property values have been changed, SaveChanges executes an UPDATE request.
- Deleted. Entity is marked for deletion, SaveChanges executes a DELETE request.
- Detached. The state of the entity is not monitored by the database context.
In the desktop application, the status changes automatically. In this type of application, you extract the entity and change the values of any properties, which leads to a state change to Modified. After calling SaveChanges, the Entity Framework generates a SQL UPDATE query that updates only those properties whose values have been changed.
However, the algorithm is violated in the web application, because the database context instance that retrieves the entity is destroyed after the page reloads. When HttpPost Edit, a new request occurs and you have a new instance of the context, so you need to manually change the state of the entity to Modified. After that, when you call SaveChanges, the Entity Framework will update all the columns of the record in the database, since the context no longer knows which properties have been specifically changed.
If you want Update to change only user-edited fields, you can somehow save the original values (for example, hidden form fields) by making them available at the time HttpPost Edit is called. Thus, you can create a Student entity using the original values, call the Attach method with the original version of the entity, update the values of the entity, and call SaveChanges. For more information, refer to the
Add / Attach and Entity States materials and post on the Entity Framework
Local Data development team blog.
Code in
Views \ Student \ Edit . cshtml is similar to the code in
Create . cshtml , no changes are necessary.
Click on the
Students tab and then on the
Edit link.

Change the values and click
Save .

Creating a Delete Page
In
Controllers \ StudentController.cs, the HttpGet Delete method uses the Find method to retrieve the selected Student entity, as well as in Details and Edit before. To implement your error message in case of an error in the SaveChanges call, you need to add additional functionality to the method and the corresponding view.
As with update and create operations, the delete operation also needs two methods. The method called in response to a GET request shows the user a view that allows you to confirm or cancel the deletion. If the user confirms the deletion, a POST request is created and the HttpPost Delete method is called.
You need to add the try-catch exception block to the code of the HttpPost Delete method to handle errors that may occur when updating the database. If an error occurs, the HttpPost Delete method calls the HttpGet Delete method, passing it the parameter that signals the error. The HttpGet Delete method again generates a delete confirmation page and an error text.
Replace the code of the HttpGet Delete method with the following code that allows you to handle errors:
public ActionResult Delete(int id, bool? saveChangesError) { if (saveChangesError.GetValueOrDefault()) { ViewBag.ErrorMessage = "Unable to save changes. Try again, and if the problem persists see your system administrator."; } return View(db.Students.Find(id)); }
This code accepts an optional Boolean type parameter that signals the occurrence of an error. This parameter is null (false) after calling HttpGet Delete and true when calling HttpPost Delete.
Replace the code for the HttpPost Delete (DeleteConfirmed) method with the following code that performs the deletion and handles the errors:
[HttpPost, ActionName("Delete")] public ActionResult DeleteConfirmed(int id) { try { Student student = db.Students.Find(id); db.Students.Remove(student); db.SaveChanges(); } catch (DataException) { //Log the error (add a variable name after DataException) return RedirectToAction("Delete", new System.Web.Routing.RouteValueDictionary { { "id", id }, { "saveChangesError", true } }); } return RedirectToAction("Index"); }
The code returns the selected entity and calls the Remove method, which changes the state of the entity to Deleted. When you call SaveChanges, a DELETE SQL query is generated.
If performance is a priority, then you can do without unnecessary SQL queries that return a record by replacing the code that calls the Find and Remove methods:
Student studentToDelete = new Student() { StudentID = id }; db.Entry(studentToDelete).State = EntityState.Deleted;
With this code, we instantiate the Student entity using only the primary key value and then define the state of the entity as Deleted. This is all Entity Framework needs to delete an entity.
As mentioned, the HttpGet Delete method does not delete data. Deleting data in response to a GET request (or, also, editing, creating and any other action with data) creates a security breach. For details, see
ASP.NET MVC. Tip # 46 - Stephen Walther's blog post.
In
Views \ Student \ Delete . cshtml add the following code between h2 and h3:
<p class = "error"> @ ViewBag.ErrorMessage </ p>
Click on the
Students tab and then on the
Delete link:

Click
Delete . The Index page is loaded, already without the student we deleted (you will see an example of processing the code in the method in the
Handling Concurrency lesson.)
Make sure there are no open database connections left.
To be sure that all connections to the database have been properly closed and the resources occupied by them are released, you need to make sure that the context is destroyed. Therefore, you can find the Dispose method at the end of the StudentController controller class in the
StudentController . cs :
protected override void Dispose(bool disposing) { db.Dispose(); base.Dispose(disposing); }
The base Controller class implements the IDisposable interface, so this code simply overrides (override) the Dispose (bool) method for external deletion of the context instance.
Now you have everything that implements simple CRUD operations for Student entities. In the next lesson, we will extend the functionality of the Index page by adding sorting and pagination.
Thanks for the help in the translation of Alexander Belotserkovsky (
ahriman ).