In the previous tutorial you implemented the repository and unit of work patterns. This tutorial covers the following topics:
And to use a no-tracking query you'll add new validation logic to the Department Edit page:
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.
Create the
Run the Details page to verify that the select query works (select the Course tab and thenDetails for one course).
In HomeController.cs, replace the LINQ statement in the
In the previous tutorial you used the generic repository to read and update
In the DAL folder, create CourseRepository.cs and replace the existing code with the following code:
When the Update button is clicked and the
Create a view in the Views\Course folder for the Update Course Credits page:
In Views\Course\UpdateCourseCredits.cshtml, replace the existing code with the following code:
Click Update. You see the number of rows affected:
Click Back to List to see the list of courses with the revised number of credits.
For more information about raw SQL queries, see Raw SQL Queries on the Entity Framework team blog.
You can specify whether the context tracks entity objects for a query by using the
In DepartmentController.cs, add a new method that you can call from the
Now run the Department Edit page again and this time change the Budget amount. When you click Save, you see an error page:
The exception error message is "
In DepartmentController.cs, in the
In Controllers/CourseController, replace the
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
A
For the
In Views\Course\Index.cshtml, immediately before the opening
This time the first breakpoint will be for the departments query for the drop-down list. Skip that and view the
Most of the time you don't need to be aware of this use of proxies, but there are exceptions:
- Performing raw SQL queries.
- Performing no-tracking queries.
- Examining queries sent to the database.
- Working with proxy classes.
- Disabling automatic detection of changes.
- Disabling validation when saving changes.
And to use a no-tracking query you'll add new validation logic to the Department Edit page:
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:- Use the
DbSet.SqlQuery
method for queries that return entity types. The returned objects must be of the type expected by theDbSet
object, and they are automatically tracked by the database context unless you turn tracking off. (See the following section about theAsNoTracking
method.) - Use the
DbDatabase.SqlQuery
method for queries that return types that aren't entities. The returned data isn't tracked by the database context, even if you use this method to retrieve entity types. - Use the
DbDatabase.SqlCommand
for non-query commands.
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 theGenericRepository
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 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:public virtual IEnumerable<TEntity> GetWithRawSql(string query, params object[] parameters) { return dbSet.SqlQuery(query, parameters).ToList(); }In CourseController.cs, call the new method from the
Details
method, as shown in the following example:public ActionResult Details(int id) { var query = "SELECT * FROM Course WHERE CourseID = @p0"; return View(unitOfWork.CourseRepository.GetWithRawSql(query, id).Single()); }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 thenDetails for one course).
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:var data = from student in db.Students group student by student.EnrollmentDate into dateGroup select new EnrollmentDateGroup() { EnrollmentDate = dateGroup.Key, StudentCount = dateGroup.Count() };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:var query = "SELECT EnrollmentDate, COUNT(*) AS StudentCount " + "FROM Person " + "WHERE EnrollmentDate IS NOT NULL " + "GROUP BY EnrollmentDate"; var data = db.Database.SqlQuery<EnrollmentDateGroup>(query);Run the About page. It displays the same data it did before.
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 SQLUPDATE
statement. The web page will look like the following illustration: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 dedicatedCourseRepository
class that derives from the GenericRepository
class.In the DAL folder, create CourseRepository.cs and replace the existing code with the following code:
using System; using ContosoUniversity.Models; namespace ContosoUniversity.DAL{ public class CourseRepository : GenericRepository<Course> { public CourseRepository(SchoolContext context) : base(context) { } public int UpdateCourseCredits(int multiplier) { return context.Database.ExecuteSqlCommand("UPDATE Course SET Credits = Credits * {0}", multiplier); } } }In UnitOfWork.cs, change the
Course
repository type from GenericRepository<Course>
toCourseRepository:
private CourseRepository courseRepository;
public CourseRepository CourseRepository { get { if (this.courseRepository == null) { this.courseRepository = new CourseRepository(context); } return courseRepository; } }In CourseContoller.cs, add an
UpdateCourseCredits
method:public ActionResult UpdateCourseCredits(int? multiplier) { if (multiplier != null) { ViewBag.RowsAffected = unitOfWork.CourseRepository.UpdateCourseCredits(multiplier.Value); } return View(); }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:Create a view in the Views\Course folder for the Update Course Credits page:
In Views\Course\UpdateCourseCredits.cshtml, replace the existing code with the following code:
@model ContosoUniversity.Models.Course @{ ViewBag.Title = "UpdateCourseCredits"; } <h2>Update Course Credits</h2> @if (ViewBag.RowsAffected == null) { using (Html.BeginForm()) { <p> Enter a number to multiply every course's credits by: @Html.TextBox("multiplier") </p> <p> <input type="submit" value="Update" /> </p> } } @if (ViewBag.RowsAffected != null) { <p> Number of rows updated: @ViewBag.RowsAffected </p> }<div> @Html.ActionLink("Back to List", "Index")</div>Run the page 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:
Click Update. You see the number of rows affected:
Click Back to List to see the list of courses with the revised number of 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:- The query retrieves such a large volume of data that turning off tracking might noticeably enhance performance.
- You want to attach an entity in order to update it, but you earlier retrieved the same entity for a different purpose. Because the entity is already being tracked by the database context, you can't attach the entity that you want to change. One way to prevent this from happening is to use the
AsNoTracking
option with the earlier query.
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:private void ValidateOneAdministratorAssignmentPerInstructor(Department department) { if (department.PersonID != null) { var duplicateDepartment = db.Departments .Include("Administrator") .Where(d => d.PersonID == department.PersonID) .FirstOrDefault(); if (duplicateDepartment != null && duplicateDepartment.DepartmentID != department.DepartmentID) { var errorMessage = String.Format( "Instructor {0} {1} is already administrator of the {2} department.", duplicateDepartment.Administrator.FirstMidName, duplicateDepartment.Administrator.LastName, duplicateDepartment.Name); ModelState.AddModelError(string.Empty, errorMessage); } } }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:if (ModelState.IsValid) { ValidateOneAdministratorAssignmentPerInstructor(department); } if (ModelState.IsValid) { db.Entry(department).State = EntityState.Modified; db.SaveChanges(); return RedirectToAction("Index"); }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:
Now run the Department Edit page again and this time change the Budget amount. When you click Save, you see an error page:
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:- The
Edit
method calls theValidateOneAdministratorAssignmentPerInstructor
method, which retrieves all departments that have Kim Abercrombie as their administrator. That causes the English department to be read. Because that's the department being edited, no error is reported. As a result of this read operation, however, the English department entity that was read from the database is now being tracked by the database context. - The
Edit
method tries to set theModified
flag on the English department entity created by the MVC model binder, but that fails because the context is already tracking an entity for the English department.
In DepartmentController.cs, in the
ValidateOneAdministratorAssignmentPerInstructor
method, specify no tracking, as shown in the following example:var duplicateDepartment = db.Departments .Include("Administrator") .Where(d => d.PersonID == department.PersonID) .AsNoTracking() .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'sToString
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:public ViewResult Index() { var courses = unitOfWork.CourseRepository.Get(); return View(courses.ToList()); }Now set a breakpoint in GenericRepository.cs on the
return query.ToList();
and thereturn 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 Compact. It's a simple Select
statement:{SELECT [Extent1].[CourseID] AS [CourseID], [Extent1].[Title] AS [Title], [Extent1].[Credits] AS [Credits], [Extent1].[DepartmentID] AS [DepartmentID] 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:
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:public ActionResult Index(int? SelectedDepartment) { var departments = unitOfWork.DepartmentRepository.Get( orderBy: q => q.OrderBy(d => d.Name)); ViewBag.SelectedDepartment = new SelectList(departments, "DepartmentID", "Name", SelectedDepartment); int departmentID = SelectedDepartment.GetValueOrDefault(); return View(unitOfWork.CourseRepository.Get( filter: d => !SelectedDepartment.HasValue || d.DepartmentID == departmentID, orderBy: q => q.OrderBy(d => d.CourseID), includeProperties: "Department")); }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\Course\Index.cshtml, immediately before the opening
table
tag, add the following code to create the drop-down list and a submit button:@using (Html.BeginForm()) { <p>Select Department: @Html.DropDownList("SelectedDepartment","All") <input type="submit" value="Filter" /></p> }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: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:{SELECT [Extent1].[CourseID] AS [CourseID], [Extent1].[Title] AS [Title], [Extent1].[Credits] AS [Credits], [Extent1].[DepartmentID] AS [DepartmentID], [Extent2].[DepartmentID] AS [DepartmentID1], [Extent2].[Name] AS [Name], [Extent2].[Budget] AS [Budget], [Extent2].[StartDate] AS [StartDate], [Extent2].[PersonID] AS [PersonID], [Extent2].[Timestamp] AS [Timestamp] FROM [Course] AS [Extent1] INNER JOIN [Department] AS [Extent2] ON [Extent1].[DepartmentID] = [Extent2].[DepartmentID] 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 theCourse
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:
- In some scenarios you might want to prevent the Entity Framework from creating proxy instances. For example, serializing non-proxy instances might be more efficient than serializing proxy instances.
- When you instantiate an entity class using the
new
operator, you don't get a proxy instance. This means you don't get functionality such as lazy loading and automatic change tracking. This is typically okay; you generally don't need lazy loading, because you're creating a new entity that isn't in the database, and you generally don't need change tracking if you're explicitly marking the entity asAdded
. However, if you do need lazy loading and you need change tracking, you can create new entity instances with proxies using theCreate
method of theDbSet
class. - You might want to get an actual entity type from a proxy type. You can use the
GetObjectType
method of theObjectContext
class to get the actual entity type of a proxy type instance.
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:DbSet.Find
DbSet.Local
DbSet.Remove
DbSet.Add
DbSet.Attach
DbContext.SaveChanges
DbContext.GetValidationErrors
DbContext.Entry
DbChangeTracker.Entries
Disabling Validation When Saving Changes
When you call theSaveChanges
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 on the Entity Framework team blog.Links to Entity Framework Resources
This completes this series of tutorials on using the Entity Framework in an ASP.NET MVC application. For more information about the Entity Framework, see the following resources:- Introduction to the Entity Framework 4.1 (Code First)
- The Entity Framework Code First Class Library API Reference
- Entity Framework FAQ
- The Entity Framework Team Blog
- Entity Framework in the MSDN Library
- Entity Framework in the MSDN Data Developer Center
- Entity Framework Forums on MSDN
- Julie Lerman's blog
- Code First DataAnnotations Attributes
- Maximizing Performance with the Entity Framework in an ASP.NET Web Application
- Profiling Database Activity in the Entity Framework
- Entity Framework Power Tools
- Fluent API Samples. How to customize mapping using fluent API method calls.
- Connections and Models. How to connect to different types of databases.
- Pluggable Conventions. How to change conventions.
- Finding Entities. How to use the
Find
method with composite keys. - Loading Related Entities. Additional options for eager, lazy, and explicit loading.
- Load and AsNoTracking. More on explicit loading.
No comments:
Post a Comment