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

Reading related data - EF Core with ASP.NET Core MVC tutorial (6 of 10)

By Tom Dykstra and Rick Anderson

The Contoso University sample web application demonstrates how to create ASP.NET Core MVC web applications using Entity Framework Core and Visual Studio. For information about the tutorial series, see the first tutorial in the series.

In the previous tutorial, you completed the School data model. In this tutorial, you’ll read and display related data – that is, data that the Entity Framework loads into navigation properties.

The following illustrations show the pages that you’ll work with.

Courses Index page
Courses Index page
Instructors Index page
Instructors Index page

There are several ways that Object-Relational Mapping (ORM) software such as Entity Framework can load related data into the navigation properties of an entity:

Performance considerations

If you know you need related data for every entity retrieved, eager loading often offers the best performance, because a single query sent to the database is typically more efficient than separate queries for each entity retrieved. For example, suppose that each department has ten related courses. Eager loading of all related data would result in just a single (join) query and a single round trip to the database. A separate query for courses for each department would result in eleven round trips to the database. The extra round trips to the database are especially detrimental to performance when latency is high.

On the other hand, in some scenarios separate queries is more efficient. Eager loading of all related data in one query might cause a very complex join to be generated, which SQL Server can’t process efficiently. Or if you need to access an entity’s navigation properties only for a subset of a set of the entities you’re processing, separate queries might perform better because eager loading of everything up front would retrieve more data than you need. If performance is critical, it’s best to test performance both ways in order to make the best choice.

Create a Courses page that displays Department name

The Course entity includes a navigation property that contains the Department entity of the department that the course is assigned to. To display the name of the assigned department in a list of courses, you need to get the Name property from the Department entity that is in the Course.Department navigation property.

Create a controller named CoursesController for the Course entity type, using the same options for the MVC Controller with views, using Entity Framework scaffolder that you did earlier for the Students controller, as shown in the following illustration:

Add Courses controller
Add Courses controller

Open CoursesController.cs and examine the Index method. The automatic scaffolding has specified eager loading for the Department navigation property by using the Include method.

Replace the Index method with the following code that uses a more appropriate name for the IQueryable that returns Course entities (courses instead of schoolContext):

[!code-csharpMain]

   1:  //#define ScaffoldedCode
   2:  #define RevisedIndexMethod
   3:   
   4:  using System;
   5:  using System.Collections.Generic;
   6:  using System.Linq;
   7:  using System.Threading.Tasks;
   8:  using Microsoft.AspNetCore.Mvc;
   9:  using Microsoft.AspNetCore.Mvc.Rendering;
  10:  using Microsoft.EntityFrameworkCore;
  11:  using ContosoUniversity.Data;
  12:  using ContosoUniversity.Models;
  13:   
  14:  namespace ContosoUniversity.Controllers
  15:  {
  16:      public class CoursesController : Controller
  17:      {
  18:          private readonly SchoolContext _context;
  19:   
  20:          public CoursesController(SchoolContext context)
  21:          {
  22:              _context = context;    
  23:          }
  24:   
  25:          // GET: Courses
  26:  #if ScaffoldedCode
  27:          public async Task<IActionResult> Index()
  28:          {
  29:              var schoolContext = _context.Courses
  30:                  .Include(c => c.Department)
  31:                  .AsNoTracking();
  32:              return View(await schoolContext.ToListAsync());
  33:          }
  34:  #elif RevisedIndexMethod
  35:          #region snippet_RevisedIndexMethod
  36:          public async Task<IActionResult> Index()
  37:          {
  38:              var courses = _context.Courses
  39:                  .Include(c => c.Department)
  40:                  .AsNoTracking();
  41:              return View(await courses.ToListAsync());
  42:          }
  43:          #endregion
  44:  #endif
  45:          // GET: Courses/Details/5
  46:          #region snippet_Details
  47:          public async Task<IActionResult> Details(int? id)
  48:          {
  49:              if (id == null)
  50:              {
  51:                  return NotFound();
  52:              }
  53:   
  54:              var course = await _context.Courses
  55:                  .Include(c => c.Department)
  56:                  .AsNoTracking()
  57:                  .SingleOrDefaultAsync(m => m.CourseID == id);
  58:              if (course == null)
  59:              {
  60:                  return NotFound();
  61:              }
  62:   
  63:              return View(course);
  64:          }
  65:          #endregion
  66:   
  67:          // GET: Courses/Create
  68:          #region snippet_CreateGet
  69:          public IActionResult Create()
  70:          {
  71:              PopulateDepartmentsDropDownList();
  72:              return View();
  73:          }
  74:          #endregion
  75:          // POST: Courses/Create
  76:          #region snippet_CreatePost
  77:          [HttpPost]
  78:          [ValidateAntiForgeryToken]
  79:          public async Task<IActionResult> Create([Bind("CourseID,Credits,DepartmentID,Title")] Course course)
  80:          {
  81:              if (ModelState.IsValid)
  82:              {
  83:                  _context.Add(course);
  84:                  await _context.SaveChangesAsync();
  85:                  return RedirectToAction(nameof(Index));
  86:              }
  87:              PopulateDepartmentsDropDownList(course.DepartmentID);
  88:              return View(course);
  89:          }
  90:          #endregion
  91:   
  92:          // GET: Courses/Edit/5
  93:          #region snippet_EditGet
  94:          public async Task<IActionResult> Edit(int? id)
  95:          {
  96:              if (id == null)
  97:              {
  98:                  return NotFound();
  99:              }
 100:   
 101:              var course = await _context.Courses
 102:                  .AsNoTracking()
 103:                  .SingleOrDefaultAsync(m => m.CourseID == id);
 104:              if (course == null)
 105:              {
 106:                  return NotFound();
 107:              }
 108:              PopulateDepartmentsDropDownList(course.DepartmentID);
 109:              return View(course);
 110:          }
 111:          #endregion
 112:   
 113:   
 114:          // POST: Courses/Edit/5
 115:          #region snippet_EditPost
 116:          [HttpPost, ActionName("Edit")]
 117:          [ValidateAntiForgeryToken]
 118:          public async Task<IActionResult> EditPost(int? id)
 119:          {
 120:              if (id == null)
 121:              {
 122:                  return NotFound();
 123:              }
 124:   
 125:              var courseToUpdate = await _context.Courses
 126:                  .SingleOrDefaultAsync(c => c.CourseID == id);
 127:   
 128:              if (await TryUpdateModelAsync<Course>(courseToUpdate,
 129:                  "",
 130:                  c => c.Credits, c => c.DepartmentID, c => c.Title))
 131:              {
 132:                  try
 133:                  {
 134:                      await _context.SaveChangesAsync();
 135:                  }
 136:                  catch (DbUpdateException /* ex */)
 137:                  {
 138:                      //Log the error (uncomment ex variable name and write a log.)
 139:                      ModelState.AddModelError("", "Unable to save changes. " +
 140:                          "Try again, and if the problem persists, " +
 141:                          "see your system administrator.");
 142:                  }
 143:                  return RedirectToAction(nameof(Index));
 144:              }
 145:              PopulateDepartmentsDropDownList(courseToUpdate.DepartmentID);
 146:              return View(courseToUpdate);
 147:          }
 148:          #endregion
 149:   
 150:          #region snippet_Departments
 151:          private void PopulateDepartmentsDropDownList(object selectedDepartment = null)
 152:          {
 153:              var departmentsQuery = from d in _context.Departments
 154:                                     orderby d.Name
 155:                                     select d;
 156:              ViewBag.DepartmentID = new SelectList(departmentsQuery.AsNoTracking(), "DepartmentID", "Name", selectedDepartment);
 157:          }
 158:          #endregion
 159:   
 160:          // GET: Courses/Delete/5
 161:          #region snippet_DeleteGet
 162:          public async Task<IActionResult> Delete(int? id)
 163:          {
 164:              if (id == null)
 165:              {
 166:                  return NotFound();
 167:              }
 168:   
 169:              var course = await _context.Courses
 170:                  .Include(c => c.Department)
 171:                  .AsNoTracking()
 172:                  .SingleOrDefaultAsync(m => m.CourseID == id);
 173:              if (course == null)
 174:              {
 175:                  return NotFound();
 176:              }
 177:   
 178:              return View(course);
 179:          }
 180:          #endregion
 181:   
 182:          // POST: Courses/Delete/5
 183:          [HttpPost, ActionName("Delete")]
 184:          [ValidateAntiForgeryToken]
 185:          public async Task<IActionResult> DeleteConfirmed(int id)
 186:          {
 187:              var course = await _context.Courses
 188:                  .SingleOrDefaultAsync(m => m.CourseID == id);
 189:              if (course != null)
 190:              {
 191:                  _context.Courses.Remove(course);
 192:                  await _context.SaveChangesAsync();
 193:              }
 194:              return RedirectToAction(nameof(Index));
 195:          }
 196:   
 197:          #region snippet_UpdateGet
 198:          public IActionResult UpdateCourseCredits()
 199:          {
 200:              return View();
 201:          }
 202:          #endregion
 203:   
 204:          #region snippet_UpdatePost
 205:          [HttpPost]
 206:          public async Task<IActionResult> UpdateCourseCredits(int? multiplier)
 207:          {
 208:              if (multiplier != null)
 209:              {
 210:                  ViewData["RowsAffected"] = 
 211:                      await _context.Database.ExecuteSqlCommandAsync(
 212:                          "UPDATE Course SET Credits = Credits * {0}",
 213:                          parameters: multiplier);
 214:              }
 215:              return View();
 216:          }
 217:          #endregion
 218:      }
 219:  }

Open Views/Courses/Index.cshtml and replace the template code with the following code. The changes are highlighted:

[!code-htmlMain]

   1:  @model IEnumerable<ContosoUniversity.Models.Course>
   2:   
   3:  @{
   4:      ViewData["Title"] = "Courses";
   5:  }
   6:   
   7:  <h2>Courses</h2>
   8:   
   9:  <p>
  10:      <a asp-action="Create">Create New</a>
  11:  </p>
  12:  <table class="table">
  13:      <thead>
  14:          <tr>
  15:              <th>
  16:                  @Html.DisplayNameFor(model => model.CourseID)
  17:              </th>
  18:              <th>
  19:                  @Html.DisplayNameFor(model => model.Title)
  20:              </th>
  21:              <th>
  22:                  @Html.DisplayNameFor(model => model.Credits)
  23:              </th>
  24:              <th>
  25:                  @Html.DisplayNameFor(model => model.Department)
  26:              </th>
  27:              <th></th>
  28:          </tr>
  29:      </thead>
  30:      <tbody>
  31:          @foreach (var item in Model)
  32:          {
  33:              <tr>
  34:                  <td>
  35:                      @Html.DisplayFor(modelItem => item.CourseID)
  36:                  </td>
  37:                  <td>
  38:                      @Html.DisplayFor(modelItem => item.Title)
  39:                  </td>
  40:                  <td>
  41:                      @Html.DisplayFor(modelItem => item.Credits)
  42:                  </td>
  43:                  <td>
  44:                      @Html.DisplayFor(modelItem => item.Department.Name)
  45:                  </td>
  46:                  <td>
  47:                      <a asp-action="Edit" asp-route-id="@item.CourseID">Edit</a> |
  48:                      <a asp-action="Details" asp-route-id="@item.CourseID">Details</a> |
  49:                      <a asp-action="Delete" asp-route-id="@item.CourseID">Delete</a>
  50:                  </td>
  51:              </tr>
  52:          }
  53:      </tbody>
  54:  </table>

You’ve made the following changes to the scaffolded code:

Run the app and select the Courses tab to see the list with department names.

Courses Index page
Courses Index page

Create an Instructors page that shows Courses and Enrollments

In this section, you’ll create a controller and view for the Instructor entity in order to display the Instructors page:

Instructors Index page
Instructors Index page

This page reads and displays related data in the following ways:

Create a view model for the Instructor Index view

The Instructors page shows data from three different tables. Therefore, you’ll create a view model that includes three properties, each holding the data for one of the tables.

In the SchoolViewModels folder, create InstructorIndexData.cs and replace the existing code with the following code:

[!code-csharpMain]

   1:  using System;
   2:  using System.Collections.Generic;
   3:  using System.Linq;
   4:  using System.Threading.Tasks;
   5:   
   6:  namespace ContosoUniversity.Models.SchoolViewModels
   7:  {
   8:      public class InstructorIndexData
   9:      {
  10:          public IEnumerable<Instructor> Instructors { get; set; }
  11:          public IEnumerable<Course> Courses { get; set; }
  12:          public IEnumerable<Enrollment> Enrollments { get; set; }
  13:      }
  14:  }

Create the Instructor controller and views

Create an Instructors controller with EF read/write actions as shown in the following illustration:

Add Instructors controller
Add Instructors controller

Open InstructorsController.cs and add a using statement for the ViewModels namespace:

[!code-csharpMain]

   1:  #define ExplicitLoading // or EagerLoading or ScaffoldedCode
   2:  #define EditCourses // or EditOfficeAssignment
   3:   
   4:  using System;
   5:  using System.Collections.Generic;
   6:  using System.Linq;
   7:  using System.Threading.Tasks;
   8:  using Microsoft.AspNetCore.Mvc;
   9:  using Microsoft.AspNetCore.Mvc.Rendering;
  10:  using Microsoft.EntityFrameworkCore;
  11:  using ContosoUniversity.Data;
  12:  using ContosoUniversity.Models;
  13:  #region snippet_Using
  14:  using ContosoUniversity.Models.SchoolViewModels;
  15:  #endregion
  16:   
  17:  namespace ContosoUniversity.Controllers
  18:  {
  19:      public class InstructorsController : Controller
  20:      {
  21:          private readonly SchoolContext _context;
  22:   
  23:          public InstructorsController(SchoolContext context)
  24:          {
  25:              _context = context;
  26:          }
  27:   
  28:          // GET: Instructors
  29:  #if ScaffoldedCode
  30:          #region snippet_ScaffoldedCode
  31:          public async Task<IActionResult> Index()
  32:          {
  33:              return View(await _context.Instructors.AsNoTracking().ToListAsync());
  34:          }
  35:          #endregion
  36:  #elif EagerLoading
  37:          #region snippet_EagerLoading
  38:          public async Task<IActionResult> Index(int? id, int? courseID)
  39:          {
  40:              var viewModel = new InstructorIndexData();
  41:              #region snippet_ThenInclude
  42:              viewModel.Instructors = await _context.Instructors
  43:                    .Include(i => i.OfficeAssignment)
  44:                    .Include(i => i.CourseAssignments)
  45:                      .ThenInclude(i => i.Course)
  46:                          .ThenInclude(i => i.Enrollments)
  47:                              .ThenInclude(i => i.Student)
  48:                    .Include(i => i.CourseAssignments)
  49:                      .ThenInclude(i => i.Course)
  50:                          .ThenInclude(i => i.Department)
  51:                    .AsNoTracking()
  52:                    .OrderBy(i => i.LastName)
  53:                    .ToListAsync();
  54:              #endregion
  55:              
  56:              if (id != null)
  57:              {
  58:                  ViewData["InstructorID"] = id.Value;
  59:                  Instructor instructor = viewModel.Instructors.Where(
  60:                      i => i.ID == id.Value).Single();
  61:                  viewModel.Courses = instructor.CourseAssignments.Select(s => s.Course);
  62:              }
  63:   
  64:              if (courseID != null)
  65:              {
  66:                  ViewData["CourseID"] = courseID.Value;
  67:                  viewModel.Enrollments = viewModel.Courses.Where(
  68:                      x => x.CourseID == courseID).Single().Enrollments;
  69:              }
  70:   
  71:              return View(viewModel);
  72:          }
  73:          #endregion
  74:  #elif ExplicitLoading
  75:          #region snippet_ExplicitLoading
  76:          public async Task<IActionResult> Index(int? id, int? courseID)
  77:          {
  78:              var viewModel = new InstructorIndexData();
  79:              viewModel.Instructors = await _context.Instructors
  80:                    .Include(i => i.OfficeAssignment)
  81:                    .Include(i => i.CourseAssignments)
  82:                      .ThenInclude(i => i.Course)
  83:                          .ThenInclude(i => i.Department)
  84:                    .OrderBy(i => i.LastName)
  85:                    .ToListAsync();
  86:   
  87:              if (id != null)
  88:              {
  89:                  ViewData["InstructorID"] = id.Value;
  90:                  Instructor instructor = viewModel.Instructors.Where(
  91:                      i => i.ID == id.Value).Single();
  92:                  viewModel.Courses = instructor.CourseAssignments.Select(s => s.Course);
  93:              }
  94:   
  95:              if (courseID != null)
  96:              {
  97:                  ViewData["CourseID"] = courseID.Value;
  98:                  var selectedCourse = viewModel.Courses.Where(x => x.CourseID == courseID).Single();
  99:                  await _context.Entry(selectedCourse).Collection(x => x.Enrollments).LoadAsync();
 100:                  foreach (Enrollment enrollment in selectedCourse.Enrollments)
 101:                  {
 102:                      await _context.Entry(enrollment).Reference(x => x.Student).LoadAsync();
 103:                  }
 104:                  viewModel.Enrollments = selectedCourse.Enrollments;
 105:              }
 106:   
 107:              return View(viewModel);
 108:          }
 109:          #endregion
 110:  #endif
 111:          // GET: Instructors/Details/5
 112:          public async Task<IActionResult> Details(int? id)
 113:          {
 114:              if (id == null)
 115:              {
 116:                  return NotFound();
 117:              }
 118:   
 119:              var instructor = await _context.Instructors
 120:                  .AsNoTracking()
 121:                  .SingleOrDefaultAsync(m => m.ID == id);
 122:              if (instructor == null)
 123:              {
 124:                  return NotFound();
 125:              }
 126:   
 127:              return View(instructor);
 128:          }
 129:   
 130:          // GET: Instructors/Create
 131:          #region snippet_Create
 132:          public IActionResult Create()
 133:          {
 134:              var instructor = new Instructor();
 135:              instructor.CourseAssignments = new List<CourseAssignment>();
 136:              PopulateAssignedCourseData(instructor);
 137:              return View();
 138:          }
 139:   
 140:          // POST: Instructors/Create
 141:          [HttpPost]
 142:          [ValidateAntiForgeryToken]
 143:          public async Task<IActionResult> Create([Bind("FirstMidName,HireDate,LastName,OfficeAssignment")] Instructor instructor, string[] selectedCourses)
 144:          {
 145:              if (selectedCourses != null)
 146:              {
 147:                  instructor.CourseAssignments = new List<CourseAssignment>();
 148:                  foreach (var course in selectedCourses)
 149:                  {
 150:                      var courseToAdd = new CourseAssignment { InstructorID = instructor.ID, CourseID = int.Parse(course) };
 151:                      instructor.CourseAssignments.Add(courseToAdd);
 152:                  }
 153:              }
 154:              if (ModelState.IsValid)
 155:              {
 156:                  _context.Add(instructor);
 157:                  await _context.SaveChangesAsync();
 158:                  return RedirectToAction(nameof(Index));
 159:              }
 160:              PopulateAssignedCourseData(instructor);
 161:              return View(instructor);
 162:          }
 163:          #endregion
 164:   
 165:          // GET: Instructors/Edit/5
 166:  #if EditOfficeAssignment
 167:          #region snippet_EditGetOA
 168:          public async Task<IActionResult> Edit(int? id)
 169:          {
 170:              if (id == null)
 171:              {
 172:                  return NotFound();
 173:              }
 174:   
 175:              var instructor = await _context.Instructors
 176:                  .Include(i => i.OfficeAssignment)
 177:                  .AsNoTracking()
 178:                  .SingleOrDefaultAsync(m => m.ID == id);
 179:              if (instructor == null)
 180:              {
 181:                  return NotFound();
 182:              }
 183:              return View(instructor);
 184:          }
 185:          #endregion
 186:  #elif EditCourses
 187:          #region snippet_EditGetCourses
 188:          public async Task<IActionResult> Edit(int? id)
 189:          {
 190:              if (id == null)
 191:              {
 192:                  return NotFound();
 193:              }
 194:   
 195:              var instructor = await _context.Instructors
 196:                  .Include(i => i.OfficeAssignment)
 197:                  .Include(i => i.CourseAssignments).ThenInclude(i => i.Course)
 198:                  .AsNoTracking()
 199:                  .SingleOrDefaultAsync(m => m.ID == id);
 200:              if (instructor == null)
 201:              {
 202:                  return NotFound();
 203:              }
 204:              PopulateAssignedCourseData(instructor);
 205:              return View(instructor);
 206:          }
 207:   
 208:          private void PopulateAssignedCourseData(Instructor instructor)
 209:          {
 210:              var allCourses = _context.Courses;
 211:              var instructorCourses = new HashSet<int>(instructor.CourseAssignments.Select(c => c.CourseID));
 212:              var viewModel = new List<AssignedCourseData>();
 213:              foreach (var course in allCourses)
 214:              {
 215:                  viewModel.Add(new AssignedCourseData
 216:                  {
 217:                      CourseID = course.CourseID,
 218:                      Title = course.Title,
 219:                      Assigned = instructorCourses.Contains(course.CourseID)
 220:                  });
 221:              }
 222:              ViewData["Courses"] = viewModel;
 223:          }
 224:          #endregion
 225:  #endif
 226:          // POST: Instructors/Edit/5
 227:          // To protect from overposting attacks, please enable the specific properties you want to bind to, for 
 228:          // more details see http://go.microsoft.com/fwlink/?LinkId=317598.
 229:  #if EditOfficeAssignment
 230:          #region snippet_EditPostOA
 231:          [HttpPost, ActionName("Edit")]
 232:          [ValidateAntiForgeryToken]
 233:          public async Task<IActionResult> EditPost(int? id)
 234:          {
 235:              if (id == null)
 236:              {
 237:                  return NotFound();
 238:              }
 239:   
 240:              var instructorToUpdate = await _context.Instructors
 241:                  .Include(i => i.OfficeAssignment)
 242:                  .SingleOrDefaultAsync(s => s.ID == id);
 243:   
 244:              if (await TryUpdateModelAsync<Instructor>(
 245:                  instructorToUpdate,
 246:                  "",
 247:                  i => i.FirstMidName, i => i.LastName, i => i.HireDate, i => i.OfficeAssignment))
 248:              {
 249:                  if (String.IsNullOrWhiteSpace(instructorToUpdate.OfficeAssignment?.Location))
 250:                  {
 251:                      instructorToUpdate.OfficeAssignment = null;
 252:                  }
 253:                  try
 254:                  {
 255:                      await _context.SaveChangesAsync();
 256:                  }
 257:                  catch (DbUpdateException /* ex */)
 258:                  {
 259:                      //Log the error (uncomment ex variable name and write a log.)
 260:                      ModelState.AddModelError("", "Unable to save changes. " +
 261:                          "Try again, and if the problem persists, " +
 262:                          "see your system administrator.");
 263:                  }
 264:                  return RedirectToAction(nameof(Index));
 265:              }
 266:              return View(instructorToUpdate);
 267:          }
 268:          #endregion
 269:   
 270:  #elif EditCourses
 271:          #region snippet_EditPostCourses
 272:          [HttpPost]
 273:          [ValidateAntiForgeryToken]
 274:          public async Task<IActionResult> Edit(int? id, string[] selectedCourses)
 275:          {
 276:              if (id == null)
 277:              {
 278:                  return NotFound();
 279:              }
 280:   
 281:              var instructorToUpdate = await _context.Instructors
 282:                  .Include(i => i.OfficeAssignment)
 283:                  .Include(i => i.CourseAssignments)
 284:                      .ThenInclude(i => i.Course)
 285:                  .SingleOrDefaultAsync(m => m.ID == id);
 286:   
 287:              if (await TryUpdateModelAsync<Instructor>(
 288:                  instructorToUpdate,
 289:                  "",
 290:                  i => i.FirstMidName, i => i.LastName, i => i.HireDate, i => i.OfficeAssignment))
 291:              {
 292:                  if (String.IsNullOrWhiteSpace(instructorToUpdate.OfficeAssignment?.Location))
 293:                  {
 294:                      instructorToUpdate.OfficeAssignment = null;
 295:                  }
 296:                  UpdateInstructorCourses(selectedCourses, instructorToUpdate);
 297:                  try
 298:                  {
 299:                      await _context.SaveChangesAsync();
 300:                  }
 301:                  catch (DbUpdateException /* ex */)
 302:                  {
 303:                      //Log the error (uncomment ex variable name and write a log.)
 304:                      ModelState.AddModelError("", "Unable to save changes. " +
 305:                          "Try again, and if the problem persists, " +
 306:                          "see your system administrator.");
 307:                  }
 308:                  return RedirectToAction(nameof(Index));
 309:              }
 310:              UpdateInstructorCourses(selectedCourses, instructorToUpdate);
 311:              PopulateAssignedCourseData(instructorToUpdate);
 312:              return View(instructorToUpdate);
 313:          }
 314:          #endregion
 315:   
 316:          #region snippet_UpdateCourses
 317:          private void UpdateInstructorCourses(string[] selectedCourses, Instructor instructorToUpdate)
 318:          {
 319:              if (selectedCourses == null)
 320:              {
 321:                  instructorToUpdate.CourseAssignments = new List<CourseAssignment>();
 322:                  return;
 323:              }
 324:   
 325:              var selectedCoursesHS = new HashSet<string>(selectedCourses);
 326:              var instructorCourses = new HashSet<int>
 327:                  (instructorToUpdate.CourseAssignments.Select(c => c.Course.CourseID));
 328:              foreach (var course in _context.Courses)
 329:              {
 330:                  if (selectedCoursesHS.Contains(course.CourseID.ToString()))
 331:                  {
 332:                      if (!instructorCourses.Contains(course.CourseID))
 333:                      {
 334:                          instructorToUpdate.CourseAssignments.Add(new CourseAssignment { InstructorID = instructorToUpdate.ID, CourseID = course.CourseID });
 335:                      }
 336:                  }
 337:                  else
 338:                  {
 339:   
 340:                      if (instructorCourses.Contains(course.CourseID))
 341:                      {
 342:                          CourseAssignment courseToRemove = instructorToUpdate.CourseAssignments.SingleOrDefault(i => i.CourseID == course.CourseID);
 343:                          _context.Remove(courseToRemove);
 344:                      }
 345:                  }
 346:              }
 347:          }
 348:          #endregion
 349:  #endif
 350:          // GET: Instructors/Delete/5
 351:          public async Task<IActionResult> Delete(int? id)
 352:          {
 353:              if (id == null)
 354:              {
 355:                  return NotFound();
 356:              }
 357:   
 358:              var instructor = await _context.Instructors
 359:                  .AsNoTracking()
 360:                  .SingleOrDefaultAsync(m => m.ID == id);
 361:              if (instructor == null)
 362:              {
 363:                  return NotFound();
 364:              }
 365:   
 366:              return View(instructor);
 367:          }
 368:   
 369:          #region snippet_DeleteConfirmed
 370:          [HttpPost, ActionName("Delete")]
 371:          [ValidateAntiForgeryToken]
 372:          public async Task<IActionResult> DeleteConfirmed(int id)
 373:          {
 374:              Instructor instructor = await _context.Instructors
 375:                  .Include(i => i.CourseAssignments)
 376:                  .SingleAsync(i => i.ID == id);
 377:   
 378:              var departments = await _context.Departments
 379:                  .Where(d => d.InstructorID == id)
 380:                  .ToListAsync();
 381:              departments.ForEach(d => d.InstructorID = null);
 382:   
 383:              _context.Instructors.Remove(instructor);
 384:   
 385:              await _context.SaveChangesAsync();
 386:              return RedirectToAction(nameof(Index));
 387:          }
 388:          #endregion
 389:      }
 390:  }

Replace the Index method with the following code to do eager loading of related data and put it in the view model.

[!code-csharpMain]

   1:  #define ExplicitLoading // or EagerLoading or ScaffoldedCode
   2:  #define EditCourses // or EditOfficeAssignment
   3:   
   4:  using System;
   5:  using System.Collections.Generic;
   6:  using System.Linq;
   7:  using System.Threading.Tasks;
   8:  using Microsoft.AspNetCore.Mvc;
   9:  using Microsoft.AspNetCore.Mvc.Rendering;
  10:  using Microsoft.EntityFrameworkCore;
  11:  using ContosoUniversity.Data;
  12:  using ContosoUniversity.Models;
  13:  #region snippet_Using
  14:  using ContosoUniversity.Models.SchoolViewModels;
  15:  #endregion
  16:   
  17:  namespace ContosoUniversity.Controllers
  18:  {
  19:      public class InstructorsController : Controller
  20:      {
  21:          private readonly SchoolContext _context;
  22:   
  23:          public InstructorsController(SchoolContext context)
  24:          {
  25:              _context = context;
  26:          }
  27:   
  28:          // GET: Instructors
  29:  #if ScaffoldedCode
  30:          #region snippet_ScaffoldedCode
  31:          public async Task<IActionResult> Index()
  32:          {
  33:              return View(await _context.Instructors.AsNoTracking().ToListAsync());
  34:          }
  35:          #endregion
  36:  #elif EagerLoading
  37:          #region snippet_EagerLoading
  38:          public async Task<IActionResult> Index(int? id, int? courseID)
  39:          {
  40:              var viewModel = new InstructorIndexData();
  41:              #region snippet_ThenInclude
  42:              viewModel.Instructors = await _context.Instructors
  43:                    .Include(i => i.OfficeAssignment)
  44:                    .Include(i => i.CourseAssignments)
  45:                      .ThenInclude(i => i.Course)
  46:                          .ThenInclude(i => i.Enrollments)
  47:                              .ThenInclude(i => i.Student)
  48:                    .Include(i => i.CourseAssignments)
  49:                      .ThenInclude(i => i.Course)
  50:                          .ThenInclude(i => i.Department)
  51:                    .AsNoTracking()
  52:                    .OrderBy(i => i.LastName)
  53:                    .ToListAsync();
  54:              #endregion
  55:              
  56:              if (id != null)
  57:              {
  58:                  ViewData["InstructorID"] = id.Value;
  59:                  Instructor instructor = viewModel.Instructors.Where(
  60:                      i => i.ID == id.Value).Single();
  61:                  viewModel.Courses = instructor.CourseAssignments.Select(s => s.Course);
  62:              }
  63:   
  64:              if (courseID != null)
  65:              {
  66:                  ViewData["CourseID"] = courseID.Value;
  67:                  viewModel.Enrollments = viewModel.Courses.Where(
  68:                      x => x.CourseID == courseID).Single().Enrollments;
  69:              }
  70:   
  71:              return View(viewModel);
  72:          }
  73:          #endregion
  74:  #elif ExplicitLoading
  75:          #region snippet_ExplicitLoading
  76:          public async Task<IActionResult> Index(int? id, int? courseID)
  77:          {
  78:              var viewModel = new InstructorIndexData();
  79:              viewModel.Instructors = await _context.Instructors
  80:                    .Include(i => i.OfficeAssignment)
  81:                    .Include(i => i.CourseAssignments)
  82:                      .ThenInclude(i => i.Course)
  83:                          .ThenInclude(i => i.Department)
  84:                    .OrderBy(i => i.LastName)
  85:                    .ToListAsync();
  86:   
  87:              if (id != null)
  88:              {
  89:                  ViewData["InstructorID"] = id.Value;
  90:                  Instructor instructor = viewModel.Instructors.Where(
  91:                      i => i.ID == id.Value).Single();
  92:                  viewModel.Courses = instructor.CourseAssignments.Select(s => s.Course);
  93:              }
  94:   
  95:              if (courseID != null)
  96:              {
  97:                  ViewData["CourseID"] = courseID.Value;
  98:                  var selectedCourse = viewModel.Courses.Where(x => x.CourseID == courseID).Single();
  99:                  await _context.Entry(selectedCourse).Collection(x => x.Enrollments).LoadAsync();
 100:                  foreach (Enrollment enrollment in selectedCourse.Enrollments)
 101:                  {
 102:                      await _context.Entry(enrollment).Reference(x => x.Student).LoadAsync();
 103:                  }
 104:                  viewModel.Enrollments = selectedCourse.Enrollments;
 105:              }
 106:   
 107:              return View(viewModel);
 108:          }
 109:          #endregion
 110:  #endif
 111:          // GET: Instructors/Details/5
 112:          public async Task<IActionResult> Details(int? id)
 113:          {
 114:              if (id == null)
 115:              {
 116:                  return NotFound();
 117:              }
 118:   
 119:              var instructor = await _context.Instructors
 120:                  .AsNoTracking()
 121:                  .SingleOrDefaultAsync(m => m.ID == id);
 122:              if (instructor == null)
 123:              {
 124:                  return NotFound();
 125:              }
 126:   
 127:              return View(instructor);
 128:          }
 129:   
 130:          // GET: Instructors/Create
 131:          #region snippet_Create
 132:          public IActionResult Create()
 133:          {
 134:              var instructor = new Instructor();
 135:              instructor.CourseAssignments = new List<CourseAssignment>();
 136:              PopulateAssignedCourseData(instructor);
 137:              return View();
 138:          }
 139:   
 140:          // POST: Instructors/Create
 141:          [HttpPost]
 142:          [ValidateAntiForgeryToken]
 143:          public async Task<IActionResult> Create([Bind("FirstMidName,HireDate,LastName,OfficeAssignment")] Instructor instructor, string[] selectedCourses)
 144:          {
 145:              if (selectedCourses != null)
 146:              {
 147:                  instructor.CourseAssignments = new List<CourseAssignment>();
 148:                  foreach (var course in selectedCourses)
 149:                  {
 150:                      var courseToAdd = new CourseAssignment { InstructorID = instructor.ID, CourseID = int.Parse(course) };
 151:                      instructor.CourseAssignments.Add(courseToAdd);
 152:                  }
 153:              }
 154:              if (ModelState.IsValid)
 155:              {
 156:                  _context.Add(instructor);
 157:                  await _context.SaveChangesAsync();
 158:                  return RedirectToAction(nameof(Index));
 159:              }
 160:              PopulateAssignedCourseData(instructor);
 161:              return View(instructor);
 162:          }
 163:          #endregion
 164:   
 165:          // GET: Instructors/Edit/5
 166:  #if EditOfficeAssignment
 167:          #region snippet_EditGetOA
 168:          public async Task<IActionResult> Edit(int? id)
 169:          {
 170:              if (id == null)
 171:              {
 172:                  return NotFound();
 173:              }
 174:   
 175:              var instructor = await _context.Instructors
 176:                  .Include(i => i.OfficeAssignment)
 177:                  .AsNoTracking()
 178:                  .SingleOrDefaultAsync(m => m.ID == id);
 179:              if (instructor == null)
 180:              {
 181:                  return NotFound();
 182:              }
 183:              return View(instructor);
 184:          }
 185:          #endregion
 186:  #elif EditCourses
 187:          #region snippet_EditGetCourses
 188:          public async Task<IActionResult> Edit(int? id)
 189:          {
 190:              if (id == null)
 191:              {
 192:                  return NotFound();
 193:              }
 194:   
 195:              var instructor = await _context.Instructors
 196:                  .Include(i => i.OfficeAssignment)
 197:                  .Include(i => i.CourseAssignments).ThenInclude(i => i.Course)
 198:                  .AsNoTracking()
 199:                  .SingleOrDefaultAsync(m => m.ID == id);
 200:              if (instructor == null)
 201:              {
 202:                  return NotFound();
 203:              }
 204:              PopulateAssignedCourseData(instructor);
 205:              return View(instructor);
 206:          }
 207:   
 208:          private void PopulateAssignedCourseData(Instructor instructor)
 209:          {
 210:              var allCourses = _context.Courses;
 211:              var instructorCourses = new HashSet<int>(instructor.CourseAssignments.Select(c => c.CourseID));
 212:              var viewModel = new List<AssignedCourseData>();
 213:              foreach (var course in allCourses)
 214:              {
 215:                  viewModel.Add(new AssignedCourseData
 216:                  {
 217:                      CourseID = course.CourseID,
 218:                      Title = course.Title,
 219:                      Assigned = instructorCourses.Contains(course.CourseID)
 220:                  });
 221:              }
 222:              ViewData["Courses"] = viewModel;
 223:          }
 224:          #endregion
 225:  #endif
 226:          // POST: Instructors/Edit/5
 227:          // To protect from overposting attacks, please enable the specific properties you want to bind to, for 
 228:          // more details see http://go.microsoft.com/fwlink/?LinkId=317598.
 229:  #if EditOfficeAssignment
 230:          #region snippet_EditPostOA
 231:          [HttpPost, ActionName("Edit")]
 232:          [ValidateAntiForgeryToken]
 233:          public async Task<IActionResult> EditPost(int? id)
 234:          {
 235:              if (id == null)
 236:              {
 237:                  return NotFound();
 238:              }
 239:   
 240:              var instructorToUpdate = await _context.Instructors
 241:                  .Include(i => i.OfficeAssignment)
 242:                  .SingleOrDefaultAsync(s => s.ID == id);
 243:   
 244:              if (await TryUpdateModelAsync<Instructor>(
 245:                  instructorToUpdate,
 246:                  "",
 247:                  i => i.FirstMidName, i => i.LastName, i => i.HireDate, i => i.OfficeAssignment))
 248:              {
 249:                  if (String.IsNullOrWhiteSpace(instructorToUpdate.OfficeAssignment?.Location))
 250:                  {
 251:                      instructorToUpdate.OfficeAssignment = null;
 252:                  }
 253:                  try
 254:                  {
 255:                      await _context.SaveChangesAsync();
 256:                  }
 257:                  catch (DbUpdateException /* ex */)
 258:                  {
 259:                      //Log the error (uncomment ex variable name and write a log.)
 260:                      ModelState.AddModelError("", "Unable to save changes. " +
 261:                          "Try again, and if the problem persists, " +
 262:                          "see your system administrator.");
 263:                  }
 264:                  return RedirectToAction(nameof(Index));
 265:              }
 266:              return View(instructorToUpdate);
 267:          }
 268:          #endregion
 269:   
 270:  #elif EditCourses
 271:          #region snippet_EditPostCourses
 272:          [HttpPost]
 273:          [ValidateAntiForgeryToken]
 274:          public async Task<IActionResult> Edit(int? id, string[] selectedCourses)
 275:          {
 276:              if (id == null)
 277:              {
 278:                  return NotFound();
 279:              }
 280:   
 281:              var instructorToUpdate = await _context.Instructors
 282:                  .Include(i => i.OfficeAssignment)
 283:                  .Include(i => i.CourseAssignments)
 284:                      .ThenInclude(i => i.Course)
 285:                  .SingleOrDefaultAsync(m => m.ID == id);
 286:   
 287:              if (await TryUpdateModelAsync<Instructor>(
 288:                  instructorToUpdate,
 289:                  "",
 290:                  i => i.FirstMidName, i => i.LastName, i => i.HireDate, i => i.OfficeAssignment))
 291:              {
 292:                  if (String.IsNullOrWhiteSpace(instructorToUpdate.OfficeAssignment?.Location))
 293:                  {
 294:                      instructorToUpdate.OfficeAssignment = null;
 295:                  }
 296:                  UpdateInstructorCourses(selectedCourses, instructorToUpdate);
 297:                  try
 298:                  {
 299:                      await _context.SaveChangesAsync();
 300:                  }
 301:                  catch (DbUpdateException /* ex */)
 302:                  {
 303:                      //Log the error (uncomment ex variable name and write a log.)
 304:                      ModelState.AddModelError("", "Unable to save changes. " +
 305:                          "Try again, and if the problem persists, " +
 306:                          "see your system administrator.");
 307:                  }
 308:                  return RedirectToAction(nameof(Index));
 309:              }
 310:              UpdateInstructorCourses(selectedCourses, instructorToUpdate);
 311:              PopulateAssignedCourseData(instructorToUpdate);
 312:              return View(instructorToUpdate);
 313:          }
 314:          #endregion
 315:   
 316:          #region snippet_UpdateCourses
 317:          private void UpdateInstructorCourses(string[] selectedCourses, Instructor instructorToUpdate)
 318:          {
 319:              if (selectedCourses == null)
 320:              {
 321:                  instructorToUpdate.CourseAssignments = new List<CourseAssignment>();
 322:                  return;
 323:              }
 324:   
 325:              var selectedCoursesHS = new HashSet<string>(selectedCourses);
 326:              var instructorCourses = new HashSet<int>
 327:                  (instructorToUpdate.CourseAssignments.Select(c => c.Course.CourseID));
 328:              foreach (var course in _context.Courses)
 329:              {
 330:                  if (selectedCoursesHS.Contains(course.CourseID.ToString()))
 331:                  {
 332:                      if (!instructorCourses.Contains(course.CourseID))
 333:                      {
 334:                          instructorToUpdate.CourseAssignments.Add(new CourseAssignment { InstructorID = instructorToUpdate.ID, CourseID = course.CourseID });
 335:                      }
 336:                  }
 337:                  else
 338:                  {
 339:   
 340:                      if (instructorCourses.Contains(course.CourseID))
 341:                      {
 342:                          CourseAssignment courseToRemove = instructorToUpdate.CourseAssignments.SingleOrDefault(i => i.CourseID == course.CourseID);
 343:                          _context.Remove(courseToRemove);
 344:                      }
 345:                  }
 346:              }
 347:          }
 348:          #endregion
 349:  #endif
 350:          // GET: Instructors/Delete/5
 351:          public async Task<IActionResult> Delete(int? id)
 352:          {
 353:              if (id == null)
 354:              {
 355:                  return NotFound();
 356:              }
 357:   
 358:              var instructor = await _context.Instructors
 359:                  .AsNoTracking()
 360:                  .SingleOrDefaultAsync(m => m.ID == id);
 361:              if (instructor == null)
 362:              {
 363:                  return NotFound();
 364:              }
 365:   
 366:              return View(instructor);
 367:          }
 368:   
 369:          #region snippet_DeleteConfirmed
 370:          [HttpPost, ActionName("Delete")]
 371:          [ValidateAntiForgeryToken]
 372:          public async Task<IActionResult> DeleteConfirmed(int id)
 373:          {
 374:              Instructor instructor = await _context.Instructors
 375:                  .Include(i => i.CourseAssignments)
 376:                  .SingleAsync(i => i.ID == id);
 377:   
 378:              var departments = await _context.Departments
 379:                  .Where(d => d.InstructorID == id)
 380:                  .ToListAsync();
 381:              departments.ForEach(d => d.InstructorID = null);
 382:   
 383:              _context.Instructors.Remove(instructor);
 384:   
 385:              await _context.SaveChangesAsync();
 386:              return RedirectToAction(nameof(Index));
 387:          }
 388:          #endregion
 389:      }
 390:  }

The method accepts optional route data (id) and a query string parameter (courseID) that provide the ID values of the selected instructor and selected course. The parameters are provided by the Select hyperlinks on the page.

The code begins by creating an instance of the view model and putting in it the list of instructors. The code specifies eager loading for the Instructor.OfficeAssignment and the Instructor.CourseAssignments navigation properties. Within the CourseAssignments property, the Course property is loaded, and within that, the Enrollments and Department properties are loaded, and within each Enrollment entity the Student property is loaded.

[!code-csharpMain]

   1:  #define ExplicitLoading // or EagerLoading or ScaffoldedCode
   2:  #define EditCourses // or EditOfficeAssignment
   3:   
   4:  using System;
   5:  using System.Collections.Generic;
   6:  using System.Linq;
   7:  using System.Threading.Tasks;
   8:  using Microsoft.AspNetCore.Mvc;
   9:  using Microsoft.AspNetCore.Mvc.Rendering;
  10:  using Microsoft.EntityFrameworkCore;
  11:  using ContosoUniversity.Data;
  12:  using ContosoUniversity.Models;
  13:  #region snippet_Using
  14:  using ContosoUniversity.Models.SchoolViewModels;
  15:  #endregion
  16:   
  17:  namespace ContosoUniversity.Controllers
  18:  {
  19:      public class InstructorsController : Controller
  20:      {
  21:          private readonly SchoolContext _context;
  22:   
  23:          public InstructorsController(SchoolContext context)
  24:          {
  25:              _context = context;
  26:          }
  27:   
  28:          // GET: Instructors
  29:  #if ScaffoldedCode
  30:          #region snippet_ScaffoldedCode
  31:          public async Task<IActionResult> Index()
  32:          {
  33:              return View(await _context.Instructors.AsNoTracking().ToListAsync());
  34:          }
  35:          #endregion
  36:  #elif EagerLoading
  37:          #region snippet_EagerLoading
  38:          public async Task<IActionResult> Index(int? id, int? courseID)
  39:          {
  40:              var viewModel = new InstructorIndexData();
  41:              #region snippet_ThenInclude
  42:              viewModel.Instructors = await _context.Instructors
  43:                    .Include(i => i.OfficeAssignment)
  44:                    .Include(i => i.CourseAssignments)
  45:                      .ThenInclude(i => i.Course)
  46:                          .ThenInclude(i => i.Enrollments)
  47:                              .ThenInclude(i => i.Student)
  48:                    .Include(i => i.CourseAssignments)
  49:                      .ThenInclude(i => i.Course)
  50:                          .ThenInclude(i => i.Department)
  51:                    .AsNoTracking()
  52:                    .OrderBy(i => i.LastName)
  53:                    .ToListAsync();
  54:              #endregion
  55:              
  56:              if (id != null)
  57:              {
  58:                  ViewData["InstructorID"] = id.Value;
  59:                  Instructor instructor = viewModel.Instructors.Where(
  60:                      i => i.ID == id.Value).Single();
  61:                  viewModel.Courses = instructor.CourseAssignments.Select(s => s.Course);
  62:              }
  63:   
  64:              if (courseID != null)
  65:              {
  66:                  ViewData["CourseID"] = courseID.Value;
  67:                  viewModel.Enrollments = viewModel.Courses.Where(
  68:                      x => x.CourseID == courseID).Single().Enrollments;
  69:              }
  70:   
  71:              return View(viewModel);
  72:          }
  73:          #endregion
  74:  #elif ExplicitLoading
  75:          #region snippet_ExplicitLoading
  76:          public async Task<IActionResult> Index(int? id, int? courseID)
  77:          {
  78:              var viewModel = new InstructorIndexData();
  79:              viewModel.Instructors = await _context.Instructors
  80:                    .Include(i => i.OfficeAssignment)
  81:                    .Include(i => i.CourseAssignments)
  82:                      .ThenInclude(i => i.Course)
  83:                          .ThenInclude(i => i.Department)
  84:                    .OrderBy(i => i.LastName)
  85:                    .ToListAsync();
  86:   
  87:              if (id != null)
  88:              {
  89:                  ViewData["InstructorID"] = id.Value;
  90:                  Instructor instructor = viewModel.Instructors.Where(
  91:                      i => i.ID == id.Value).Single();
  92:                  viewModel.Courses = instructor.CourseAssignments.Select(s => s.Course);
  93:              }
  94:   
  95:              if (courseID != null)
  96:              {
  97:                  ViewData["CourseID"] = courseID.Value;
  98:                  var selectedCourse = viewModel.Courses.Where(x => x.CourseID == courseID).Single();
  99:                  await _context.Entry(selectedCourse).Collection(x => x.Enrollments).LoadAsync();
 100:                  foreach (Enrollment enrollment in selectedCourse.Enrollments)
 101:                  {
 102:                      await _context.Entry(enrollment).Reference(x => x.Student).LoadAsync();
 103:                  }
 104:                  viewModel.Enrollments = selectedCourse.Enrollments;
 105:              }
 106:   
 107:              return View(viewModel);
 108:          }
 109:          #endregion
 110:  #endif
 111:          // GET: Instructors/Details/5
 112:          public async Task<IActionResult> Details(int? id)
 113:          {
 114:              if (id == null)
 115:              {
 116:                  return NotFound();
 117:              }
 118:   
 119:              var instructor = await _context.Instructors
 120:                  .AsNoTracking()
 121:                  .SingleOrDefaultAsync(m => m.ID == id);
 122:              if (instructor == null)
 123:              {
 124:                  return NotFound();
 125:              }
 126:   
 127:              return View(instructor);
 128:          }
 129:   
 130:          // GET: Instructors/Create
 131:          #region snippet_Create
 132:          public IActionResult Create()
 133:          {
 134:              var instructor = new Instructor();
 135:              instructor.CourseAssignments = new List<CourseAssignment>();
 136:              PopulateAssignedCourseData(instructor);
 137:              return View();
 138:          }
 139:   
 140:          // POST: Instructors/Create
 141:          [HttpPost]
 142:          [ValidateAntiForgeryToken]
 143:          public async Task<IActionResult> Create([Bind("FirstMidName,HireDate,LastName,OfficeAssignment")] Instructor instructor, string[] selectedCourses)
 144:          {
 145:              if (selectedCourses != null)
 146:              {
 147:                  instructor.CourseAssignments = new List<CourseAssignment>();
 148:                  foreach (var course in selectedCourses)
 149:                  {
 150:                      var courseToAdd = new CourseAssignment { InstructorID = instructor.ID, CourseID = int.Parse(course) };
 151:                      instructor.CourseAssignments.Add(courseToAdd);
 152:                  }
 153:              }
 154:              if (ModelState.IsValid)
 155:              {
 156:                  _context.Add(instructor);
 157:                  await _context.SaveChangesAsync();
 158:                  return RedirectToAction(nameof(Index));
 159:              }
 160:              PopulateAssignedCourseData(instructor);
 161:              return View(instructor);
 162:          }
 163:          #endregion
 164:   
 165:          // GET: Instructors/Edit/5
 166:  #if EditOfficeAssignment
 167:          #region snippet_EditGetOA
 168:          public async Task<IActionResult> Edit(int? id)
 169:          {
 170:              if (id == null)
 171:              {
 172:                  return NotFound();
 173:              }
 174:   
 175:              var instructor = await _context.Instructors
 176:                  .Include(i => i.OfficeAssignment)
 177:                  .AsNoTracking()
 178:                  .SingleOrDefaultAsync(m => m.ID == id);
 179:              if (instructor == null)
 180:              {
 181:                  return NotFound();
 182:              }
 183:              return View(instructor);
 184:          }
 185:          #endregion
 186:  #elif EditCourses
 187:          #region snippet_EditGetCourses
 188:          public async Task<IActionResult> Edit(int? id)
 189:          {
 190:              if (id == null)
 191:              {
 192:                  return NotFound();
 193:              }
 194:   
 195:              var instructor = await _context.Instructors
 196:                  .Include(i => i.OfficeAssignment)
 197:                  .Include(i => i.CourseAssignments).ThenInclude(i => i.Course)
 198:                  .AsNoTracking()
 199:                  .SingleOrDefaultAsync(m => m.ID == id);
 200:              if (instructor == null)
 201:              {
 202:                  return NotFound();
 203:              }
 204:              PopulateAssignedCourseData(instructor);
 205:              return View(instructor);
 206:          }
 207:   
 208:          private void PopulateAssignedCourseData(Instructor instructor)
 209:          {
 210:              var allCourses = _context.Courses;
 211:              var instructorCourses = new HashSet<int>(instructor.CourseAssignments.Select(c => c.CourseID));
 212:              var viewModel = new List<AssignedCourseData>();
 213:              foreach (var course in allCourses)
 214:              {
 215:                  viewModel.Add(new AssignedCourseData
 216:                  {
 217:                      CourseID = course.CourseID,
 218:                      Title = course.Title,
 219:                      Assigned = instructorCourses.Contains(course.CourseID)
 220:                  });
 221:              }
 222:              ViewData["Courses"] = viewModel;
 223:          }
 224:          #endregion
 225:  #endif
 226:          // POST: Instructors/Edit/5
 227:          // To protect from overposting attacks, please enable the specific properties you want to bind to, for 
 228:          // more details see http://go.microsoft.com/fwlink/?LinkId=317598.
 229:  #if EditOfficeAssignment
 230:          #region snippet_EditPostOA
 231:          [HttpPost, ActionName("Edit")]
 232:          [ValidateAntiForgeryToken]
 233:          public async Task<IActionResult> EditPost(int? id)
 234:          {
 235:              if (id == null)
 236:              {
 237:                  return NotFound();
 238:              }
 239:   
 240:              var instructorToUpdate = await _context.Instructors
 241:                  .Include(i => i.OfficeAssignment)
 242:                  .SingleOrDefaultAsync(s => s.ID == id);
 243:   
 244:              if (await TryUpdateModelAsync<Instructor>(
 245:                  instructorToUpdate,
 246:                  "",
 247:                  i => i.FirstMidName, i => i.LastName, i => i.HireDate, i => i.OfficeAssignment))
 248:              {
 249:                  if (String.IsNullOrWhiteSpace(instructorToUpdate.OfficeAssignment?.Location))
 250:                  {
 251:                      instructorToUpdate.OfficeAssignment = null;
 252:                  }
 253:                  try
 254:                  {
 255:                      await _context.SaveChangesAsync();
 256:                  }
 257:                  catch (DbUpdateException /* ex */)
 258:                  {
 259:                      //Log the error (uncomment ex variable name and write a log.)
 260:                      ModelState.AddModelError("", "Unable to save changes. " +
 261:                          "Try again, and if the problem persists, " +
 262:                          "see your system administrator.");
 263:                  }
 264:                  return RedirectToAction(nameof(Index));
 265:              }
 266:              return View(instructorToUpdate);
 267:          }
 268:          #endregion
 269:   
 270:  #elif EditCourses
 271:          #region snippet_EditPostCourses
 272:          [HttpPost]
 273:          [ValidateAntiForgeryToken]
 274:          public async Task<IActionResult> Edit(int? id, string[] selectedCourses)
 275:          {
 276:              if (id == null)
 277:              {
 278:                  return NotFound();
 279:              }
 280:   
 281:              var instructorToUpdate = await _context.Instructors
 282:                  .Include(i => i.OfficeAssignment)
 283:                  .Include(i => i.CourseAssignments)
 284:                      .ThenInclude(i => i.Course)
 285:                  .SingleOrDefaultAsync(m => m.ID == id);
 286:   
 287:              if (await TryUpdateModelAsync<Instructor>(
 288:                  instructorToUpdate,
 289:                  "",
 290:                  i => i.FirstMidName, i => i.LastName, i => i.HireDate, i => i.OfficeAssignment))
 291:              {
 292:                  if (String.IsNullOrWhiteSpace(instructorToUpdate.OfficeAssignment?.Location))
 293:                  {
 294:                      instructorToUpdate.OfficeAssignment = null;
 295:                  }
 296:                  UpdateInstructorCourses(selectedCourses, instructorToUpdate);
 297:                  try
 298:                  {
 299:                      await _context.SaveChangesAsync();
 300:                  }
 301:                  catch (DbUpdateException /* ex */)
 302:                  {
 303:                      //Log the error (uncomment ex variable name and write a log.)
 304:                      ModelState.AddModelError("", "Unable to save changes. " +
 305:                          "Try again, and if the problem persists, " +
 306:                          "see your system administrator.");
 307:                  }
 308:                  return RedirectToAction(nameof(Index));
 309:              }
 310:              UpdateInstructorCourses(selectedCourses, instructorToUpdate);
 311:              PopulateAssignedCourseData(instructorToUpdate);
 312:              return View(instructorToUpdate);
 313:          }
 314:          #endregion
 315:   
 316:          #region snippet_UpdateCourses
 317:          private void UpdateInstructorCourses(string[] selectedCourses, Instructor instructorToUpdate)
 318:          {
 319:              if (selectedCourses == null)
 320:              {
 321:                  instructorToUpdate.CourseAssignments = new List<CourseAssignment>();
 322:                  return;
 323:              }
 324:   
 325:              var selectedCoursesHS = new HashSet<string>(selectedCourses);
 326:              var instructorCourses = new HashSet<int>
 327:                  (instructorToUpdate.CourseAssignments.Select(c => c.Course.CourseID));
 328:              foreach (var course in _context.Courses)
 329:              {
 330:                  if (selectedCoursesHS.Contains(course.CourseID.ToString()))
 331:                  {
 332:                      if (!instructorCourses.Contains(course.CourseID))
 333:                      {
 334:                          instructorToUpdate.CourseAssignments.Add(new CourseAssignment { InstructorID = instructorToUpdate.ID, CourseID = course.CourseID });
 335:                      }
 336:                  }
 337:                  else
 338:                  {
 339:   
 340:                      if (instructorCourses.Contains(course.CourseID))
 341:                      {
 342:                          CourseAssignment courseToRemove = instructorToUpdate.CourseAssignments.SingleOrDefault(i => i.CourseID == course.CourseID);
 343:                          _context.Remove(courseToRemove);
 344:                      }
 345:                  }
 346:              }
 347:          }
 348:          #endregion
 349:  #endif
 350:          // GET: Instructors/Delete/5
 351:          public async Task<IActionResult> Delete(int? id)
 352:          {
 353:              if (id == null)
 354:              {
 355:                  return NotFound();
 356:              }
 357:   
 358:              var instructor = await _context.Instructors
 359:                  .AsNoTracking()
 360:                  .SingleOrDefaultAsync(m => m.ID == id);
 361:              if (instructor == null)
 362:              {
 363:                  return NotFound();
 364:              }
 365:   
 366:              return View(instructor);
 367:          }
 368:   
 369:          #region snippet_DeleteConfirmed
 370:          [HttpPost, ActionName("Delete")]
 371:          [ValidateAntiForgeryToken]
 372:          public async Task<IActionResult> DeleteConfirmed(int id)
 373:          {
 374:              Instructor instructor = await _context.Instructors
 375:                  .Include(i => i.CourseAssignments)
 376:                  .SingleAsync(i => i.ID == id);
 377:   
 378:              var departments = await _context.Departments
 379:                  .Where(d => d.InstructorID == id)
 380:                  .ToListAsync();
 381:              departments.ForEach(d => d.InstructorID = null);
 382:   
 383:              _context.Instructors.Remove(instructor);
 384:   
 385:              await _context.SaveChangesAsync();
 386:              return RedirectToAction(nameof(Index));
 387:          }
 388:          #endregion
 389:      }
 390:  }

Since the view always requires the OfficeAssignment entity, it’s more efficient to fetch that in the same query. Course entities are required when an instructor is selected in the web page, so a single query is better than multiple queries only if the page is displayed more often with a course selected than without.

The code repeats CourseAssignments and Course because you need two properties from Course. The first string of ThenInclude calls gets CourseAssignment.Course, Course.Enrollments, and Enrollment.Student.

[!code-csharpMain]

   1:  #define ExplicitLoading // or EagerLoading or ScaffoldedCode
   2:  #define EditCourses // or EditOfficeAssignment
   3:   
   4:  using System;
   5:  using System.Collections.Generic;
   6:  using System.Linq;
   7:  using System.Threading.Tasks;
   8:  using Microsoft.AspNetCore.Mvc;
   9:  using Microsoft.AspNetCore.Mvc.Rendering;
  10:  using Microsoft.EntityFrameworkCore;
  11:  using ContosoUniversity.Data;
  12:  using ContosoUniversity.Models;
  13:  #region snippet_Using
  14:  using ContosoUniversity.Models.SchoolViewModels;
  15:  #endregion
  16:   
  17:  namespace ContosoUniversity.Controllers
  18:  {
  19:      public class InstructorsController : Controller
  20:      {
  21:          private readonly SchoolContext _context;
  22:   
  23:          public InstructorsController(SchoolContext context)
  24:          {
  25:              _context = context;
  26:          }
  27:   
  28:          // GET: Instructors
  29:  #if ScaffoldedCode
  30:          #region snippet_ScaffoldedCode
  31:          public async Task<IActionResult> Index()
  32:          {
  33:              return View(await _context.Instructors.AsNoTracking().ToListAsync());
  34:          }
  35:          #endregion
  36:  #elif EagerLoading
  37:          #region snippet_EagerLoading
  38:          public async Task<IActionResult> Index(int? id, int? courseID)
  39:          {
  40:              var viewModel = new InstructorIndexData();
  41:              #region snippet_ThenInclude
  42:              viewModel.Instructors = await _context.Instructors
  43:                    .Include(i => i.OfficeAssignment)
  44:                    .Include(i => i.CourseAssignments)
  45:                      .ThenInclude(i => i.Course)
  46:                          .ThenInclude(i => i.Enrollments)
  47:                              .ThenInclude(i => i.Student)
  48:                    .Include(i => i.CourseAssignments)
  49:                      .ThenInclude(i => i.Course)
  50:                          .ThenInclude(i => i.Department)
  51:                    .AsNoTracking()
  52:                    .OrderBy(i => i.LastName)
  53:                    .ToListAsync();
  54:              #endregion
  55:              
  56:              if (id != null)
  57:              {
  58:                  ViewData["InstructorID"] = id.Value;
  59:                  Instructor instructor = viewModel.Instructors.Where(
  60:                      i => i.ID == id.Value).Single();
  61:                  viewModel.Courses = instructor.CourseAssignments.Select(s => s.Course);
  62:              }
  63:   
  64:              if (courseID != null)
  65:              {
  66:                  ViewData["CourseID"] = courseID.Value;
  67:                  viewModel.Enrollments = viewModel.Courses.Where(
  68:                      x => x.CourseID == courseID).Single().Enrollments;
  69:              }
  70:   
  71:              return View(viewModel);
  72:          }
  73:          #endregion
  74:  #elif ExplicitLoading
  75:          #region snippet_ExplicitLoading
  76:          public async Task<IActionResult> Index(int? id, int? courseID)
  77:          {
  78:              var viewModel = new InstructorIndexData();
  79:              viewModel.Instructors = await _context.Instructors
  80:                    .Include(i => i.OfficeAssignment)
  81:                    .Include(i => i.CourseAssignments)
  82:                      .ThenInclude(i => i.Course)
  83:                          .ThenInclude(i => i.Department)
  84:                    .OrderBy(i => i.LastName)
  85:                    .ToListAsync();
  86:   
  87:              if (id != null)
  88:              {
  89:                  ViewData["InstructorID"] = id.Value;
  90:                  Instructor instructor = viewModel.Instructors.Where(
  91:                      i => i.ID == id.Value).Single();
  92:                  viewModel.Courses = instructor.CourseAssignments.Select(s => s.Course);
  93:              }
  94:   
  95:              if (courseID != null)
  96:              {
  97:                  ViewData["CourseID"] = courseID.Value;
  98:                  var selectedCourse = viewModel.Courses.Where(x => x.CourseID == courseID).Single();
  99:                  await _context.Entry(selectedCourse).Collection(x => x.Enrollments).LoadAsync();
 100:                  foreach (Enrollment enrollment in selectedCourse.Enrollments)
 101:                  {
 102:                      await _context.Entry(enrollment).Reference(x => x.Student).LoadAsync();
 103:                  }
 104:                  viewModel.Enrollments = selectedCourse.Enrollments;
 105:              }
 106:   
 107:              return View(viewModel);
 108:          }
 109:          #endregion
 110:  #endif
 111:          // GET: Instructors/Details/5
 112:          public async Task<IActionResult> Details(int? id)
 113:          {
 114:              if (id == null)
 115:              {
 116:                  return NotFound();
 117:              }
 118:   
 119:              var instructor = await _context.Instructors
 120:                  .AsNoTracking()
 121:                  .SingleOrDefaultAsync(m => m.ID == id);
 122:              if (instructor == null)
 123:              {
 124:                  return NotFound();
 125:              }
 126:   
 127:              return View(instructor);
 128:          }
 129:   
 130:          // GET: Instructors/Create
 131:          #region snippet_Create
 132:          public IActionResult Create()
 133:          {
 134:              var instructor = new Instructor();
 135:              instructor.CourseAssignments = new List<CourseAssignment>();
 136:              PopulateAssignedCourseData(instructor);
 137:              return View();
 138:          }
 139:   
 140:          // POST: Instructors/Create
 141:          [HttpPost]
 142:          [ValidateAntiForgeryToken]
 143:          public async Task<IActionResult> Create([Bind("FirstMidName,HireDate,LastName,OfficeAssignment")] Instructor instructor, string[] selectedCourses)
 144:          {
 145:              if (selectedCourses != null)
 146:              {
 147:                  instructor.CourseAssignments = new List<CourseAssignment>();
 148:                  foreach (var course in selectedCourses)
 149:                  {
 150:                      var courseToAdd = new CourseAssignment { InstructorID = instructor.ID, CourseID = int.Parse(course) };
 151:                      instructor.CourseAssignments.Add(courseToAdd);
 152:                  }
 153:              }
 154:              if (ModelState.IsValid)
 155:              {
 156:                  _context.Add(instructor);
 157:                  await _context.SaveChangesAsync();
 158:                  return RedirectToAction(nameof(Index));
 159:              }
 160:              PopulateAssignedCourseData(instructor);
 161:              return View(instructor);
 162:          }
 163:          #endregion
 164:   
 165:          // GET: Instructors/Edit/5
 166:  #if EditOfficeAssignment
 167:          #region snippet_EditGetOA
 168:          public async Task<IActionResult> Edit(int? id)
 169:          {
 170:              if (id == null)
 171:              {
 172:                  return NotFound();
 173:              }
 174:   
 175:              var instructor = await _context.Instructors
 176:                  .Include(i => i.OfficeAssignment)
 177:                  .AsNoTracking()
 178:                  .SingleOrDefaultAsync(m => m.ID == id);
 179:              if (instructor == null)
 180:              {
 181:                  return NotFound();
 182:              }
 183:              return View(instructor);
 184:          }
 185:          #endregion
 186:  #elif EditCourses
 187:          #region snippet_EditGetCourses
 188:          public async Task<IActionResult> Edit(int? id)
 189:          {
 190:              if (id == null)
 191:              {
 192:                  return NotFound();
 193:              }
 194:   
 195:              var instructor = await _context.Instructors
 196:                  .Include(i => i.OfficeAssignment)
 197:                  .Include(i => i.CourseAssignments).ThenInclude(i => i.Course)
 198:                  .AsNoTracking()
 199:                  .SingleOrDefaultAsync(m => m.ID == id);
 200:              if (instructor == null)
 201:              {
 202:                  return NotFound();
 203:              }
 204:              PopulateAssignedCourseData(instructor);
 205:              return View(instructor);
 206:          }
 207:   
 208:          private void PopulateAssignedCourseData(Instructor instructor)
 209:          {
 210:              var allCourses = _context.Courses;
 211:              var instructorCourses = new HashSet<int>(instructor.CourseAssignments.Select(c => c.CourseID));
 212:              var viewModel = new List<AssignedCourseData>();
 213:              foreach (var course in allCourses)
 214:              {
 215:                  viewModel.Add(new AssignedCourseData
 216:                  {
 217:                      CourseID = course.CourseID,
 218:                      Title = course.Title,
 219:                      Assigned = instructorCourses.Contains(course.CourseID)
 220:                  });
 221:              }
 222:              ViewData["Courses"] = viewModel;
 223:          }
 224:          #endregion
 225:  #endif
 226:          // POST: Instructors/Edit/5
 227:          // To protect from overposting attacks, please enable the specific properties you want to bind to, for 
 228:          // more details see http://go.microsoft.com/fwlink/?LinkId=317598.
 229:  #if EditOfficeAssignment
 230:          #region snippet_EditPostOA
 231:          [HttpPost, ActionName("Edit")]
 232:          [ValidateAntiForgeryToken]
 233:          public async Task<IActionResult> EditPost(int? id)
 234:          {
 235:              if (id == null)
 236:              {
 237:                  return NotFound();
 238:              }
 239:   
 240:              var instructorToUpdate = await _context.Instructors
 241:                  .Include(i => i.OfficeAssignment)
 242:                  .SingleOrDefaultAsync(s => s.ID == id);
 243:   
 244:              if (await TryUpdateModelAsync<Instructor>(
 245:                  instructorToUpdate,
 246:                  "",
 247:                  i => i.FirstMidName, i => i.LastName, i => i.HireDate, i => i.OfficeAssignment))
 248:              {
 249:                  if (String.IsNullOrWhiteSpace(instructorToUpdate.OfficeAssignment?.Location))
 250:                  {
 251:                      instructorToUpdate.OfficeAssignment = null;
 252:                  }
 253:                  try
 254:                  {
 255:                      await _context.SaveChangesAsync();
 256:                  }
 257:                  catch (DbUpdateException /* ex */)
 258:                  {
 259:                      //Log the error (uncomment ex variable name and write a log.)
 260:                      ModelState.AddModelError("", "Unable to save changes. " +
 261:                          "Try again, and if the problem persists, " +
 262:                          "see your system administrator.");
 263:                  }
 264:                  return RedirectToAction(nameof(Index));
 265:              }
 266:              return View(instructorToUpdate);
 267:          }
 268:          #endregion
 269:   
 270:  #elif EditCourses
 271:          #region snippet_EditPostCourses
 272:          [HttpPost]
 273:          [ValidateAntiForgeryToken]
 274:          public async Task<IActionResult> Edit(int? id, string[] selectedCourses)
 275:          {
 276:              if (id == null)
 277:              {
 278:                  return NotFound();
 279:              }
 280:   
 281:              var instructorToUpdate = await _context.Instructors
 282:                  .Include(i => i.OfficeAssignment)
 283:                  .Include(i => i.CourseAssignments)
 284:                      .ThenInclude(i => i.Course)
 285:                  .SingleOrDefaultAsync(m => m.ID == id);
 286:   
 287:              if (await TryUpdateModelAsync<Instructor>(
 288:                  instructorToUpdate,
 289:                  "",
 290:                  i => i.FirstMidName, i => i.LastName, i => i.HireDate, i => i.OfficeAssignment))
 291:              {
 292:                  if (String.IsNullOrWhiteSpace(instructorToUpdate.OfficeAssignment?.Location))
 293:                  {
 294:                      instructorToUpdate.OfficeAssignment = null;
 295:                  }
 296:                  UpdateInstructorCourses(selectedCourses, instructorToUpdate);
 297:                  try
 298:                  {
 299:                      await _context.SaveChangesAsync();
 300:                  }
 301:                  catch (DbUpdateException /* ex */)
 302:                  {
 303:                      //Log the error (uncomment ex variable name and write a log.)
 304:                      ModelState.AddModelError("", "Unable to save changes. " +
 305:                          "Try again, and if the problem persists, " +
 306:                          "see your system administrator.");
 307:                  }
 308:                  return RedirectToAction(nameof(Index));
 309:              }
 310:              UpdateInstructorCourses(selectedCourses, instructorToUpdate);
 311:              PopulateAssignedCourseData(instructorToUpdate);
 312:              return View(instructorToUpdate);
 313:          }
 314:          #endregion
 315:   
 316:          #region snippet_UpdateCourses
 317:          private void UpdateInstructorCourses(string[] selectedCourses, Instructor instructorToUpdate)
 318:          {
 319:              if (selectedCourses == null)
 320:              {
 321:                  instructorToUpdate.CourseAssignments = new List<CourseAssignment>();
 322:                  return;
 323:              }
 324:   
 325:              var selectedCoursesHS = new HashSet<string>(selectedCourses);
 326:              var instructorCourses = new HashSet<int>
 327:                  (instructorToUpdate.CourseAssignments.Select(c => c.Course.CourseID));
 328:              foreach (var course in _context.Courses)
 329:              {
 330:                  if (selectedCoursesHS.Contains(course.CourseID.ToString()))
 331:                  {
 332:                      if (!instructorCourses.Contains(course.CourseID))
 333:                      {
 334:                          instructorToUpdate.CourseAssignments.Add(new CourseAssignment { InstructorID = instructorToUpdate.ID, CourseID = course.CourseID });
 335:                      }
 336:                  }
 337:                  else
 338:                  {
 339:   
 340:                      if (instructorCourses.Contains(course.CourseID))
 341:                      {
 342:                          CourseAssignment courseToRemove = instructorToUpdate.CourseAssignments.SingleOrDefault(i => i.CourseID == course.CourseID);
 343:                          _context.Remove(courseToRemove);
 344:                      }
 345:                  }
 346:              }
 347:          }
 348:          #endregion
 349:  #endif
 350:          // GET: Instructors/Delete/5
 351:          public async Task<IActionResult> Delete(int? id)
 352:          {
 353:              if (id == null)
 354:              {
 355:                  return NotFound();
 356:              }
 357:   
 358:              var instructor = await _context.Instructors
 359:                  .AsNoTracking()
 360:                  .SingleOrDefaultAsync(m => m.ID == id);
 361:              if (instructor == null)
 362:              {
 363:                  return NotFound();
 364:              }
 365:   
 366:              return View(instructor);
 367:          }
 368:   
 369:          #region snippet_DeleteConfirmed
 370:          [HttpPost, ActionName("Delete")]
 371:          [ValidateAntiForgeryToken]
 372:          public async Task<IActionResult> DeleteConfirmed(int id)
 373:          {
 374:              Instructor instructor = await _context.Instructors
 375:                  .Include(i => i.CourseAssignments)
 376:                  .SingleAsync(i => i.ID == id);
 377:   
 378:              var departments = await _context.Departments
 379:                  .Where(d => d.InstructorID == id)
 380:                  .ToListAsync();
 381:              departments.ForEach(d => d.InstructorID = null);
 382:   
 383:              _context.Instructors.Remove(instructor);
 384:   
 385:              await _context.SaveChangesAsync();
 386:              return RedirectToAction(nameof(Index));
 387:          }
 388:          #endregion
 389:      }
 390:  }

At that point in the code, another ThenInclude would be for navigation properties of Student, which you don’t need. But calling Include starts over with Instructor properties, so you have to go through the chain again, this time specifying Course.Department instead of Course.Enrollments.

[!code-csharpMain]

   1:  #define ExplicitLoading // or EagerLoading or ScaffoldedCode
   2:  #define EditCourses // or EditOfficeAssignment
   3:   
   4:  using System;
   5:  using System.Collections.Generic;
   6:  using System.Linq;
   7:  using System.Threading.Tasks;
   8:  using Microsoft.AspNetCore.Mvc;
   9:  using Microsoft.AspNetCore.Mvc.Rendering;
  10:  using Microsoft.EntityFrameworkCore;
  11:  using ContosoUniversity.Data;
  12:  using ContosoUniversity.Models;
  13:  #region snippet_Using
  14:  using ContosoUniversity.Models.SchoolViewModels;
  15:  #endregion
  16:   
  17:  namespace ContosoUniversity.Controllers
  18:  {
  19:      public class InstructorsController : Controller
  20:      {
  21:          private readonly SchoolContext _context;
  22:   
  23:          public InstructorsController(SchoolContext context)
  24:          {
  25:              _context = context;
  26:          }
  27:   
  28:          // GET: Instructors
  29:  #if ScaffoldedCode
  30:          #region snippet_ScaffoldedCode
  31:          public async Task<IActionResult> Index()
  32:          {
  33:              return View(await _context.Instructors.AsNoTracking().ToListAsync());
  34:          }
  35:          #endregion
  36:  #elif EagerLoading
  37:          #region snippet_EagerLoading
  38:          public async Task<IActionResult> Index(int? id, int? courseID)
  39:          {
  40:              var viewModel = new InstructorIndexData();
  41:              #region snippet_ThenInclude
  42:              viewModel.Instructors = await _context.Instructors
  43:                    .Include(i => i.OfficeAssignment)
  44:                    .Include(i => i.CourseAssignments)
  45:                      .ThenInclude(i => i.Course)
  46:                          .ThenInclude(i => i.Enrollments)
  47:                              .ThenInclude(i => i.Student)
  48:                    .Include(i => i.CourseAssignments)
  49:                      .ThenInclude(i => i.Course)
  50:                          .ThenInclude(i => i.Department)
  51:                    .AsNoTracking()
  52:                    .OrderBy(i => i.LastName)
  53:                    .ToListAsync();
  54:              #endregion
  55:              
  56:              if (id != null)
  57:              {
  58:                  ViewData["InstructorID"] = id.Value;
  59:                  Instructor instructor = viewModel.Instructors.Where(
  60:                      i => i.ID == id.Value).Single();
  61:                  viewModel.Courses = instructor.CourseAssignments.Select(s => s.Course);
  62:              }
  63:   
  64:              if (courseID != null)
  65:              {
  66:                  ViewData["CourseID"] = courseID.Value;
  67:                  viewModel.Enrollments = viewModel.Courses.Where(
  68:                      x => x.CourseID == courseID).Single().Enrollments;
  69:              }
  70:   
  71:              return View(viewModel);
  72:          }
  73:          #endregion
  74:  #elif ExplicitLoading
  75:          #region snippet_ExplicitLoading
  76:          public async Task<IActionResult> Index(int? id, int? courseID)
  77:          {
  78:              var viewModel = new InstructorIndexData();
  79:              viewModel.Instructors = await _context.Instructors
  80:                    .Include(i => i.OfficeAssignment)
  81:                    .Include(i => i.CourseAssignments)
  82:                      .ThenInclude(i => i.Course)
  83:                          .ThenInclude(i => i.Department)
  84:                    .OrderBy(i => i.LastName)
  85:                    .ToListAsync();
  86:   
  87:              if (id != null)
  88:              {
  89:                  ViewData["InstructorID"] = id.Value;
  90:                  Instructor instructor = viewModel.Instructors.Where(
  91:                      i => i.ID == id.Value).Single();
  92:                  viewModel.Courses = instructor.CourseAssignments.Select(s => s.Course);
  93:              }
  94:   
  95:              if (courseID != null)
  96:              {
  97:                  ViewData["CourseID"] = courseID.Value;
  98:                  var selectedCourse = viewModel.Courses.Where(x => x.CourseID == courseID).Single();
  99:                  await _context.Entry(selectedCourse).Collection(x => x.Enrollments).LoadAsync();
 100:                  foreach (Enrollment enrollment in selectedCourse.Enrollments)
 101:                  {
 102:                      await _context.Entry(enrollment).Reference(x => x.Student).LoadAsync();
 103:                  }
 104:                  viewModel.Enrollments = selectedCourse.Enrollments;
 105:              }
 106:   
 107:              return View(viewModel);
 108:          }
 109:          #endregion
 110:  #endif
 111:          // GET: Instructors/Details/5
 112:          public async Task<IActionResult> Details(int? id)
 113:          {
 114:              if (id == null)
 115:              {
 116:                  return NotFound();
 117:              }
 118:   
 119:              var instructor = await _context.Instructors
 120:                  .AsNoTracking()
 121:                  .SingleOrDefaultAsync(m => m.ID == id);
 122:              if (instructor == null)
 123:              {
 124:                  return NotFound();
 125:              }
 126:   
 127:              return View(instructor);
 128:          }
 129:   
 130:          // GET: Instructors/Create
 131:          #region snippet_Create
 132:          public IActionResult Create()
 133:          {
 134:              var instructor = new Instructor();
 135:              instructor.CourseAssignments = new List<CourseAssignment>();
 136:              PopulateAssignedCourseData(instructor);
 137:              return View();
 138:          }
 139:   
 140:          // POST: Instructors/Create
 141:          [HttpPost]
 142:          [ValidateAntiForgeryToken]
 143:          public async Task<IActionResult> Create([Bind("FirstMidName,HireDate,LastName,OfficeAssignment")] Instructor instructor, string[] selectedCourses)
 144:          {
 145:              if (selectedCourses != null)
 146:              {
 147:                  instructor.CourseAssignments = new List<CourseAssignment>();
 148:                  foreach (var course in selectedCourses)
 149:                  {
 150:                      var courseToAdd = new CourseAssignment { InstructorID = instructor.ID, CourseID = int.Parse(course) };
 151:                      instructor.CourseAssignments.Add(courseToAdd);
 152:                  }
 153:              }
 154:              if (ModelState.IsValid)
 155:              {
 156:                  _context.Add(instructor);
 157:                  await _context.SaveChangesAsync();
 158:                  return RedirectToAction(nameof(Index));
 159:              }
 160:              PopulateAssignedCourseData(instructor);
 161:              return View(instructor);
 162:          }
 163:          #endregion
 164:   
 165:          // GET: Instructors/Edit/5
 166:  #if EditOfficeAssignment
 167:          #region snippet_EditGetOA
 168:          public async Task<IActionResult> Edit(int? id)
 169:          {
 170:              if (id == null)
 171:              {
 172:                  return NotFound();
 173:              }
 174:   
 175:              var instructor = await _context.Instructors
 176:                  .Include(i => i.OfficeAssignment)
 177:                  .AsNoTracking()
 178:                  .SingleOrDefaultAsync(m => m.ID == id);
 179:              if (instructor == null)
 180:              {
 181:                  return NotFound();
 182:              }
 183:              return View(instructor);
 184:          }
 185:          #endregion
 186:  #elif EditCourses
 187:          #region snippet_EditGetCourses
 188:          public async Task<IActionResult> Edit(int? id)
 189:          {
 190:              if (id == null)
 191:              {
 192:                  return NotFound();
 193:              }
 194:   
 195:              var instructor = await _context.Instructors
 196:                  .Include(i => i.OfficeAssignment)
 197:                  .Include(i => i.CourseAssignments).ThenInclude(i => i.Course)
 198:                  .AsNoTracking()
 199:                  .SingleOrDefaultAsync(m => m.ID == id);
 200:              if (instructor == null)
 201:              {
 202:                  return NotFound();
 203:              }
 204:              PopulateAssignedCourseData(instructor);
 205:              return View(instructor);
 206:          }
 207:   
 208:          private void PopulateAssignedCourseData(Instructor instructor)
 209:          {
 210:              var allCourses = _context.Courses;
 211:              var instructorCourses = new HashSet<int>(instructor.CourseAssignments.Select(c => c.CourseID));
 212:              var viewModel = new List<AssignedCourseData>();
 213:              foreach (var course in allCourses)
 214:              {
 215:                  viewModel.Add(new AssignedCourseData
 216:                  {
 217:                      CourseID = course.CourseID,
 218:                      Title = course.Title,
 219:                      Assigned = instructorCourses.Contains(course.CourseID)
 220:                  });
 221:              }
 222:              ViewData["Courses"] = viewModel;
 223:          }
 224:          #endregion
 225:  #endif
 226:          // POST: Instructors/Edit/5
 227:          // To protect from overposting attacks, please enable the specific properties you want to bind to, for 
 228:          // more details see http://go.microsoft.com/fwlink/?LinkId=317598.
 229:  #if EditOfficeAssignment
 230:          #region snippet_EditPostOA
 231:          [HttpPost, ActionName("Edit")]
 232:          [ValidateAntiForgeryToken]
 233:          public async Task<IActionResult> EditPost(int? id)
 234:          {
 235:              if (id == null)
 236:              {
 237:                  return NotFound();
 238:              }
 239:   
 240:              var instructorToUpdate = await _context.Instructors
 241:                  .Include(i => i.OfficeAssignment)
 242:                  .SingleOrDefaultAsync(s => s.ID == id);
 243:   
 244:              if (await TryUpdateModelAsync<Instructor>(
 245:                  instructorToUpdate,
 246:                  "",
 247:                  i => i.FirstMidName, i => i.LastName, i => i.HireDate, i => i.OfficeAssignment))
 248:              {
 249:                  if (String.IsNullOrWhiteSpace(instructorToUpdate.OfficeAssignment?.Location))
 250:                  {
 251:                      instructorToUpdate.OfficeAssignment = null;
 252:                  }
 253:                  try
 254:                  {
 255:                      await _context.SaveChangesAsync();
 256:                  }
 257:                  catch (DbUpdateException /* ex */)
 258:                  {
 259:                      //Log the error (uncomment ex variable name and write a log.)
 260:                      ModelState.AddModelError("", "Unable to save changes. " +
 261:                          "Try again, and if the problem persists, " +
 262:                          "see your system administrator.");
 263:                  }
 264:                  return RedirectToAction(nameof(Index));
 265:              }
 266:              return View(instructorToUpdate);
 267:          }
 268:          #endregion
 269:   
 270:  #elif EditCourses
 271:          #region snippet_EditPostCourses
 272:          [HttpPost]
 273:          [ValidateAntiForgeryToken]
 274:          public async Task<IActionResult> Edit(int? id, string[] selectedCourses)
 275:          {
 276:              if (id == null)
 277:              {
 278:                  return NotFound();
 279:              }
 280:   
 281:              var instructorToUpdate = await _context.Instructors
 282:                  .Include(i => i.OfficeAssignment)
 283:                  .Include(i => i.CourseAssignments)
 284:                      .ThenInclude(i => i.Course)
 285:                  .SingleOrDefaultAsync(m => m.ID == id);
 286:   
 287:              if (await TryUpdateModelAsync<Instructor>(
 288:                  instructorToUpdate,
 289:                  "",
 290:                  i => i.FirstMidName, i => i.LastName, i => i.HireDate, i => i.OfficeAssignment))
 291:              {
 292:                  if (String.IsNullOrWhiteSpace(instructorToUpdate.OfficeAssignment?.Location))
 293:                  {
 294:                      instructorToUpdate.OfficeAssignment = null;
 295:                  }
 296:                  UpdateInstructorCourses(selectedCourses, instructorToUpdate);
 297:                  try
 298:                  {
 299:                      await _context.SaveChangesAsync();
 300:                  }
 301:                  catch (DbUpdateException /* ex */)
 302:                  {
 303:                      //Log the error (uncomment ex variable name and write a log.)
 304:                      ModelState.AddModelError("", "Unable to save changes. " +
 305:                          "Try again, and if the problem persists, " +
 306:                          "see your system administrator.");
 307:                  }
 308:                  return RedirectToAction(nameof(Index));
 309:              }
 310:              UpdateInstructorCourses(selectedCourses, instructorToUpdate);
 311:              PopulateAssignedCourseData(instructorToUpdate);
 312:              return View(instructorToUpdate);
 313:          }
 314:          #endregion
 315:   
 316:          #region snippet_UpdateCourses
 317:          private void UpdateInstructorCourses(string[] selectedCourses, Instructor instructorToUpdate)
 318:          {
 319:              if (selectedCourses == null)
 320:              {
 321:                  instructorToUpdate.CourseAssignments = new List<CourseAssignment>();
 322:                  return;
 323:              }
 324:   
 325:              var selectedCoursesHS = new HashSet<string>(selectedCourses);
 326:              var instructorCourses = new HashSet<int>
 327:                  (instructorToUpdate.CourseAssignments.Select(c => c.Course.CourseID));
 328:              foreach (var course in _context.Courses)
 329:              {
 330:                  if (selectedCoursesHS.Contains(course.CourseID.ToString()))
 331:                  {
 332:                      if (!instructorCourses.Contains(course.CourseID))
 333:                      {
 334:                          instructorToUpdate.CourseAssignments.Add(new CourseAssignment { InstructorID = instructorToUpdate.ID, CourseID = course.CourseID });
 335:                      }
 336:                  }
 337:                  else
 338:                  {
 339:   
 340:                      if (instructorCourses.Contains(course.CourseID))
 341:                      {
 342:                          CourseAssignment courseToRemove = instructorToUpdate.CourseAssignments.SingleOrDefault(i => i.CourseID == course.CourseID);
 343:                          _context.Remove(courseToRemove);
 344:                      }
 345:                  }
 346:              }
 347:          }
 348:          #endregion
 349:  #endif
 350:          // GET: Instructors/Delete/5
 351:          public async Task<IActionResult> Delete(int? id)
 352:          {
 353:              if (id == null)
 354:              {
 355:                  return NotFound();
 356:              }
 357:   
 358:              var instructor = await _context.Instructors
 359:                  .AsNoTracking()
 360:                  .SingleOrDefaultAsync(m => m.ID == id);
 361:              if (instructor == null)
 362:              {
 363:                  return NotFound();
 364:              }
 365:   
 366:              return View(instructor);
 367:          }
 368:   
 369:          #region snippet_DeleteConfirmed
 370:          [HttpPost, ActionName("Delete")]
 371:          [ValidateAntiForgeryToken]
 372:          public async Task<IActionResult> DeleteConfirmed(int id)
 373:          {
 374:              Instructor instructor = await _context.Instructors
 375:                  .Include(i => i.CourseAssignments)
 376:                  .SingleAsync(i => i.ID == id);
 377:   
 378:              var departments = await _context.Departments
 379:                  .Where(d => d.InstructorID == id)
 380:                  .ToListAsync();
 381:              departments.ForEach(d => d.InstructorID = null);
 382:   
 383:              _context.Instructors.Remove(instructor);
 384:   
 385:              await _context.SaveChangesAsync();
 386:              return RedirectToAction(nameof(Index));
 387:          }
 388:          #endregion
 389:      }
 390:  }

The following code executes when an instructor was selected. The selected instructor is retrieved from the list of instructors in the view model. The view model’s Courses property is then loaded with the Course entities from that instructor’s CourseAssignments navigation property.

[!code-csharpMain]

   1:  #define ExplicitLoading // or EagerLoading or ScaffoldedCode
   2:  #define EditCourses // or EditOfficeAssignment
   3:   
   4:  using System;
   5:  using System.Collections.Generic;
   6:  using System.Linq;
   7:  using System.Threading.Tasks;
   8:  using Microsoft.AspNetCore.Mvc;
   9:  using Microsoft.AspNetCore.Mvc.Rendering;
  10:  using Microsoft.EntityFrameworkCore;
  11:  using ContosoUniversity.Data;
  12:  using ContosoUniversity.Models;
  13:  #region snippet_Using
  14:  using ContosoUniversity.Models.SchoolViewModels;
  15:  #endregion
  16:   
  17:  namespace ContosoUniversity.Controllers
  18:  {
  19:      public class InstructorsController : Controller
  20:      {
  21:          private readonly SchoolContext _context;
  22:   
  23:          public InstructorsController(SchoolContext context)
  24:          {
  25:              _context = context;
  26:          }
  27:   
  28:          // GET: Instructors
  29:  #if ScaffoldedCode
  30:          #region snippet_ScaffoldedCode
  31:          public async Task<IActionResult> Index()
  32:          {
  33:              return View(await _context.Instructors.AsNoTracking().ToListAsync());
  34:          }
  35:          #endregion
  36:  #elif EagerLoading
  37:          #region snippet_EagerLoading
  38:          public async Task<IActionResult> Index(int? id, int? courseID)
  39:          {
  40:              var viewModel = new InstructorIndexData();
  41:              #region snippet_ThenInclude
  42:              viewModel.Instructors = await _context.Instructors
  43:                    .Include(i => i.OfficeAssignment)
  44:                    .Include(i => i.CourseAssignments)
  45:                      .ThenInclude(i => i.Course)
  46:                          .ThenInclude(i => i.Enrollments)
  47:                              .ThenInclude(i => i.Student)
  48:                    .Include(i => i.CourseAssignments)
  49:                      .ThenInclude(i => i.Course)
  50:                          .ThenInclude(i => i.Department)
  51:                    .AsNoTracking()
  52:                    .OrderBy(i => i.LastName)
  53:                    .ToListAsync();
  54:              #endregion
  55:              
  56:              if (id != null)
  57:              {
  58:                  ViewData["InstructorID"] = id.Value;
  59:                  Instructor instructor = viewModel.Instructors.Where(
  60:                      i => i.ID == id.Value).Single();
  61:                  viewModel.Courses = instructor.CourseAssignments.Select(s => s.Course);
  62:              }
  63:   
  64:              if (courseID != null)
  65:              {
  66:                  ViewData["CourseID"] = courseID.Value;
  67:                  viewModel.Enrollments = viewModel.Courses.Where(
  68:                      x => x.CourseID == courseID).Single().Enrollments;
  69:              }
  70:   
  71:              return View(viewModel);
  72:          }
  73:          #endregion
  74:  #elif ExplicitLoading
  75:          #region snippet_ExplicitLoading
  76:          public async Task<IActionResult> Index(int? id, int? courseID)
  77:          {
  78:              var viewModel = new InstructorIndexData();
  79:              viewModel.Instructors = await _context.Instructors
  80:                    .Include(i => i.OfficeAssignment)
  81:                    .Include(i => i.CourseAssignments)
  82:                      .ThenInclude(i => i.Course)
  83:                          .ThenInclude(i => i.Department)
  84:                    .OrderBy(i => i.LastName)
  85:                    .ToListAsync();
  86:   
  87:              if (id != null)
  88:              {
  89:                  ViewData["InstructorID"] = id.Value;
  90:                  Instructor instructor = viewModel.Instructors.Where(
  91:                      i => i.ID == id.Value).Single();
  92:                  viewModel.Courses = instructor.CourseAssignments.Select(s => s.Course);
  93:              }
  94:   
  95:              if (courseID != null)
  96:              {
  97:                  ViewData["CourseID"] = courseID.Value;
  98:                  var selectedCourse = viewModel.Courses.Where(x => x.CourseID == courseID).Single();
  99:                  await _context.Entry(selectedCourse).Collection(x => x.Enrollments).LoadAsync();
 100:                  foreach (Enrollment enrollment in selectedCourse.Enrollments)
 101:                  {
 102:                      await _context.Entry(enrollment).Reference(x => x.Student).LoadAsync();
 103:                  }
 104:                  viewModel.Enrollments = selectedCourse.Enrollments;
 105:              }
 106:   
 107:              return View(viewModel);
 108:          }
 109:          #endregion
 110:  #endif
 111:          // GET: Instructors/Details/5
 112:          public async Task<IActionResult> Details(int? id)
 113:          {
 114:              if (id == null)
 115:              {
 116:                  return NotFound();
 117:              }
 118:   
 119:              var instructor = await _context.Instructors
 120:                  .AsNoTracking()
 121:                  .SingleOrDefaultAsync(m => m.ID == id);
 122:              if (instructor == null)
 123:              {
 124:                  return NotFound();
 125:              }
 126:   
 127:              return View(instructor);
 128:          }
 129:   
 130:          // GET: Instructors/Create
 131:          #region snippet_Create
 132:          public IActionResult Create()
 133:          {
 134:              var instructor = new Instructor();
 135:              instructor.CourseAssignments = new List<CourseAssignment>();
 136:              PopulateAssignedCourseData(instructor);
 137:              return View();
 138:          }
 139:   
 140:          // POST: Instructors/Create
 141:          [HttpPost]
 142:          [ValidateAntiForgeryToken]
 143:          public async Task<IActionResult> Create([Bind("FirstMidName,HireDate,LastName,OfficeAssignment")] Instructor instructor, string[] selectedCourses)
 144:          {
 145:              if (selectedCourses != null)
 146:              {
 147:                  instructor.CourseAssignments = new List<CourseAssignment>();
 148:                  foreach (var course in selectedCourses)
 149:                  {
 150:                      var courseToAdd = new CourseAssignment { InstructorID = instructor.ID, CourseID = int.Parse(course) };
 151:                      instructor.CourseAssignments.Add(courseToAdd);
 152:                  }
 153:              }
 154:              if (ModelState.IsValid)
 155:              {
 156:                  _context.Add(instructor);
 157:                  await _context.SaveChangesAsync();
 158:                  return RedirectToAction(nameof(Index));
 159:              }
 160:              PopulateAssignedCourseData(instructor);
 161:              return View(instructor);
 162:          }
 163:          #endregion
 164:   
 165:          // GET: Instructors/Edit/5
 166:  #if EditOfficeAssignment
 167:          #region snippet_EditGetOA
 168:          public async Task<IActionResult> Edit(int? id)
 169:          {
 170:              if (id == null)
 171:              {
 172:                  return NotFound();
 173:              }
 174:   
 175:              var instructor = await _context.Instructors
 176:                  .Include(i => i.OfficeAssignment)
 177:                  .AsNoTracking()
 178:                  .SingleOrDefaultAsync(m => m.ID == id);
 179:              if (instructor == null)
 180:              {
 181:                  return NotFound();
 182:              }
 183:              return View(instructor);
 184:          }
 185:          #endregion
 186:  #elif EditCourses
 187:          #region snippet_EditGetCourses
 188:          public async Task<IActionResult> Edit(int? id)
 189:          {
 190:              if (id == null)
 191:              {
 192:                  return NotFound();
 193:              }
 194:   
 195:              var instructor = await _context.Instructors
 196:                  .Include(i => i.OfficeAssignment)
 197:                  .Include(i => i.CourseAssignments).ThenInclude(i => i.Course)
 198:                  .AsNoTracking()
 199:                  .SingleOrDefaultAsync(m => m.ID == id);
 200:              if (instructor == null)
 201:              {
 202:                  return NotFound();
 203:              }
 204:              PopulateAssignedCourseData(instructor);
 205:              return View(instructor);
 206:          }
 207:   
 208:          private void PopulateAssignedCourseData(Instructor instructor)
 209:          {
 210:              var allCourses = _context.Courses;
 211:              var instructorCourses = new HashSet<int>(instructor.CourseAssignments.Select(c => c.CourseID));
 212:              var viewModel = new List<AssignedCourseData>();
 213:              foreach (var course in allCourses)
 214:              {
 215:                  viewModel.Add(new AssignedCourseData
 216:                  {
 217:                      CourseID = course.CourseID,
 218:                      Title = course.Title,
 219:                      Assigned = instructorCourses.Contains(course.CourseID)
 220:                  });
 221:              }
 222:              ViewData["Courses"] = viewModel;
 223:          }
 224:          #endregion
 225:  #endif
 226:          // POST: Instructors/Edit/5
 227:          // To protect from overposting attacks, please enable the specific properties you want to bind to, for 
 228:          // more details see http://go.microsoft.com/fwlink/?LinkId=317598.
 229:  #if EditOfficeAssignment
 230:          #region snippet_EditPostOA
 231:          [HttpPost, ActionName("Edit")]
 232:          [ValidateAntiForgeryToken]
 233:          public async Task<IActionResult> EditPost(int? id)
 234:          {
 235:              if (id == null)
 236:              {
 237:                  return NotFound();
 238:              }
 239:   
 240:              var instructorToUpdate = await _context.Instructors
 241:                  .Include(i => i.OfficeAssignment)
 242:                  .SingleOrDefaultAsync(s => s.ID == id);
 243:   
 244:              if (await TryUpdateModelAsync<Instructor>(
 245:                  instructorToUpdate,
 246:                  "",
 247:                  i => i.FirstMidName, i => i.LastName, i => i.HireDate, i => i.OfficeAssignment))
 248:              {
 249:                  if (String.IsNullOrWhiteSpace(instructorToUpdate.OfficeAssignment?.Location))
 250:                  {
 251:                      instructorToUpdate.OfficeAssignment = null;
 252:                  }
 253:                  try
 254:                  {
 255:                      await _context.SaveChangesAsync();
 256:                  }
 257:                  catch (DbUpdateException /* ex */)
 258:                  {
 259:                      //Log the error (uncomment ex variable name and write a log.)
 260:                      ModelState.AddModelError("", "Unable to save changes. " +
 261:                          "Try again, and if the problem persists, " +
 262:                          "see your system administrator.");
 263:                  }
 264:                  return RedirectToAction(nameof(Index));
 265:              }
 266:              return View(instructorToUpdate);
 267:          }
 268:          #endregion
 269:   
 270:  #elif EditCourses
 271:          #region snippet_EditPostCourses
 272:          [HttpPost]
 273:          [ValidateAntiForgeryToken]
 274:          public async Task<IActionResult> Edit(int? id, string[] selectedCourses)
 275:          {
 276:              if (id == null)
 277:              {
 278:                  return NotFound();
 279:              }
 280:   
 281:              var instructorToUpdate = await _context.Instructors
 282:                  .Include(i => i.OfficeAssignment)
 283:                  .Include(i => i.CourseAssignments)
 284:                      .ThenInclude(i => i.Course)
 285:                  .SingleOrDefaultAsync(m => m.ID == id);
 286:   
 287:              if (await TryUpdateModelAsync<Instructor>(
 288:                  instructorToUpdate,
 289:                  "",
 290:                  i => i.FirstMidName, i => i.LastName, i => i.HireDate, i => i.OfficeAssignment))
 291:              {
 292:                  if (String.IsNullOrWhiteSpace(instructorToUpdate.OfficeAssignment?.Location))
 293:                  {
 294:                      instructorToUpdate.OfficeAssignment = null;
 295:                  }
 296:                  UpdateInstructorCourses(selectedCourses, instructorToUpdate);
 297:                  try
 298:                  {
 299:                      await _context.SaveChangesAsync();
 300:                  }
 301:                  catch (DbUpdateException /* ex */)
 302:                  {
 303:                      //Log the error (uncomment ex variable name and write a log.)
 304:                      ModelState.AddModelError("", "Unable to save changes. " +
 305:                          "Try again, and if the problem persists, " +
 306:                          "see your system administrator.");
 307:                  }
 308:                  return RedirectToAction(nameof(Index));
 309:              }
 310:              UpdateInstructorCourses(selectedCourses, instructorToUpdate);
 311:              PopulateAssignedCourseData(instructorToUpdate);
 312:              return View(instructorToUpdate);
 313:          }
 314:          #endregion
 315:   
 316:          #region snippet_UpdateCourses
 317:          private void UpdateInstructorCourses(string[] selectedCourses, Instructor instructorToUpdate)
 318:          {
 319:              if (selectedCourses == null)
 320:              {
 321:                  instructorToUpdate.CourseAssignments = new List<CourseAssignment>();
 322:                  return;
 323:              }
 324:   
 325:              var selectedCoursesHS = new HashSet<string>(selectedCourses);
 326:              var instructorCourses = new HashSet<int>
 327:                  (instructorToUpdate.CourseAssignments.Select(c => c.Course.CourseID));
 328:              foreach (var course in _context.Courses)
 329:              {
 330:                  if (selectedCoursesHS.Contains(course.CourseID.ToString()))
 331:                  {
 332:                      if (!instructorCourses.Contains(course.CourseID))
 333:                      {
 334:                          instructorToUpdate.CourseAssignments.Add(new CourseAssignment { InstructorID = instructorToUpdate.ID, CourseID = course.CourseID });
 335:                      }
 336:                  }
 337:                  else
 338:                  {
 339:   
 340:                      if (instructorCourses.Contains(course.CourseID))
 341:                      {
 342:                          CourseAssignment courseToRemove = instructorToUpdate.CourseAssignments.SingleOrDefault(i => i.CourseID == course.CourseID);
 343:                          _context.Remove(courseToRemove);
 344:                      }
 345:                  }
 346:              }
 347:          }
 348:          #endregion
 349:  #endif
 350:          // GET: Instructors/Delete/5
 351:          public async Task<IActionResult> Delete(int? id)
 352:          {
 353:              if (id == null)
 354:              {
 355:                  return NotFound();
 356:              }
 357:   
 358:              var instructor = await _context.Instructors
 359:                  .AsNoTracking()
 360:                  .SingleOrDefaultAsync(m => m.ID == id);
 361:              if (instructor == null)
 362:              {
 363:                  return NotFound();
 364:              }
 365:   
 366:              return View(instructor);
 367:          }
 368:   
 369:          #region snippet_DeleteConfirmed
 370:          [HttpPost, ActionName("Delete")]
 371:          [ValidateAntiForgeryToken]
 372:          public async Task<IActionResult> DeleteConfirmed(int id)
 373:          {
 374:              Instructor instructor = await _context.Instructors
 375:                  .Include(i => i.CourseAssignments)
 376:                  .SingleAsync(i => i.ID == id);
 377:   
 378:              var departments = await _context.Departments
 379:                  .Where(d => d.InstructorID == id)
 380:                  .ToListAsync();
 381:              departments.ForEach(d => d.InstructorID = null);
 382:   
 383:              _context.Instructors.Remove(instructor);
 384:   
 385:              await _context.SaveChangesAsync();
 386:              return RedirectToAction(nameof(Index));
 387:          }
 388:          #endregion
 389:      }
 390:  }

The Where method returns a collection, but in this case the criteria passed to that method result in only a single Instructor entity being returned. The Single method converts the collection into a single Instructor entity, which gives you access to that entity’s CourseAssignments property. The CourseAssignments property contains CourseAssignment entities, from which you want only the related Course entities.

You use the Single method on a collection when you know the collection will have only one item. The Single method throws an exception if the collection passed to it is empty or if there’s more than one item. An alternative is SingleOrDefault, which returns a default value (null in this case) if the collection is empty. However, in this case that would still result in an exception (from trying to find a Courses property on a null reference), and the exception message would less clearly indicate the cause of the problem. When you call the Single method, you can also pass in the Where condition instead of calling the Where method separately:

Instead of:

Next, if a course was selected, the selected course is retrieved from the list of courses in the view model. Then the view model’s Enrollments property is loaded with the Enrollment entities from that course’s Enrollments navigation property.

[!code-csharpMain]

   1:  #define ExplicitLoading // or EagerLoading or ScaffoldedCode
   2:  #define EditCourses // or EditOfficeAssignment
   3:   
   4:  using System;
   5:  using System.Collections.Generic;
   6:  using System.Linq;
   7:  using System.Threading.Tasks;
   8:  using Microsoft.AspNetCore.Mvc;
   9:  using Microsoft.AspNetCore.Mvc.Rendering;
  10:  using Microsoft.EntityFrameworkCore;
  11:  using ContosoUniversity.Data;
  12:  using ContosoUniversity.Models;
  13:  #region snippet_Using
  14:  using ContosoUniversity.Models.SchoolViewModels;
  15:  #endregion
  16:   
  17:  namespace ContosoUniversity.Controllers
  18:  {
  19:      public class InstructorsController : Controller
  20:      {
  21:          private readonly SchoolContext _context;
  22:   
  23:          public InstructorsController(SchoolContext context)
  24:          {
  25:              _context = context;
  26:          }
  27:   
  28:          // GET: Instructors
  29:  #if ScaffoldedCode
  30:          #region snippet_ScaffoldedCode
  31:          public async Task<IActionResult> Index()
  32:          {
  33:              return View(await _context.Instructors.AsNoTracking().ToListAsync());
  34:          }
  35:          #endregion
  36:  #elif EagerLoading
  37:          #region snippet_EagerLoading
  38:          public async Task<IActionResult> Index(int? id, int? courseID)
  39:          {
  40:              var viewModel = new InstructorIndexData();
  41:              #region snippet_ThenInclude
  42:              viewModel.Instructors = await _context.Instructors
  43:                    .Include(i => i.OfficeAssignment)
  44:                    .Include(i => i.CourseAssignments)
  45:                      .ThenInclude(i => i.Course)
  46:                          .ThenInclude(i => i.Enrollments)
  47:                              .ThenInclude(i => i.Student)
  48:                    .Include(i => i.CourseAssignments)
  49:                      .ThenInclude(i => i.Course)
  50:                          .ThenInclude(i => i.Department)
  51:                    .AsNoTracking()
  52:                    .OrderBy(i => i.LastName)
  53:                    .ToListAsync();
  54:              #endregion
  55:              
  56:              if (id != null)
  57:              {
  58:                  ViewData["InstructorID"] = id.Value;
  59:                  Instructor instructor = viewModel.Instructors.Where(
  60:                      i => i.ID == id.Value).Single();
  61:                  viewModel.Courses = instructor.CourseAssignments.Select(s => s.Course);
  62:              }
  63:   
  64:              if (courseID != null)
  65:              {
  66:                  ViewData["CourseID"] = courseID.Value;
  67:                  viewModel.Enrollments = viewModel.Courses.Where(
  68:                      x => x.CourseID == courseID).Single().Enrollments;
  69:              }
  70:   
  71:              return View(viewModel);
  72:          }
  73:          #endregion
  74:  #elif ExplicitLoading
  75:          #region snippet_ExplicitLoading
  76:          public async Task<IActionResult> Index(int? id, int? courseID)
  77:          {
  78:              var viewModel = new InstructorIndexData();
  79:              viewModel.Instructors = await _context.Instructors
  80:                    .Include(i => i.OfficeAssignment)
  81:                    .Include(i => i.CourseAssignments)
  82:                      .ThenInclude(i => i.Course)
  83:                          .ThenInclude(i => i.Department)
  84:                    .OrderBy(i => i.LastName)
  85:                    .ToListAsync();
  86:   
  87:              if (id != null)
  88:              {
  89:                  ViewData["InstructorID"] = id.Value;
  90:                  Instructor instructor = viewModel.Instructors.Where(
  91:                      i => i.ID == id.Value).Single();
  92:                  viewModel.Courses = instructor.CourseAssignments.Select(s => s.Course);
  93:              }
  94:   
  95:              if (courseID != null)
  96:              {
  97:                  ViewData["CourseID"] = courseID.Value;
  98:                  var selectedCourse = viewModel.Courses.Where(x => x.CourseID == courseID).Single();
  99:                  await _context.Entry(selectedCourse).Collection(x => x.Enrollments).LoadAsync();
 100:                  foreach (Enrollment enrollment in selectedCourse.Enrollments)
 101:                  {
 102:                      await _context.Entry(enrollment).Reference(x => x.Student).LoadAsync();
 103:                  }
 104:                  viewModel.Enrollments = selectedCourse.Enrollments;
 105:              }
 106:   
 107:              return View(viewModel);
 108:          }
 109:          #endregion
 110:  #endif
 111:          // GET: Instructors/Details/5
 112:          public async Task<IActionResult> Details(int? id)
 113:          {
 114:              if (id == null)
 115:              {
 116:                  return NotFound();
 117:              }
 118:   
 119:              var instructor = await _context.Instructors
 120:                  .AsNoTracking()
 121:                  .SingleOrDefaultAsync(m => m.ID == id);
 122:              if (instructor == null)
 123:              {
 124:                  return NotFound();
 125:              }
 126:   
 127:              return View(instructor);
 128:          }
 129:   
 130:          // GET: Instructors/Create
 131:          #region snippet_Create
 132:          public IActionResult Create()
 133:          {
 134:              var instructor = new Instructor();
 135:              instructor.CourseAssignments = new List<CourseAssignment>();
 136:              PopulateAssignedCourseData(instructor);
 137:              return View();
 138:          }
 139:   
 140:          // POST: Instructors/Create
 141:          [HttpPost]
 142:          [ValidateAntiForgeryToken]
 143:          public async Task<IActionResult> Create([Bind("FirstMidName,HireDate,LastName,OfficeAssignment")] Instructor instructor, string[] selectedCourses)
 144:          {
 145:              if (selectedCourses != null)
 146:              {
 147:                  instructor.CourseAssignments = new List<CourseAssignment>();
 148:                  foreach (var course in selectedCourses)
 149:                  {
 150:                      var courseToAdd = new CourseAssignment { InstructorID = instructor.ID, CourseID = int.Parse(course) };
 151:                      instructor.CourseAssignments.Add(courseToAdd);
 152:                  }
 153:              }
 154:              if (ModelState.IsValid)
 155:              {
 156:                  _context.Add(instructor);
 157:                  await _context.SaveChangesAsync();
 158:                  return RedirectToAction(nameof(Index));
 159:              }
 160:              PopulateAssignedCourseData(instructor);
 161:              return View(instructor);
 162:          }
 163:          #endregion
 164:   
 165:          // GET: Instructors/Edit/5
 166:  #if EditOfficeAssignment
 167:          #region snippet_EditGetOA
 168:          public async Task<IActionResult> Edit(int? id)
 169:          {
 170:              if (id == null)
 171:              {
 172:                  return NotFound();
 173:              }
 174:   
 175:              var instructor = await _context.Instructors
 176:                  .Include(i => i.OfficeAssignment)
 177:                  .AsNoTracking()
 178:                  .SingleOrDefaultAsync(m => m.ID == id);
 179:              if (instructor == null)
 180:              {
 181:                  return NotFound();
 182:              }
 183:              return View(instructor);
 184:          }
 185:          #endregion
 186:  #elif EditCourses
 187:          #region snippet_EditGetCourses
 188:          public async Task<IActionResult> Edit(int? id)
 189:          {
 190:              if (id == null)
 191:              {
 192:                  return NotFound();
 193:              }
 194:   
 195:              var instructor = await _context.Instructors
 196:                  .Include(i => i.OfficeAssignment)
 197:                  .Include(i => i.CourseAssignments).ThenInclude(i => i.Course)
 198:                  .AsNoTracking()
 199:                  .SingleOrDefaultAsync(m => m.ID == id);
 200:              if (instructor == null)
 201:              {
 202:                  return NotFound();
 203:              }
 204:              PopulateAssignedCourseData(instructor);
 205:              return View(instructor);
 206:          }
 207:   
 208:          private void PopulateAssignedCourseData(Instructor instructor)
 209:          {
 210:              var allCourses = _context.Courses;
 211:              var instructorCourses = new HashSet<int>(instructor.CourseAssignments.Select(c => c.CourseID));
 212:              var viewModel = new List<AssignedCourseData>();
 213:              foreach (var course in allCourses)
 214:              {
 215:                  viewModel.Add(new AssignedCourseData
 216:                  {
 217:                      CourseID = course.CourseID,
 218:                      Title = course.Title,
 219:                      Assigned = instructorCourses.Contains(course.CourseID)
 220:                  });
 221:              }
 222:              ViewData["Courses"] = viewModel;
 223:          }
 224:          #endregion
 225:  #endif
 226:          // POST: Instructors/Edit/5
 227:          // To protect from overposting attacks, please enable the specific properties you want to bind to, for 
 228:          // more details see http://go.microsoft.com/fwlink/?LinkId=317598.
 229:  #if EditOfficeAssignment
 230:          #region snippet_EditPostOA
 231:          [HttpPost, ActionName("Edit")]
 232:          [ValidateAntiForgeryToken]
 233:          public async Task<IActionResult> EditPost(int? id)
 234:          {
 235:              if (id == null)
 236:              {
 237:                  return NotFound();
 238:              }
 239:   
 240:              var instructorToUpdate = await _context.Instructors
 241:                  .Include(i => i.OfficeAssignment)
 242:                  .SingleOrDefaultAsync(s => s.ID == id);
 243:   
 244:              if (await TryUpdateModelAsync<Instructor>(
 245:                  instructorToUpdate,
 246:                  "",
 247:                  i => i.FirstMidName, i => i.LastName, i => i.HireDate, i => i.OfficeAssignment))
 248:              {
 249:                  if (String.IsNullOrWhiteSpace(instructorToUpdate.OfficeAssignment?.Location))
 250:                  {
 251:                      instructorToUpdate.OfficeAssignment = null;
 252:                  }
 253:                  try
 254:                  {
 255:                      await _context.SaveChangesAsync();
 256:                  }
 257:                  catch (DbUpdateException /* ex */)
 258:                  {
 259:                      //Log the error (uncomment ex variable name and write a log.)
 260:                      ModelState.AddModelError("", "Unable to save changes. " +
 261:                          "Try again, and if the problem persists, " +
 262:                          "see your system administrator.");
 263:                  }
 264:                  return RedirectToAction(nameof(Index));
 265:              }
 266:              return View(instructorToUpdate);
 267:          }
 268:          #endregion
 269:   
 270:  #elif EditCourses
 271:          #region snippet_EditPostCourses
 272:          [HttpPost]
 273:          [ValidateAntiForgeryToken]
 274:          public async Task<IActionResult> Edit(int? id, string[] selectedCourses)
 275:          {
 276:              if (id == null)
 277:              {
 278:                  return NotFound();
 279:              }
 280:   
 281:              var instructorToUpdate = await _context.Instructors
 282:                  .Include(i => i.OfficeAssignment)
 283:                  .Include(i => i.CourseAssignments)
 284:                      .ThenInclude(i => i.Course)
 285:                  .SingleOrDefaultAsync(m => m.ID == id);
 286:   
 287:              if (await TryUpdateModelAsync<Instructor>(
 288:                  instructorToUpdate,
 289:                  "",
 290:                  i => i.FirstMidName, i => i.LastName, i => i.HireDate, i => i.OfficeAssignment))
 291:              {
 292:                  if (String.IsNullOrWhiteSpace(instructorToUpdate.OfficeAssignment?.Location))
 293:                  {
 294:                      instructorToUpdate.OfficeAssignment = null;
 295:                  }
 296:                  UpdateInstructorCourses(selectedCourses, instructorToUpdate);
 297:                  try
 298:                  {
 299:                      await _context.SaveChangesAsync();
 300:                  }
 301:                  catch (DbUpdateException /* ex */)
 302:                  {
 303:                      //Log the error (uncomment ex variable name and write a log.)
 304:                      ModelState.AddModelError("", "Unable to save changes. " +
 305:                          "Try again, and if the problem persists, " +
 306:                          "see your system administrator.");
 307:                  }
 308:                  return RedirectToAction(nameof(Index));
 309:              }
 310:              UpdateInstructorCourses(selectedCourses, instructorToUpdate);
 311:              PopulateAssignedCourseData(instructorToUpdate);
 312:              return View(instructorToUpdate);
 313:          }
 314:          #endregion
 315:   
 316:          #region snippet_UpdateCourses
 317:          private void UpdateInstructorCourses(string[] selectedCourses, Instructor instructorToUpdate)
 318:          {
 319:              if (selectedCourses == null)
 320:              {
 321:                  instructorToUpdate.CourseAssignments = new List<CourseAssignment>();
 322:                  return;
 323:              }
 324:   
 325:              var selectedCoursesHS = new HashSet<string>(selectedCourses);
 326:              var instructorCourses = new HashSet<int>
 327:                  (instructorToUpdate.CourseAssignments.Select(c => c.Course.CourseID));
 328:              foreach (var course in _context.Courses)
 329:              {
 330:                  if (selectedCoursesHS.Contains(course.CourseID.ToString()))
 331:                  {
 332:                      if (!instructorCourses.Contains(course.CourseID))
 333:                      {
 334:                          instructorToUpdate.CourseAssignments.Add(new CourseAssignment { InstructorID = instructorToUpdate.ID, CourseID = course.CourseID });
 335:                      }
 336:                  }
 337:                  else
 338:                  {
 339:   
 340:                      if (instructorCourses.Contains(course.CourseID))
 341:                      {
 342:                          CourseAssignment courseToRemove = instructorToUpdate.CourseAssignments.SingleOrDefault(i => i.CourseID == course.CourseID);
 343:                          _context.Remove(courseToRemove);
 344:                      }
 345:                  }
 346:              }
 347:          }
 348:          #endregion
 349:  #endif
 350:          // GET: Instructors/Delete/5
 351:          public async Task<IActionResult> Delete(int? id)
 352:          {
 353:              if (id == null)
 354:              {
 355:                  return NotFound();
 356:              }
 357:   
 358:              var instructor = await _context.Instructors
 359:                  .AsNoTracking()
 360:                  .SingleOrDefaultAsync(m => m.ID == id);
 361:              if (instructor == null)
 362:              {
 363:                  return NotFound();
 364:              }
 365:   
 366:              return View(instructor);
 367:          }
 368:   
 369:          #region snippet_DeleteConfirmed
 370:          [HttpPost, ActionName("Delete")]
 371:          [ValidateAntiForgeryToken]
 372:          public async Task<IActionResult> DeleteConfirmed(int id)
 373:          {
 374:              Instructor instructor = await _context.Instructors
 375:                  .Include(i => i.CourseAssignments)
 376:                  .SingleAsync(i => i.ID == id);
 377:   
 378:              var departments = await _context.Departments
 379:                  .Where(d => d.InstructorID == id)
 380:                  .ToListAsync();
 381:              departments.ForEach(d => d.InstructorID = null);
 382:   
 383:              _context.Instructors.Remove(instructor);
 384:   
 385:              await _context.SaveChangesAsync();
 386:              return RedirectToAction(nameof(Index));
 387:          }
 388:          #endregion
 389:      }
 390:  }

Modify the Instructor Index view

In Views/Instructors/Index.cshtml, replace the template code with the following code. The changes are highlighted.

[!code-htmlMain]

   1:  @model ContosoUniversity.Models.SchoolViewModels.InstructorIndexData
   2:   
   3:  @{
   4:      ViewData["Title"] = "Instructors";
   5:  }
   6:   
   7:  <h2>Instructors</h2>
   8:   
   9:  <p>
  10:      <a asp-action="Create">Create New</a>
  11:  </p>
  12:  <table class="table">
  13:      <thead>
  14:          <tr>
  15:              <th>Last Name</th>
  16:              <th>First Name</th>
  17:              <th>Hire Date</th>
  18:              <th>Office</th>
  19:              <th>Courses</th>
  20:              <th></th>
  21:          </tr>
  22:      </thead>
  23:      <tbody>
  24:          @foreach (var item in Model.Instructors)
  25:          {
  26:              string selectedRow = "";
  27:              if (item.ID == (int?)ViewData["InstructorID"])
  28:              {
  29:                  selectedRow = "success";
  30:              }
  31:              <tr class="@selectedRow">
  32:                  <td>
  33:                      @Html.DisplayFor(modelItem => item.LastName)
  34:                  </td>
  35:                  <td>
  36:                      @Html.DisplayFor(modelItem => item.FirstMidName)
  37:                  </td>
  38:                  <td>
  39:                      @Html.DisplayFor(modelItem => item.HireDate)
  40:                  </td>
  41:                  <td>
  42:                      @if (item.OfficeAssignment != null)
  43:                      {
  44:                          @item.OfficeAssignment.Location
  45:                      }
  46:                  </td>
  47:                  <td>
  48:                      @{
  49:                          foreach (var course in item.CourseAssignments)
  50:                          {
  51:                              @course.Course.CourseID @:  @course.Course.Title <br />
  52:                          }
  53:                      }
  54:                  </td>
  55:                  <td>
  56:                      <a asp-action="Index" asp-route-id="@item.ID">Select</a> |
  57:                      <a asp-action="Edit" asp-route-id="@item.ID">Edit</a> |
  58:                      <a asp-action="Details" asp-route-id="@item.ID">Details</a> |
  59:                      <a asp-action="Delete" asp-route-id="@item.ID">Delete</a>
  60:                  </td>
  61:              </tr>
  62:             }
  63:      </tbody>
  64:  </table>
  65:   
  66:  @if (Model.Courses != null)
  67:  {
  68:      <h3>Courses Taught by Selected Instructor</h3>
  69:      <table class="table">
  70:          <tr>
  71:              <th></th>
  72:              <th>Number</th>
  73:              <th>Title</th>
  74:              <th>Department</th>
  75:          </tr>
  76:   
  77:          @foreach (var item in Model.Courses)
  78:          {
  79:              string selectedRow = "";
  80:              if (item.CourseID == (int?)ViewData["CourseID"])
  81:              {
  82:                  selectedRow = "success";
  83:              }
  84:              <tr class="@selectedRow">
  85:                  <td>
  86:                      @Html.ActionLink("Select", "Index", new { courseID = item.CourseID })
  87:                  </td>
  88:                  <td>
  89:                      @item.CourseID
  90:                  </td>
  91:                  <td>
  92:                      @item.Title
  93:                  </td>
  94:                  <td>
  95:                      @item.Department.Name
  96:                  </td>
  97:              </tr>
  98:          }
  99:   
 100:      </table>
 101:  }
 102:   
 103:  @if (Model.Enrollments != null)
 104:  {
 105:      <h3>
 106:          Students Enrolled in Selected Course
 107:      </h3>
 108:      <table class="table">
 109:          <tr>
 110:              <th>Name</th>
 111:              <th>Grade</th>
 112:          </tr>
 113:          @foreach (var item in Model.Enrollments)
 114:          {
 115:              <tr>
 116:                  <td>
 117:                      @item.Student.FullName
 118:                  </td>
 119:                  <td>
 120:                      @Html.DisplayFor(modelItem => item.Grade)
 121:                  </td>
 122:              </tr>
 123:          }
 124:      </table>
 125:  }
 126:   

You’ve made the following changes to the existing code:

Run the app and select the Instructors tab. The page displays the Location property of related OfficeAssignment entities and an empty table cell when there’s no related OfficeAssignment entity.

Instructors Index page nothing selected
Instructors Index page nothing selected

In the Views/Instructors/Index.cshtml file, after the closing table element (at the end of the file), add the following code. This code displays a list of courses related to an instructor when an instructor is selected.

[!code-htmlMain]

   1:  @model ContosoUniversity.Models.SchoolViewModels.InstructorIndexData
   2:   
   3:  @{
   4:      ViewData["Title"] = "Instructors";
   5:  }
   6:   
   7:  <h2>Instructors</h2>
   8:   
   9:  <p>
  10:      <a asp-action="Create">Create New</a>
  11:  </p>
  12:  <table class="table">
  13:      <thead>
  14:          <tr>
  15:              <th>Last Name</th>
  16:              <th>First Name</th>
  17:              <th>Hire Date</th>
  18:              <th>Office</th>
  19:              <th>Courses</th>
  20:              <th></th>
  21:          </tr>
  22:      </thead>
  23:      <tbody>
  24:          @foreach (var item in Model.Instructors)
  25:          {
  26:              string selectedRow = "";
  27:              if (item.ID == (int?)ViewData["InstructorID"])
  28:              {
  29:                  selectedRow = "success";
  30:              }
  31:              <tr class="@selectedRow">
  32:                  <td>
  33:                      @Html.DisplayFor(modelItem => item.LastName)
  34:                  </td>
  35:                  <td>
  36:                      @Html.DisplayFor(modelItem => item.FirstMidName)
  37:                  </td>
  38:                  <td>
  39:                      @Html.DisplayFor(modelItem => item.HireDate)
  40:                  </td>
  41:                  <td>
  42:                      @if (item.OfficeAssignment != null)
  43:                      {
  44:                          @item.OfficeAssignment.Location
  45:                      }
  46:                  </td>
  47:                  <td>
  48:                      @{
  49:                          foreach (var course in item.CourseAssignments)
  50:                          {
  51:                              @course.Course.CourseID @:  @course.Course.Title <br />
  52:                          }
  53:                      }
  54:                  </td>
  55:                  <td>
  56:                      <a asp-action="Index" asp-route-id="@item.ID">Select</a> |
  57:                      <a asp-action="Edit" asp-route-id="@item.ID">Edit</a> |
  58:                      <a asp-action="Details" asp-route-id="@item.ID">Details</a> |
  59:                      <a asp-action="Delete" asp-route-id="@item.ID">Delete</a>
  60:                  </td>
  61:              </tr>
  62:             }
  63:      </tbody>
  64:  </table>
  65:   
  66:  @if (Model.Courses != null)
  67:  {
  68:      <h3>Courses Taught by Selected Instructor</h3>
  69:      <table class="table">
  70:          <tr>
  71:              <th></th>
  72:              <th>Number</th>
  73:              <th>Title</th>
  74:              <th>Department</th>
  75:          </tr>
  76:   
  77:          @foreach (var item in Model.Courses)
  78:          {
  79:              string selectedRow = "";
  80:              if (item.CourseID == (int?)ViewData["CourseID"])
  81:              {
  82:                  selectedRow = "success";
  83:              }
  84:              <tr class="@selectedRow">
  85:                  <td>
  86:                      @Html.ActionLink("Select", "Index", new { courseID = item.CourseID })
  87:                  </td>
  88:                  <td>
  89:                      @item.CourseID
  90:                  </td>
  91:                  <td>
  92:                      @item.Title
  93:                  </td>
  94:                  <td>
  95:                      @item.Department.Name
  96:                  </td>
  97:              </tr>
  98:          }
  99:   
 100:      </table>
 101:  }
 102:   
 103:  @if (Model.Enrollments != null)
 104:  {
 105:      <h3>
 106:          Students Enrolled in Selected Course
 107:      </h3>
 108:      <table class="table">
 109:          <tr>
 110:              <th>Name</th>
 111:              <th>Grade</th>
 112:          </tr>
 113:          @foreach (var item in Model.Enrollments)
 114:          {
 115:              <tr>
 116:                  <td>
 117:                      @item.Student.FullName
 118:                  </td>
 119:                  <td>
 120:                      @Html.DisplayFor(modelItem => item.Grade)
 121:                  </td>
 122:              </tr>
 123:          }
 124:      </table>
 125:  }
 126:   

This code reads the Courses property of the view model to display a list of courses. It also provides a Select hyperlink that sends the ID of the selected course to the Index action method.

Refresh the page and select an instructor. Now you see a grid that displays courses assigned to the selected instructor, and for each course you see the name of the assigned department.

Instructors Index page instructor selected
Instructors Index page instructor selected

After the code block you just added, add the following code. This displays a list of the students who are enrolled in a course when that course is selected.

[!code-htmlMain]

   1:  @model ContosoUniversity.Models.SchoolViewModels.InstructorIndexData
   2:   
   3:  @{
   4:      ViewData["Title"] = "Instructors";
   5:  }
   6:   
   7:  <h2>Instructors</h2>
   8:   
   9:  <p>
  10:      <a asp-action="Create">Create New</a>
  11:  </p>
  12:  <table class="table">
  13:      <thead>
  14:          <tr>
  15:              <th>Last Name</th>
  16:              <th>First Name</th>
  17:              <th>Hire Date</th>
  18:              <th>Office</th>
  19:              <th>Courses</th>
  20:              <th></th>
  21:          </tr>
  22:      </thead>
  23:      <tbody>
  24:          @foreach (var item in Model.Instructors)
  25:          {
  26:              string selectedRow = "";
  27:              if (item.ID == (int?)ViewData["InstructorID"])
  28:              {
  29:                  selectedRow = "success";
  30:              }
  31:              <tr class="@selectedRow">
  32:                  <td>
  33:                      @Html.DisplayFor(modelItem => item.LastName)
  34:                  </td>
  35:                  <td>
  36:                      @Html.DisplayFor(modelItem => item.FirstMidName)
  37:                  </td>
  38:                  <td>
  39:                      @Html.DisplayFor(modelItem => item.HireDate)
  40:                  </td>
  41:                  <td>
  42:                      @if (item.OfficeAssignment != null)
  43:                      {
  44:                          @item.OfficeAssignment.Location
  45:                      }
  46:                  </td>
  47:                  <td>
  48:                      @{
  49:                          foreach (var course in item.CourseAssignments)
  50:                          {
  51:                              @course.Course.CourseID @:  @course.Course.Title <br />
  52:                          }
  53:                      }
  54:                  </td>
  55:                  <td>
  56:                      <a asp-action="Index" asp-route-id="@item.ID">Select</a> |
  57:                      <a asp-action="Edit" asp-route-id="@item.ID">Edit</a> |
  58:                      <a asp-action="Details" asp-route-id="@item.ID">Details</a> |
  59:                      <a asp-action="Delete" asp-route-id="@item.ID">Delete</a>
  60:                  </td>
  61:              </tr>
  62:             }
  63:      </tbody>
  64:  </table>
  65:   
  66:  @if (Model.Courses != null)
  67:  {
  68:      <h3>Courses Taught by Selected Instructor</h3>
  69:      <table class="table">
  70:          <tr>
  71:              <th></th>
  72:              <th>Number</th>
  73:              <th>Title</th>
  74:              <th>Department</th>
  75:          </tr>
  76:   
  77:          @foreach (var item in Model.Courses)
  78:          {
  79:              string selectedRow = "";
  80:              if (item.CourseID == (int?)ViewData["CourseID"])
  81:              {
  82:                  selectedRow = "success";
  83:              }
  84:              <tr class="@selectedRow">
  85:                  <td>
  86:                      @Html.ActionLink("Select", "Index", new { courseID = item.CourseID })
  87:                  </td>
  88:                  <td>
  89:                      @item.CourseID
  90:                  </td>
  91:                  <td>
  92:                      @item.Title
  93:                  </td>
  94:                  <td>
  95:                      @item.Department.Name
  96:                  </td>
  97:              </tr>
  98:          }
  99:   
 100:      </table>
 101:  }
 102:   
 103:  @if (Model.Enrollments != null)
 104:  {
 105:      <h3>
 106:          Students Enrolled in Selected Course
 107:      </h3>
 108:      <table class="table">
 109:          <tr>
 110:              <th>Name</th>
 111:              <th>Grade</th>
 112:          </tr>
 113:          @foreach (var item in Model.Enrollments)
 114:          {
 115:              <tr>
 116:                  <td>
 117:                      @item.Student.FullName
 118:                  </td>
 119:                  <td>
 120:                      @Html.DisplayFor(modelItem => item.Grade)
 121:                  </td>
 122:              </tr>
 123:          }
 124:      </table>
 125:  }
 126:   

This code reads the Enrollments property of the view model in order to display a list of students enrolled in the course.

Refresh the page again and select an instructor. Then select a course to see the list of enrolled students and their grades.

Instructors Index page instructor and course selected
Instructors Index page instructor and course selected

Explicit loading

When you retrieved the list of instructors in InstructorsController.cs, you specified eager loading for the CourseAssignments navigation property.

Suppose you expected users to only rarely want to see enrollments in a selected instructor and course. In that case, you might want to load the enrollment data only if it’s requested. To see an example of how to do explicit loading, replace the Index method with the following code, which removes eager loading for Enrollments and loads that property explicitly. The code changes are highlighted.

[!code-csharpMain]

   1:  #define ExplicitLoading // or EagerLoading or ScaffoldedCode
   2:  #define EditCourses // or EditOfficeAssignment
   3:   
   4:  using System;
   5:  using System.Collections.Generic;
   6:  using System.Linq;
   7:  using System.Threading.Tasks;
   8:  using Microsoft.AspNetCore.Mvc;
   9:  using Microsoft.AspNetCore.Mvc.Rendering;
  10:  using Microsoft.EntityFrameworkCore;
  11:  using ContosoUniversity.Data;
  12:  using ContosoUniversity.Models;
  13:  #region snippet_Using
  14:  using ContosoUniversity.Models.SchoolViewModels;
  15:  #endregion
  16:   
  17:  namespace ContosoUniversity.Controllers
  18:  {
  19:      public class InstructorsController : Controller
  20:      {
  21:          private readonly SchoolContext _context;
  22:   
  23:          public InstructorsController(SchoolContext context)
  24:          {
  25:              _context = context;
  26:          }
  27:   
  28:          // GET: Instructors
  29:  #if ScaffoldedCode
  30:          #region snippet_ScaffoldedCode
  31:          public async Task<IActionResult> Index()
  32:          {
  33:              return View(await _context.Instructors.AsNoTracking().ToListAsync());
  34:          }
  35:          #endregion
  36:  #elif EagerLoading
  37:          #region snippet_EagerLoading
  38:          public async Task<IActionResult> Index(int? id, int? courseID)
  39:          {
  40:              var viewModel = new InstructorIndexData();
  41:              #region snippet_ThenInclude
  42:              viewModel.Instructors = await _context.Instructors
  43:                    .Include(i => i.OfficeAssignment)
  44:                    .Include(i => i.CourseAssignments)
  45:                      .ThenInclude(i => i.Course)
  46:                          .ThenInclude(i => i.Enrollments)
  47:                              .ThenInclude(i => i.Student)
  48:                    .Include(i => i.CourseAssignments)
  49:                      .ThenInclude(i => i.Course)
  50:                          .ThenInclude(i => i.Department)
  51:                    .AsNoTracking()
  52:                    .OrderBy(i => i.LastName)
  53:                    .ToListAsync();
  54:              #endregion
  55:              
  56:              if (id != null)
  57:              {
  58:                  ViewData["InstructorID"] = id.Value;
  59:                  Instructor instructor = viewModel.Instructors.Where(
  60:                      i => i.ID == id.Value).Single();
  61:                  viewModel.Courses = instructor.CourseAssignments.Select(s => s.Course);
  62:              }
  63:   
  64:              if (courseID != null)
  65:              {
  66:                  ViewData["CourseID"] = courseID.Value;
  67:                  viewModel.Enrollments = viewModel.Courses.Where(
  68:                      x => x.CourseID == courseID).Single().Enrollments;
  69:              }
  70:   
  71:              return View(viewModel);
  72:          }
  73:          #endregion
  74:  #elif ExplicitLoading
  75:          #region snippet_ExplicitLoading
  76:          public async Task<IActionResult> Index(int? id, int? courseID)
  77:          {
  78:              var viewModel = new InstructorIndexData();
  79:              viewModel.Instructors = await _context.Instructors
  80:                    .Include(i => i.OfficeAssignment)
  81:                    .Include(i => i.CourseAssignments)
  82:                      .ThenInclude(i => i.Course)
  83:                          .ThenInclude(i => i.Department)
  84:                    .OrderBy(i => i.LastName)
  85:                    .ToListAsync();
  86:   
  87:              if (id != null)
  88:              {
  89:                  ViewData["InstructorID"] = id.Value;
  90:                  Instructor instructor = viewModel.Instructors.Where(
  91:                      i => i.ID == id.Value).Single();
  92:                  viewModel.Courses = instructor.CourseAssignments.Select(s => s.Course);
  93:              }
  94:   
  95:              if (courseID != null)
  96:              {
  97:                  ViewData["CourseID"] = courseID.Value;
  98:                  var selectedCourse = viewModel.Courses.Where(x => x.CourseID == courseID).Single();
  99:                  await _context.Entry(selectedCourse).Collection(x => x.Enrollments).LoadAsync();
 100:                  foreach (Enrollment enrollment in selectedCourse.Enrollments)
 101:                  {
 102:                      await _context.Entry(enrollment).Reference(x => x.Student).LoadAsync();
 103:                  }
 104:                  viewModel.Enrollments = selectedCourse.Enrollments;
 105:              }
 106:   
 107:              return View(viewModel);
 108:          }
 109:          #endregion
 110:  #endif
 111:          // GET: Instructors/Details/5
 112:          public async Task<IActionResult> Details(int? id)
 113:          {
 114:              if (id == null)
 115:              {
 116:                  return NotFound();
 117:              }
 118:   
 119:              var instructor = await _context.Instructors
 120:                  .AsNoTracking()
 121:                  .SingleOrDefaultAsync(m => m.ID == id);
 122:              if (instructor == null)
 123:              {
 124:                  return NotFound();
 125:              }
 126:   
 127:              return View(instructor);
 128:          }
 129:   
 130:          // GET: Instructors/Create
 131:          #region snippet_Create
 132:          public IActionResult Create()
 133:          {
 134:              var instructor = new Instructor();
 135:              instructor.CourseAssignments = new List<CourseAssignment>();
 136:              PopulateAssignedCourseData(instructor);
 137:              return View();
 138:          }
 139:   
 140:          // POST: Instructors/Create
 141:          [HttpPost]
 142:          [ValidateAntiForgeryToken]
 143:          public async Task<IActionResult> Create([Bind("FirstMidName,HireDate,LastName,OfficeAssignment")] Instructor instructor, string[] selectedCourses)
 144:          {
 145:              if (selectedCourses != null)
 146:              {
 147:                  instructor.CourseAssignments = new List<CourseAssignment>();
 148:                  foreach (var course in selectedCourses)
 149:                  {
 150:                      var courseToAdd = new CourseAssignment { InstructorID = instructor.ID, CourseID = int.Parse(course) };
 151:                      instructor.CourseAssignments.Add(courseToAdd);
 152:                  }
 153:              }
 154:              if (ModelState.IsValid)
 155:              {
 156:                  _context.Add(instructor);
 157:                  await _context.SaveChangesAsync();
 158:                  return RedirectToAction(nameof(Index));
 159:              }
 160:              PopulateAssignedCourseData(instructor);
 161:              return View(instructor);
 162:          }
 163:          #endregion
 164:   
 165:          // GET: Instructors/Edit/5
 166:  #if EditOfficeAssignment
 167:          #region snippet_EditGetOA
 168:          public async Task<IActionResult> Edit(int? id)
 169:          {
 170:              if (id == null)
 171:              {
 172:                  return NotFound();
 173:              }
 174:   
 175:              var instructor = await _context.Instructors
 176:                  .Include(i => i.OfficeAssignment)
 177:                  .AsNoTracking()
 178:                  .SingleOrDefaultAsync(m => m.ID == id);
 179:              if (instructor == null)
 180:              {
 181:                  return NotFound();
 182:              }
 183:              return View(instructor);
 184:          }
 185:          #endregion
 186:  #elif EditCourses
 187:          #region snippet_EditGetCourses
 188:          public async Task<IActionResult> Edit(int? id)
 189:          {
 190:              if (id == null)
 191:              {
 192:                  return NotFound();
 193:              }
 194:   
 195:              var instructor = await _context.Instructors
 196:                  .Include(i => i.OfficeAssignment)
 197:                  .Include(i => i.CourseAssignments).ThenInclude(i => i.Course)
 198:                  .AsNoTracking()
 199:                  .SingleOrDefaultAsync(m => m.ID == id);
 200:              if (instructor == null)
 201:              {
 202:                  return NotFound();
 203:              }
 204:              PopulateAssignedCourseData(instructor);
 205:              return View(instructor);
 206:          }
 207:   
 208:          private void PopulateAssignedCourseData(Instructor instructor)
 209:          {
 210:              var allCourses = _context.Courses;
 211:              var instructorCourses = new HashSet<int>(instructor.CourseAssignments.Select(c => c.CourseID));
 212:              var viewModel = new List<AssignedCourseData>();
 213:              foreach (var course in allCourses)
 214:              {
 215:                  viewModel.Add(new AssignedCourseData
 216:                  {
 217:                      CourseID = course.CourseID,
 218:                      Title = course.Title,
 219:                      Assigned = instructorCourses.Contains(course.CourseID)
 220:                  });
 221:              }
 222:              ViewData["Courses"] = viewModel;
 223:          }
 224:          #endregion
 225:  #endif
 226:          // POST: Instructors/Edit/5
 227:          // To protect from overposting attacks, please enable the specific properties you want to bind to, for 
 228:          // more details see http://go.microsoft.com/fwlink/?LinkId=317598.
 229:  #if EditOfficeAssignment
 230:          #region snippet_EditPostOA
 231:          [HttpPost, ActionName("Edit")]
 232:          [ValidateAntiForgeryToken]
 233:          public async Task<IActionResult> EditPost(int? id)
 234:          {
 235:              if (id == null)
 236:              {
 237:                  return NotFound();
 238:              }
 239:   
 240:              var instructorToUpdate = await _context.Instructors
 241:                  .Include(i => i.OfficeAssignment)
 242:                  .SingleOrDefaultAsync(s => s.ID == id);
 243:   
 244:              if (await TryUpdateModelAsync<Instructor>(
 245:                  instructorToUpdate,
 246:                  "",
 247:                  i => i.FirstMidName, i => i.LastName, i => i.HireDate, i => i.OfficeAssignment))
 248:              {
 249:                  if (String.IsNullOrWhiteSpace(instructorToUpdate.OfficeAssignment?.Location))
 250:                  {
 251:                      instructorToUpdate.OfficeAssignment = null;
 252:                  }
 253:                  try
 254:                  {
 255:                      await _context.SaveChangesAsync();
 256:                  }
 257:                  catch (DbUpdateException /* ex */)
 258:                  {
 259:                      //Log the error (uncomment ex variable name and write a log.)
 260:                      ModelState.AddModelError("", "Unable to save changes. " +
 261:                          "Try again, and if the problem persists, " +
 262:                          "see your system administrator.");
 263:                  }
 264:                  return RedirectToAction(nameof(Index));
 265:              }
 266:              return View(instructorToUpdate);
 267:          }
 268:          #endregion
 269:   
 270:  #elif EditCourses
 271:          #region snippet_EditPostCourses
 272:          [HttpPost]
 273:          [ValidateAntiForgeryToken]
 274:          public async Task<IActionResult> Edit(int? id, string[] selectedCourses)
 275:          {
 276:              if (id == null)
 277:              {
 278:                  return NotFound();
 279:              }
 280:   
 281:              var instructorToUpdate = await _context.Instructors
 282:                  .Include(i => i.OfficeAssignment)
 283:                  .Include(i => i.CourseAssignments)
 284:                      .ThenInclude(i => i.Course)
 285:                  .SingleOrDefaultAsync(m => m.ID == id);
 286:   
 287:              if (await TryUpdateModelAsync<Instructor>(
 288:                  instructorToUpdate,
 289:                  "",
 290:                  i => i.FirstMidName, i => i.LastName, i => i.HireDate, i => i.OfficeAssignment))
 291:              {
 292:                  if (String.IsNullOrWhiteSpace(instructorToUpdate.OfficeAssignment?.Location))
 293:                  {
 294:                      instructorToUpdate.OfficeAssignment = null;
 295:                  }
 296:                  UpdateInstructorCourses(selectedCourses, instructorToUpdate);
 297:                  try
 298:                  {
 299:                      await _context.SaveChangesAsync();
 300:                  }
 301:                  catch (DbUpdateException /* ex */)
 302:                  {
 303:                      //Log the error (uncomment ex variable name and write a log.)
 304:                      ModelState.AddModelError("", "Unable to save changes. " +
 305:                          "Try again, and if the problem persists, " +
 306:                          "see your system administrator.");
 307:                  }
 308:                  return RedirectToAction(nameof(Index));
 309:              }
 310:              UpdateInstructorCourses(selectedCourses, instructorToUpdate);
 311:              PopulateAssignedCourseData(instructorToUpdate);
 312:              return View(instructorToUpdate);
 313:          }
 314:          #endregion
 315:   
 316:          #region snippet_UpdateCourses
 317:          private void UpdateInstructorCourses(string[] selectedCourses, Instructor instructorToUpdate)
 318:          {
 319:              if (selectedCourses == null)
 320:              {
 321:                  instructorToUpdate.CourseAssignments = new List<CourseAssignment>();
 322:                  return;
 323:              }
 324:   
 325:              var selectedCoursesHS = new HashSet<string>(selectedCourses);
 326:              var instructorCourses = new HashSet<int>
 327:                  (instructorToUpdate.CourseAssignments.Select(c => c.Course.CourseID));
 328:              foreach (var course in _context.Courses)
 329:              {
 330:                  if (selectedCoursesHS.Contains(course.CourseID.ToString()))
 331:                  {
 332:                      if (!instructorCourses.Contains(course.CourseID))
 333:                      {
 334:                          instructorToUpdate.CourseAssignments.Add(new CourseAssignment { InstructorID = instructorToUpdate.ID, CourseID = course.CourseID });
 335:                      }
 336:                  }
 337:                  else
 338:                  {
 339:   
 340:                      if (instructorCourses.Contains(course.CourseID))
 341:                      {
 342:                          CourseAssignment courseToRemove = instructorToUpdate.CourseAssignments.SingleOrDefault(i => i.CourseID == course.CourseID);
 343:                          _context.Remove(courseToRemove);
 344:                      }
 345:                  }
 346:              }
 347:          }
 348:          #endregion
 349:  #endif
 350:          // GET: Instructors/Delete/5
 351:          public async Task<IActionResult> Delete(int? id)
 352:          {
 353:              if (id == null)
 354:              {
 355:                  return NotFound();
 356:              }
 357:   
 358:              var instructor = await _context.Instructors
 359:                  .AsNoTracking()
 360:                  .SingleOrDefaultAsync(m => m.ID == id);
 361:              if (instructor == null)
 362:              {
 363:                  return NotFound();
 364:              }
 365:   
 366:              return View(instructor);
 367:          }
 368:   
 369:          #region snippet_DeleteConfirmed
 370:          [HttpPost, ActionName("Delete")]
 371:          [ValidateAntiForgeryToken]
 372:          public async Task<IActionResult> DeleteConfirmed(int id)
 373:          {
 374:              Instructor instructor = await _context.Instructors
 375:                  .Include(i => i.CourseAssignments)
 376:                  .SingleAsync(i => i.ID == id);
 377:   
 378:              var departments = await _context.Departments
 379:                  .Where(d => d.InstructorID == id)
 380:                  .ToListAsync();
 381:              departments.ForEach(d => d.InstructorID = null);
 382:   
 383:              _context.Instructors.Remove(instructor);
 384:   
 385:              await _context.SaveChangesAsync();
 386:              return RedirectToAction(nameof(Index));
 387:          }
 388:          #endregion
 389:      }
 390:  }

The new code drops the ThenInclude method calls for enrollment data from the code that retrieves instructor entities. If an instructor and course are selected, the highlighted code retrieves Enrollment entities for the selected course, and Student entities for each Enrollment.

Run the app, go to the Instructors Index page now and you’ll see no difference in what’s displayed on the page, although you’ve changed how the data is retrieved.

Summary

You’ve now used eager loading with one query and with multiple queries to read related data into navigation properties. In the next tutorial you’ll learn how to update related data.

Previous Next





Comments ( )
<00>  <01>  <02>  <03>  <04>  <05>  <06>  <07>  <08>  <09>  <10>  <11>  <12>  <13>  <14>  <15>  <16>  <17>  <18>  <19>  <20>  <21>  <22>  <23
Link to this page: //www.vb-net.com/AspNet-DocAndSamples-2017/aspnetcore/data/ef-mvc/read-related-data.htm
<SITEMAP>  <MVC>  <ASP>  <NET>  <DATA>  <KIOSK>  <FLEX>  <SQL>  <NOTES>  <LINUX>  <MONO>  <FREEWARE>  <DOCS>  <ENG>  <CHAT ME>  <ABOUT ME>  < THANKS ME>