"
ASP.NET (snapshot 2017) Microsoft documentation and samples

Advanced Entity Framework Scenarios for an MVC Web Application (10 of 10)

by Tom Dykstra

Download Completed Project

The Contoso University sample web application demonstrates how to create ASP.NET MVC 4 applications using the Entity Framework 5 Code First and Visual Studio 2012. For information about the tutorial series, see the first tutorial in the series. You can start the tutorial series from the beginning or download a starter project for this chapter and start here.

[!NOTE]

If you run into a problem you can’t resolve, download the completed chapter and try to reproduce your problem. You can generally find the solution to the problem by comparing your code to the completed code. For some common errors and how to solve them, see Errors and Workarounds.

In the previous tutorial you implemented the repository and unit of work patterns. This tutorial covers the following topics:

For most of these topics, you’ll work with pages that you already created. To use raw SQL to do bulk updates you’ll create a new page that updates the number of credits of all courses in the database:

Update_Course_Credits_initial_page
Update_Course_Credits_initial_page

And to use a no-tracking query you’ll add new validation logic to the Department Edit page:

Department_Edit_page_with_duplicate_administrator_error_message
Department_Edit_page_with_duplicate_administrator_error_message

Performing Raw SQL Queries

The Entity Framework Code First API includes methods that enable you to pass SQL commands directly to the database. You have the following options:

One of the advantages of using the Entity Framework is that it avoids tying your code too closely to a particular method of storing data. It does this by generating SQL queries and commands for you, which also frees you from having to write them yourself. But there are exceptional scenarios when you need to run specific SQL queries that you have manually created, and these methods make it possible for you to handle such exceptions.

As is always true when you execute SQL commands in a web application, you must take precautions to protect your site against SQL injection attacks. One way to do that is to use parameterized queries to make sure that strings submitted by a web page can’t be interpreted as SQL commands. In this tutorial you’ll use parameterized queries when integrating user input into a query.

Calling a Query that Returns Entities

Suppose you want the GenericRepository class to provide additional filtering and sorting flexibility without requiring that you create a derived class with additional methods. One way to achieve that would be to add a method that accepts a SQL query. You could then specify any kind of filtering or sorting you want in the controller, such as a Where clause that depends on a joins or a subquery. In this section you’ll see how to implement such a method.

Create the GetWithRawSql method by adding the following code to GenericRepository.cs:

[!code-csharpMain]

   1:  public virtual IEnumerable<TEntity> GetWithRawSql(string query, params object[] parameters)
   2:  {
   3:      return dbSet.SqlQuery(query, parameters).ToList();
   4:  }

In CourseController.cs, call the new method from the Details method, as shown in the following example:

[!code-csharpMain]

   1:  public ActionResult Details(int id)
   2:  {
   3:      var query = "SELECT * FROM Course WHERE CourseID = @p0";
   4:      return View(unitOfWork.CourseRepository.GetWithRawSql(query, id).Single());
   5:  }

In this case you could have used the GetByID method, but you’re using the GetWithRawSql method to verify that the GetWithRawSQL method works.

Run the Details page to verify that the select query works (select the Course tab and then Details for one course).

Course_Details_page
Course_Details_page

Calling a Query that Returns Other Types of Objects

Earlier you created a student statistics grid for the About page that showed the number of students for each enrollment date. The code that does this in HomeController.cs uses LINQ:

[!code-csharpMain]

   1:  var data = from student in db.Students
   2:             group student by student.EnrollmentDate into dateGroup
   3:             select new EnrollmentDateGroup()
   4:             {
   5:                 EnrollmentDate = dateGroup.Key,
   6:                 StudentCount = dateGroup.Count()
   7:             };

Suppose you want to write the code that retrieves this data directly in SQL rather than using LINQ. To do that you need to run a query that returns something other than entity objects, which means you need to use the Database.SqlQuery method.

In HomeController.cs, replace the LINQ statement in the About method with the following code:

[!code-csharpMain]

   1:  var query = "SELECT EnrollmentDate, COUNT(*) AS StudentCount "
   2:      + "FROM Person "
   3:      + "WHERE EnrollmentDate IS NOT NULL "
   4:      + "GROUP BY EnrollmentDate";
   5:  var data = db.Database.SqlQuery<EnrollmentDateGroup>(query);

Run the About page. It displays the same data it did before.

About_page
About_page

Calling an Update Query

Suppose Contoso University administrators want to be able to perform bulk changes in the database, such as changing the number of credits for every course. If the university has a large number of courses, it would be inefficient to retrieve them all as entities and change them individually. In this section you’ll implement a web page that allows the user to specify a factor by which to change the number of credits for all courses, and you’ll make the change by executing a SQL UPDATE statement. The web page will look like the following illustration:

Update_Course_Credits_initial_page
Update_Course_Credits_initial_page

In the previous tutorial you used the generic repository to read and update Course entities in the Course controller. For this bulk update operation, you need to create a new repository method that isn’t in the generic repository. To do that, you’ll create a dedicated CourseRepository class that derives from the GenericRepository class.

In the DAL folder, create CourseRepository.cs and replace the existing code with the following code:

[!code-csharpMain]

   1:  using System;
   2:  using ContosoUniversity.Models;
   3:   
   4:  namespace ContosoUniversity.DAL
   5:  {
   6:      public class CourseRepository : GenericRepository<Course>
   7:      {
   8:          public CourseRepository(SchoolContext context)
   9:              : base(context)
  10:          {
  11:          }
  12:   
  13:          public int UpdateCourseCredits(int multiplier)
  14:          {
  15:              return context.Database.ExecuteSqlCommand("UPDATE Course SET Credits = Credits * {0}", multiplier);
  16:          }
  17:   
  18:      }
  19:  }

In UnitOfWork.cs, change the Course repository type from GenericRepository<Course> to CourseRepository:

[!code-csharpMain]

   1:  private CourseRepository courseRepository;

[!code-csharpMain]

   1:  public CourseRepository CourseRepository
   2:  {
   3:      get
   4:      {
   5:   
   6:          if (this.courseRepository == null)
   7:          {
   8:              this.courseRepository = new CourseRepository(context);
   9:          }
  10:          return courseRepository;
  11:      }
  12:  }

In CourseContoller.cs, add an UpdateCourseCredits method:

[!code-csharpMain]

   1:  public ActionResult UpdateCourseCredits(int? multiplier)
   2:  {
   3:      if (multiplier != null)
   4:      {
   5:          ViewBag.RowsAffected = unitOfWork.CourseRepository.UpdateCourseCredits(multiplier.Value);
   6:      }
   7:      return View();
   8:  }

This method will be used for both HttpGet and HttpPost. When the HttpGet UpdateCourseCredits method runs, the multiplier variable will be null and the view will display an empty text box and a submit button, as shown in the preceding illustration.

When the Update button is clicked and the HttpPost method runs, multiplier will have the value entered in the text box. The code then calls the repository UpdateCourseCredits method, which returns the number of affected rows, and that value is stored in the ViewBag object. When the view receives the number of affected rows in the ViewBag object, it displays that number instead of the text box and submit button, as shown in the following illustration:

Update_Course_Credits_rows_affected_page
Update_Course_Credits_rows_affected_page

Create a view in the *Viewsfolder for the Update Course Credits page:

Add_View_dialog_box_for_Update_Course_Credits
Add_View_dialog_box_for_Update_Course_Credits

In Views.cshtml, replace the template code with the following code:

[!code-cshtmlMain]

   1:  @model ContosoUniversity.Models.Course
   2:   
   3:  @{
   4:      ViewBag.Title = "UpdateCourseCredits";
   5:  }
   6:   
   7:  <h2>Update Course Credits</h2>
   8:   
   9:  @if (ViewBag.RowsAffected == null)
  10:  {
  11:      using (Html.BeginForm())
  12:      {
  13:          <p>
  14:              Enter a number to multiply every course's credits by: @Html.TextBox("multiplier")
  15:          </p>
  16:          <p>
  17:              <input type="submit" value="Update" />
  18:          </p>
  19:      }
  20:  }
  21:  @if (ViewBag.RowsAffected != null)
  22:  {
  23:      <p>
  24:          Number of rows updated: @ViewBag.RowsAffected
  25:      </p>
  26:  }
  27:  <div>
  28:      @Html.ActionLink("Back to List", "Index")
  29:  </div>

Run the UpdateCourseCredits method by selecting the Courses tab, then adding “/UpdateCourseCredits” to the end of the URL in the browser’s address bar (for example: http://localhost:50205/Course/UpdateCourseCredits). Enter a number in the text box:

Update_Course_Credits_initial_page_with_2_entered
Update_Course_Credits_initial_page_with_2_entered

Click Update. You see the number of rows affected:

Update_Course_Credits_rows_affected_page
Update_Course_Credits_rows_affected_page

Click Back to List to see the list of courses with the revised number of credits.

Courses_Index_page_showing_revised_credits
Courses_Index_page_showing_revised_credits

For more information about raw SQL queries, see Raw SQL Queries on the Entity Framework team blog.

No-Tracking Queries

When a database context retrieves database rows and creates entity objects that represent them, by default it keeps track of whether the entities in memory are in sync with what’s in the database. The data in memory acts as a cache and is used when you update an entity. This caching is often unnecessary in a web application because context instances are typically short-lived (a new one is created and disposed for each request) and the context that reads an entity is typically disposed before that entity is used again.

You can specify whether the context tracks entity objects for a query by using the AsNoTracking method. Typical scenarios in which you might want to do that include the following:

In this section you’ll implement business logic that illustrates the second of these scenarios. Specifically, you’ll enforce a business rule that says that an instructor can’t be the administrator of more than one department.

In DepartmentController.cs, add a new method that you can call from the Edit and Create methods to make sure that no two departments have the same administrator:

[!code-csharpMain]

   1:  private void ValidateOneAdministratorAssignmentPerInstructor(Department department)
   2:  {
   3:      if (department.PersonID != null)
   4:      {
   5:          var duplicateDepartment = db.Departments
   6:              .Include("Administrator")
   7:              .Where(d => d.PersonID == department.PersonID)
   8:              .FirstOrDefault();
   9:          if (duplicateDepartment != null && duplicateDepartment.DepartmentID != department.DepartmentID)
  10:          {
  11:              var errorMessage = String.Format(
  12:                  "Instructor {0} {1} is already administrator of the {2} department.",
  13:                  duplicateDepartment.Administrator.FirstMidName,
  14:                  duplicateDepartment.Administrator.LastName,
  15:                  duplicateDepartment.Name);
  16:              ModelState.AddModelError(string.Empty, errorMessage);
  17:          }
  18:      }
  19:  }

Add code in the try block of the HttpPost Edit method to call this new method if there are no validation errors. The try block now looks like the following example:

[!code-csharpMain]

   1:  [HttpPost]
   2:  [ValidateAntiForgeryToken]
   3:  public ActionResult Edit(
   4:     [Bind(Include = "DepartmentID, Name, Budget, StartDate, RowVersion, PersonID")]
   5:      Department department)
   6:  {
   7:     try
   8:     {
   9:        if (ModelState.IsValid)
  10:        {
  11:           ValidateOneAdministratorAssignmentPerInstructor(department);
  12:        }
  13:   
  14:        if (ModelState.IsValid)
  15:        {
  16:           db.Entry(department).State = EntityState.Modified;
  17:           db.SaveChanges();
  18:           return RedirectToAction("Index");
  19:        }
  20:     }
  21:     catch (DbUpdateConcurrencyException ex)
  22:     {
  23:        var entry = ex.Entries.Single();
  24:        var clientValues = (Department)entry.Entity;

Run the Department Edit page and try to change a department’s administrator to an instructor who is already the administrator of a different department. You get the expected error message:

Department_Edit_page_with_duplicate_administrator_error_message
Department_Edit_page_with_duplicate_administrator_error_message

Now run the Department Edit page again and this time change the Budget amount. When you click Save, you see an error page:

Department_Edit_page_with_object_state_manager_error_message
Department_Edit_page_with_object_state_manager_error_message

The exception error message is “An object with the same key already exists in the ObjectStateManager. The ObjectStateManager cannot track multiple objects with the same key.” This happened because of the following sequence of events:

One solution to this problem is to keep the context from tracking in-memory department entities retrieved by the validation query. There’s no disadvantage to doing this, because you won’t be updating this entity or reading it again in a way that would benefit from it being cached in memory.

In DepartmentController.cs, in the ValidateOneAdministratorAssignmentPerInstructor method, specify no tracking, as shown in the following:

[!code-csharpMain]

   1:  var duplicateDepartment = db.Departments
   2:     .Include("Administrator")
   3:     .Where(d => d.PersonID == department.PersonID)
   4:     .AsNoTracking()
   5:     .FirstOrDefault();

Repeat your attempt to edit the Budget amount of a department. This time the operation is successful, and the site returns as expected to the Departments Index page, showing the revised budget value.

Examining Queries Sent to the Database

Sometimes it’s helpful to be able to see the actual SQL queries that are sent to the database. To do this, you can examine a query variable in the debugger or call the query’s ToString method. To try this out, you’ll look at a simple query and then look at what happens to it as you add options such eager loading, filtering, and sorting.

In Controllers/CourseController, replace the Index method with the following code:

[!code-csharpMain]

   1:  public ViewResult Index()
   2:  {
   3:      var courses = unitOfWork.CourseRepository.Get();
   4:      return View(courses.ToList());
   5:  }

Now set a breakpoint in GenericRepository.cs on the return query.ToList(); and the return orderBy(query).ToList(); statements of the Get method. Run the project in debug mode and select the Course Index page. When the code reaches the breakpoint, examine the query variable. You see the query that’s sent to SQL Server. It’s a simple Select statement:

[!code-sqlMain]

   1:  {SELECT 
   2:  [Extent1].[CourseID] AS [CourseID], 
   3:  [Extent1].[Title] AS [Title], 
   4:  [Extent1].[Credits] AS [Credits], 
   5:  [Extent1].[DepartmentID] AS [DepartmentID]
   6:  FROM [Course] AS [Extent1]}

Queries can be too long to display in the debugging windows in Visual Studio. To see the entire query, you can copy the variable value and paste it into a text editor:

Copy_value_of_variable_in_debug_mode
Copy_value_of_variable_in_debug_mode

Now you’ll add a drop-down list to the Course Index page so that users can filter for a particular department. You’ll sort the courses by title, and you’ll specify eager loading for the Department navigation property. In CourseController.cs, replace the Index method with the following code:

[!code-csharpMain]

   1:  public ActionResult Index(int? SelectedDepartment)
   2:  {
   3:      var departments = unitOfWork.DepartmentRepository.Get(
   4:          orderBy: q => q.OrderBy(d => d.Name));
   5:      ViewBag.SelectedDepartment = new SelectList(departments, "DepartmentID", "Name", SelectedDepartment);
   6:   
   7:      int departmentID = SelectedDepartment.GetValueOrDefault(); 
   8:      return View(unitOfWork.CourseRepository.Get(
   9:          filter: d => !SelectedDepartment.HasValue || d.DepartmentID == departmentID,
  10:          orderBy: q => q.OrderBy(d => d.CourseID),
  11:          includeProperties: "Department"));
  12:  }

The method receives the selected value of the drop-down list in the SelectedDepartment parameter. If nothing is selected, this parameter will be null.

A SelectList collection containing all departments is passed to the view for the drop-down list. The parameters passed to the SelectList constructor specify the value field name, the text field name, and the selected item.

For the Get method of the Course repository, the code specifies a filter expression, a sort order, and eager loading for the Department navigation property. The filter expression always returns true if nothing is selected in the drop-down list (that is, SelectedDepartment is null).

In Views.cshtml, immediately before the opening table tag, add the following code to create the drop-down list and a submit button:

[!code-cshtmlMain]

   1:  @using (Html.BeginForm())
   2:  {
   3:      <p>Select Department: @Html.DropDownList("SelectedDepartment","All")   
   4:      <input type="submit" value="Filter" /></p>
   5:  }

With the breakpoints still set in the GenericRepository class, run the Course Index page. Continue through the first two times that the code hits a breakpoint, so that the page is displayed in the browser. Select a department from the drop-down list and click Filter:

Course_Index_page_with_department_selected
Course_Index_page_with_department_selected

This time the first breakpoint will be for the departments query for the drop-down list. Skip that and view the query variable the next time the code reaches the breakpoint in order to see what the Course query now looks like. You’ll see something like the following:

[!code-sqlMain]

   1:  {SELECT 
   2:  [Extent1].[CourseID] AS [CourseID], 
   3:  [Extent1].[Title] AS [Title], 
   4:  [Extent1].[Credits] AS [Credits], 
   5:  [Extent1].[DepartmentID] AS [DepartmentID], 
   6:  [Extent2].[DepartmentID] AS [DepartmentID1], 
   7:  [Extent2].[Name] AS [Name], 
   8:  [Extent2].[Budget] AS [Budget], 
   9:  [Extent2].[StartDate] AS [StartDate], 
  10:  [Extent2].[PersonID] AS [PersonID], 
  11:  [Extent2].[Timestamp] AS [Timestamp]
  12:  FROM  [Course] AS [Extent1]
  13:  INNER JOIN [Department] AS [Extent2] ON [Extent1].[DepartmentID] = [Extent2].[DepartmentID]
  14:  WHERE (@p__linq__0 IS NULL) OR ([Extent1].[DepartmentID] = @p__linq__1)}

You can see that the query is now a JOIN query that loads Department data along with the Course data, and that it includes a WHERE clause.

Working with Proxy Classes

When the Entity Framework creates entity instances (for example, when you execute a query), it often creates them as instances of a dynamically generated derived type that acts as a proxy for the entity. This proxy overrides some virtual properties of the entity to insert hooks for performing actions automatically when the property is accessed. For example, this mechanism is used to support lazy loading of relationships.

Most of the time you don’t need to be aware of this use of proxies, but there are exceptions:

For more information, see Working with Proxies on the Entity Framework team blog.

Disabling Automatic Detection of Changes

The Entity Framework determines how an entity has changed (and therefore which updates need to be sent to the database) by comparing the current values of an entity with the original values. The original values are stored when the entity was queried or attached. Some of the methods that cause automatic change detection are the following:

If you’re tracking a large number of entities and you call one of these methods many times in a loop, you might get significant performance improvements by temporarily turning off automatic change detection using the AutoDetectChangesEnabled property. For more information, see Automatically Detecting Changes.

Disabling Validation When Saving Changes

When you call the SaveChanges method, by default the Entity Framework validates the data in all properties of all changed entities before updating the database. If you’ve updated a large number of entities and you’ve already validated the data, this work is unnecessary and you could make the process of saving the changes take less time by temporarily turning off validation. You can do that using the ValidateOnSaveEnabled property. For more information, see Validation.

Summary

This completes this series of tutorials on using the Entity Framework in an ASP.NET MVC application. Links to other Entity Framework resources can be found in the ASP.NET Data Access Content Map.

For more information about how to deploy your web application after you’ve built it, see ASP.NET Deployment Content Map in the MSDN Library.

For information about other topics related to MVC, such as authentication and authorization, see the MVC Recommended Resources.

Acknowledgments

VB

When the tutorial was originally produced, we provided both C# and VB versions of the completed download project. With this update we are providing a C# downloadable project for each chapter to make it easier to get started anywhere in the series, but due to time limitations and other priorities we did not do that for VB. If you build a VB project using these tutorials and would be willing to share that with others, please let us know.

Errors and Workarounds

Cannot create/shadow copy

Error Message:

Cannot create/shadow copy ‘DotNetOpenAuth.OpenId’ when that file already exists.

Solution:

Wait a few seconds and refresh the page.

Update-Database not recognized

Error Message:

The term ‘Update-Database’ is not recognized as the name of a cmdlet, function, script file, or operable program. Check the spelling of the name, or if a path was included, verify that the path is correct and try again.(From the Update-Database command in the PMC.)

Solution:

Exit Visual Studio. Reopen project and try again.

Validation failed

Error Message:

Validation failed for one or more entities. See ‘EntityValidationErrors’ property for more details. (From the Update-Database command in the PMC.)

Solution:

One cause of this problem is validation errors when the Seed method runs. See Seeding and Debugging Entity Framework (EF) DBs for tips on debugging the Seed method.

HTTP 500.19 error

Error Message:

HTTP Error 500.19 - Internal Server Error
The requested page cannot be accessed because the related configuration data for the page is invalid.

Solution:

One way you can get this error is from having multiple copies of the solution, each of them using the same port number. You can usually solve this problem by exiting all instances of Visual Studio, then restarting the project your working on. If that doesn’t work, try changing the port number. Right click on the project file and then click properties. Select the Web tab and then change the port number in the Project Url text box.

Error locating SQL Server instance

Error Message:

A network-related or instance-specific error occurred while establishing a connection to SQL Server. The server was not found or was not accessible. Verify that the instance name is correct and that SQL Server is configured to allow remote connections. (provider: SQL Network Interfaces, error: 26 - Error Locating Server/Instance Specified)

Solution:

Check connection string. If you have manually deleted the database, change the name of the database in the construction string.

Previous Next



Comments ( )
Link to this page: //www.vb-net.com/AspNet-DocAndSamples-2017/aspnet/mvc/overview/older-versions/getting-started-with-ef-5-using-mvc-4/advanced-entity-framework-scenarios-for-an-mvc-web-application.htm
< THANKS ME>