Anteckning
Åtkomst till den här sidan kräver auktorisering. Du kan prova att logga in eller ändra kataloger.
Åtkomst till den här sidan kräver auktorisering. Du kan prova att ändra kataloger.
Av Tom Dykstra, Jon P Smith och Rick Anderson
Contoso University-webbappen visar hur du skapar webbappar för Razor Pages med hjälp av EF Core och Visual Studio. Information om självstudieserien finns i den första självstudien.
Om du stöter på problem som du inte kan lösa, ladda ner den färdiga appen och jämför den koden med den du skapade när du följde handledningen.
Den här handledningen visar hur du läser och visar relaterade data. Relaterad data är data som EF Core laddas in i navigeringsegenskaper.
Följande bilder visar de slutförda sidorna för den här handledningen.
Ivrig, explicit och lat inläsning
Det finns flera sätt att EF Core läsa in relaterade data i navigeringsegenskaperna för en entitet:
Ivrig inläsning. Ivrig inläsning är när en fråga för en typ av entitet också läser in relaterade entiteter. När en entitet läss hämtas dess relaterade data. Detta resulterar vanligtvis i en enda kopplingsfråga som hämtar alla data som behövs. EF Core kommer att utfärda flera frågekommandon för vissa typer av ivrig inläsning. Att utfärda flera frågor kan vara effektivare än en stor enskild fråga. Ivrig inläsning anges med Include metoderna och ThenInclude .
Eager loading skickar flera sökfrågor när en samlingsnavigering ingår.
- En fråga för huvudfrågan
- En fråga för varje "kant" i en samling i lastträdet.
Separata frågor med
Load: Data kan hämtas i separata frågor och EF Core "korrigerar" navigeringsegenskaperna. "Korrigeringar" innebär att EF Core navigeringsegenskaperna fylls i automatiskt. Separata frågor medLoadär mer som explicit inläsning än ivrig inläsning.
Observera:EF Core korrigerar automatiskt navigeringsegenskaperna för andra entiteter som tidigare lästes in i kontextinstansen. Även om data för en navigeringsegenskap inte uttryckligen inkluderas kan egenskapen fortfarande fyllas i om vissa eller alla relaterade entiteter har lästs in tidigare.
Explicit inläsning. När entiteten först läss hämtas inte relaterade data. Koden måste skrivas för att hämta relaterade data när den behövs. Explicit inläsning med separata frågor resulterar i flera frågor som skickas till databasen. Med explicit inläsning anger koden de navigeringsegenskaper som ska läsas in. Använd
Load-metoden för att göra en explicit inläsning. Till exempel:
Lat inläsning. När entiteten först läss hämtas inte relaterade data. Första gången en navigeringsegenskap används hämtas de data som krävs för navigeringsegenskapen automatiskt. En fråga skickas till databasen varje gång en navigeringsegenskap används för första gången. Lat inläsning kan skada prestanda, till exempel när utvecklare använder N+1-frågor. N+1-frågor laddar en överordnad och itererar genom underordnade.
Skapa kurssidor
Entiteten Course innehåller en navigeringsegenskap som innehåller den relaterade Department entiteten.
Så här visar du namnet på den tilldelade avdelningen för en kurs:
- Läs in den relaterade
Departmententiteten i navigeringsegenskapenCourse.Department. - Hämta namnet från den
Department-entitetensName-egenskap.
Kurssidor för byggnadsställningar
Följ anvisningarna på studentsidor med följande undantag:
- Skapa en mapp för sidor/kurser .
- Använd
Courseför modellklassen. - Använd den befintliga kontextklassen i stället för att skapa en ny.
Öppna
Pages/Courses/Index.cshtml.csoch granskaOnGetAsyncmetoden. Scaffoldingmotorn angav föruppladdning för navigeringsegenskapenDepartment. MetodenIncludeanger ivrig inläsning.Kör appen och välj länken Kurser . I kolumnen Avdelning visas
DepartmentID, vilket inte är användbart.
Visa avdelningsnamnet
Uppdatera sidor/kurser/Index.cshtml.cs med följande kod:
using ContosoUniversity.Models;
using Microsoft.AspNetCore.Mvc.RazorPages;
using Microsoft.EntityFrameworkCore;
using System.Collections.Generic;
using System.Threading.Tasks;
namespace ContosoUniversity.Pages.Courses
{
public class IndexModel : PageModel
{
private readonly ContosoUniversity.Data.SchoolContext _context;
public IndexModel(ContosoUniversity.Data.SchoolContext context)
{
_context = context;
}
public IList<Course> Courses { get; set; }
public async Task OnGetAsync()
{
Courses = await _context.Courses
.Include(c => c.Department)
.AsNoTracking()
.ToListAsync();
}
}
}
Föregående kod ändrar egenskapen Course till Courses och lägger till AsNoTracking.
Frågor utan spårning är användbara när resultaten används i ett skrivskyddat scenario. De är vanligtvis snabbare att köra eftersom det inte finns något behov av att konfigurera ändringsspårningsinformationen. Om entiteterna som hämtas från databasen inte behöver uppdateras kommer en fråga utan spårning sannolikt att fungera bättre än en spårningsfråga.
I vissa fall är en spårningsfråga effektivare än en fråga utan spårning. Mer information finns i Spårning kontra Icke-Spårande förfrågningar.
I föregående kod AsNoTracking anropas eftersom entiteterna inte uppdateras i den aktuella kontexten.
Uppdatera Pages/Courses/Index.cshtml med följande kod.
@page
@model ContosoUniversity.Pages.Courses.IndexModel
@{
ViewData["Title"] = "Courses";
}
<h1>Courses</h1>
<p>
<a asp-page="Create">Create New</a>
</p>
<table class="table">
<thead>
<tr>
<th>
@Html.DisplayNameFor(model => model.Courses[0].CourseID)
</th>
<th>
@Html.DisplayNameFor(model => model.Courses[0].Title)
</th>
<th>
@Html.DisplayNameFor(model => model.Courses[0].Credits)
</th>
<th>
@Html.DisplayNameFor(model => model.Courses[0].Department)
</th>
<th></th>
</tr>
</thead>
<tbody>
@foreach (var item in Model.Courses)
{
<tr>
<td>
@Html.DisplayFor(modelItem => item.CourseID)
</td>
<td>
@Html.DisplayFor(modelItem => item.Title)
</td>
<td>
@Html.DisplayFor(modelItem => item.Credits)
</td>
<td>
@Html.DisplayFor(modelItem => item.Department.Name)
</td>
<td>
<a asp-page="./Edit" asp-route-id="@item.CourseID">Edit</a> |
<a asp-page="./Details" asp-route-id="@item.CourseID">Details</a> |
<a asp-page="./Delete" asp-route-id="@item.CourseID">Delete</a>
</td>
</tr>
}
</tbody>
</table>
Följande ändringar har gjorts i den genererade koden:
Ändrade egenskapsnamnet
CoursetillCourses.Lade till en talkolumn som visar egenskapsvärdet
CourseID. Som standard är primära nycklar inte genererade eftersom de normalt är meningslösa för slutanvändarna. I det här fallet är dock den primära nyckeln meningsfull.Ändrade kolumnen Avdelning för att visa avdelningsnamnet. Koden visar
Name-egenskapen förDepartment-entiteten som är inläst iDepartment-navigeringsegenskapen.@Html.DisplayFor(modelItem => item.Department.Name)
Kör appen och välj fliken Kurser för att se listan med avdelningsnamn.
Läser in relaterade data med Select
Metoden OnGetAsync läser in relaterade data med Include -metoden. Metoden Select är ett alternativ som endast läser in de relaterade data som behövs. För enskilda objekt, som Department.Name, så använder den en SQL INNER JOIN. För samlingar använder den en annan databasåtkomst, men det gör även operatorn för Include samlingar.
Följande kod läser in relaterade data med Select metoden:
public IList<CourseViewModel> CourseVM { get; set; }
public async Task OnGetAsync()
{
CourseVM = await _context.Courses
.Select(p => new CourseViewModel
{
CourseID = p.CourseID,
Title = p.Title,
Credits = p.Credits,
DepartmentName = p.Department.Name
}).ToListAsync();
}
Föregående kod returnerar inga entitetstyper, därför görs ingen spårning. Mer information om EF-spårning finns i Spårning jämfört med frågor utan spårning.
Den CourseViewModel:
public class CourseViewModel
{
public int CourseID { get; set; }
public string Title { get; set; }
public int Credits { get; set; }
public string DepartmentName { get; set; }
}
Se IndexSelectModel för de fullständiga Razor sidorna.
Skapa lärarsidor
Det här avsnittet skapar instruktörssidor och lägger till relaterade kurser och registreringar på sidan Instruktörsindex.
Den här sidan läser och visar relaterade data på följande sätt:
- Listan över lärare visar relaterade data från entiteten
OfficeAssignment(Office i föregående bild). EntiteternaInstructorochOfficeAssignmentfinns i en en-till-noll-eller-en-relation. Eager loading används för entiteternaOfficeAssignment. Ivrig inläsning är vanligtvis effektivare när relaterade data behöver visas. I det här fallet visas kontorstilldelningar för instruktörerna. - När användaren väljer en instruktör visas relaterade
Courseentiteter. EntiteternaInstructorochCoursefinns i en många-till-många-relation. Eager loading används förCourseentiteter och deras relateradeDepartmententiteter. I det här fallet kan separata frågor vara mer effektiva eftersom endast kurser för den valda instruktören behövs. Det här exemplet visar hur du använder ivrig inläsning för navigeringsegenskaper i entiteter som finns i navigeringsegenskaper. - När användaren väljer en kurs visas relaterade data från entiteten
Enrollments. I föregående bild visas elevnamn och betyg. EntiteternaCourseochEnrollmentfinns i en en-till-många-relation.
Skapa en vymodell
Sidan lärare visar data från tre olika tabeller. En vymodell krävs som innehåller tre egenskaper som representerar de tre tabellerna.
Skapa Models/SchoolViewModels/InstructorIndexData.cs med följande kod:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace ContosoUniversity.Models.SchoolViewModels
{
public class InstructorIndexData
{
public IEnumerable<Instructor> Instructors { get; set; }
public IEnumerable<Course> Courses { get; set; }
public IEnumerable<Enrollment> Enrollments { get; set; }
}
}
Strukturera instruktörssidor
Följ anvisningarna i Autogenerera studentsidorna med följande undantag:
- Skapa en mapp för sidor/lärare .
- Använd
Instructorför modellklassen. - Använd den befintliga kontextklassen i stället för att skapa en ny.
Kör appen och gå till sidan Instruktörer.
Uppdatera Pages/Instructors/Index.cshtml.cs med följande kod:
using ContosoUniversity.Models;
using ContosoUniversity.Models.SchoolViewModels; // Add VM
using Microsoft.AspNetCore.Mvc.RazorPages;
using Microsoft.EntityFrameworkCore;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace ContosoUniversity.Pages.Instructors
{
public class IndexModel : PageModel
{
private readonly ContosoUniversity.Data.SchoolContext _context;
public IndexModel(ContosoUniversity.Data.SchoolContext context)
{
_context = context;
}
public InstructorIndexData InstructorData { get; set; }
public int InstructorID { get; set; }
public int CourseID { get; set; }
public async Task OnGetAsync(int? id, int? courseID)
{
InstructorData = new InstructorIndexData();
InstructorData.Instructors = await _context.Instructors
.Include(i => i.OfficeAssignment)
.Include(i => i.Courses)
.ThenInclude(c => c.Department)
.OrderBy(i => i.LastName)
.ToListAsync();
if (id != null)
{
InstructorID = id.Value;
Instructor instructor = InstructorData.Instructors
.Where(i => i.ID == id.Value).Single();
InstructorData.Courses = instructor.Courses;
}
if (courseID != null)
{
CourseID = courseID.Value;
IEnumerable<Enrollment> Enrollments = await _context.Enrollments
.Where(x => x.CourseID == CourseID)
.Include(i=>i.Student)
.ToListAsync();
InstructorData.Enrollments = Enrollments;
}
}
}
}
Metoden OnGetAsync accepterar valfria routningsdata för ID:t för den valda instruktören.
Granska frågan i Pages/Instructors/Index.cshtml.cs filen:
InstructorData = new InstructorIndexData();
InstructorData.Instructors = await _context.Instructors
.Include(i => i.OfficeAssignment)
.Include(i => i.Courses)
.ThenInclude(c => c.Department)
.OrderBy(i => i.LastName)
.ToListAsync();
Koden anger eager loading för följande navigeringsegenskaper:
Instructor.OfficeAssignmentInstructor.CoursesCourse.Department
Följande kod körs när en instruktör har valts, id != nulldvs. .
if (id != null)
{
InstructorID = id.Value;
Instructor instructor = InstructorData.Instructors
.Where(i => i.ID == id.Value).Single();
InstructorData.Courses = instructor.Courses;
}
Den valda instruktören hämtas från listan över instruktörer i vymodellen. Vymodellens Courses egenskap läses in med entiteterna Course från den valda instruktörens navigeringsegenskap Courses .
Metoden Where returnerar en samling. I det här fallet väljer filtret en enskild entitet, så Single metoden anropas för att konvertera samlingen till en enda Instructor entitet. Entiteten Instructor ger åtkomst till navigeringsegenskapen Course .
Metoden Single används i en samling när samlingen bara har ett objekt. Metoden Single genererar ett undantag om samlingen är tom eller om det finns fler än ett objekt. Ett alternativ är SingleOrDefault, som returnerar ett standardvärde om samlingen är tom. För den här frågan återges null som standard.
Följande kod fyller i vymodellens egenskap när en kurs väljs Enrollments :
if (courseID != null)
{
CourseID = courseID.Value;
IEnumerable<Enrollment> Enrollments = await _context.Enrollments
.Where(x => x.CourseID == CourseID)
.Include(i=>i.Student)
.ToListAsync();
InstructorData.Enrollments = Enrollments;
}
Uppdatera sidan instruktörsindex
Uppdatera Pages/Instructors/Index.cshtml med följande kod.
@page "{id:int?}"
@model ContosoUniversity.Pages.Instructors.IndexModel
@{
ViewData["Title"] = "Instructors";
}
<h2>Instructors</h2>
<p>
<a asp-page="Create">Create New</a>
</p>
<table class="table">
<thead>
<tr>
<th>Last Name</th>
<th>First Name</th>
<th>Hire Date</th>
<th>Office</th>
<th>Courses</th>
<th></th>
</tr>
</thead>
<tbody>
@foreach (var item in Model.InstructorData.Instructors)
{
string selectedRow = "";
if (item.ID == Model.InstructorID)
{
selectedRow = "table-success";
}
<tr class="@selectedRow">
<td>
@Html.DisplayFor(modelItem => item.LastName)
</td>
<td>
@Html.DisplayFor(modelItem => item.FirstMidName)
</td>
<td>
@Html.DisplayFor(modelItem => item.HireDate)
</td>
<td>
@if (item.OfficeAssignment != null)
{
@item.OfficeAssignment.Location
}
</td>
<td>
@{
foreach (var course in item.Courses)
{
@course.CourseID @: @course.Title <br />
}
}
</td>
<td>
<a asp-page="./Index" asp-route-id="@item.ID">Select</a> |
<a asp-page="./Edit" asp-route-id="@item.ID">Edit</a> |
<a asp-page="./Details" asp-route-id="@item.ID">Details</a> |
<a asp-page="./Delete" asp-route-id="@item.ID">Delete</a>
</td>
</tr>
}
</tbody>
</table>
@if (Model.InstructorData.Courses != null)
{
<h3>Courses Taught by Selected Instructor</h3>
<table class="table">
<tr>
<th></th>
<th>Number</th>
<th>Title</th>
<th>Department</th>
</tr>
@foreach (var item in Model.InstructorData.Courses)
{
string selectedRow = "";
if (item.CourseID == Model.CourseID)
{
selectedRow = "table-success";
}
<tr class="@selectedRow">
<td>
<a asp-page="./Index" asp-route-courseID="@item.CourseID">Select</a>
</td>
<td>
@item.CourseID
</td>
<td>
@item.Title
</td>
<td>
@item.Department.Name
</td>
</tr>
}
</table>
}
@if (Model.InstructorData.Enrollments != null)
{
<h3>
Students Enrolled in Selected Course
</h3>
<table class="table">
<tr>
<th>Name</th>
<th>Grade</th>
</tr>
@foreach (var item in Model.InstructorData.Enrollments)
{
<tr>
<td>
@item.Student.FullName
</td>
<td>
@Html.DisplayFor(modelItem => item.Grade)
</td>
</tr>
}
</table>
}
Föregående kod gör följande ändringar:
pageUppdaterar direktivet till@page "{id:int?}"."{id:int?}"är en vägmall. Routningsmallen ändrar heltalsfrågesträngar i URL:en till routningsdata. Om du till exempel klickar på länken Välj för en lärare med endast@pagedirektivet skapas en URL som liknar följande:https://localhost:5001/Instructors?id=2När siddirektivet är
@page "{id:int?}"är URL:en:https://localhost:5001/Instructors/2Lägger till en Office-kolumn som endast visar
item.OfficeAssignment.Locationomitem.OfficeAssignmentden inte är null. Eftersom det här är en en-till-noll-eller-en-relation kanske det inte finns någon relaterad OfficeAssignment-entitet.@if (item.OfficeAssignment != null) { @item.OfficeAssignment.Location }Lägger till en kurskolumn som visar kurser som undervisas av varje lärare. Mer information om den här Razor-syntaxen finns i Explicit radövergång.
Lägger till kod som dynamiskt lägger till
class="table-success"i elementet för den valda läraren och kursentr. Detta anger en bakgrundsfärg för den valda raden med hjälp av en Bootstrap-klass.string selectedRow = ""; if (item.CourseID == Model.CourseID) { selectedRow = "table-success"; } <tr class="@selectedRow">Lägger till en ny hyperlänk med etiketten Välj. Den här länken skickar den valda instruktörens
IndexID till metoden och anger en bakgrundsfärg.<a asp-action="Index" asp-route-id="@item.ID">Select</a> |Lägger till en tabell med kurser för den valda instruktören.
Lägger till en tabell med studentregistreringar för den valda kursen.
Kör appen och välj fliken Instruktörer . Sidan visar Location (office) från den relaterade OfficeAssignment entiteten. Om OfficeAssignment är null visas en tom tabellcell.
Klicka på länken Välj för en lärare. Radformatet ändras och kurser som tilldelats den instruktören visas.
Välj en kurs för att se listan över registrerade studenter och deras betyg.
Nästa steg
Nästa handledning visar hur du uppdaterar relaterade data.
Den här handledningen visar hur du läser och visar relaterade data. Relaterad data är data som EF Core laddas in i navigeringsegenskaper.
Följande bilder visar de slutförda sidorna för den här handledningen.
Ivrig, explicit och lat inläsning
Det finns flera sätt att EF Core läsa in relaterade data i navigeringsegenskaperna för en entitet:
Ivrig inläsning. Ivrig inläsning är när en fråga för en typ av entitet också läser in relaterade entiteter. När en entitet läss hämtas dess relaterade data. Detta resulterar vanligtvis i en enda kopplingsfråga som hämtar alla data som behövs. EF Core kommer att utfärda flera frågekommandon för vissa typer av ivrig inläsning. Att utfärda flera frågor kan vara effektivare än en jättelik enskild fråga. Ivrig inläsning anges med
Includemetoderna ochThenInclude.
Eager loading skickar flera sökfrågor när en samlingsnavigering ingår.
- En fråga för huvudfrågan
- En fråga för varje "kant" i en samling i lastträdet.
Separata frågor med
Load: Data kan hämtas i separata frågor och EF Core "korrigerar" navigeringsegenskaperna. "Korrigeringar" innebär att EF Core navigeringsegenskaperna fylls i automatiskt. Separata frågor medLoadär mer som explicit inläsning än ivrig inläsning.
Observera:EF Core korrigerar automatiskt navigeringsegenskaperna för andra entiteter som tidigare lästes in i kontextinstansen. Även om data för en navigeringsegenskap inte uttryckligen inkluderas kan egenskapen fortfarande fyllas i om vissa eller alla relaterade entiteter har lästs in tidigare.
Explicit inläsning. När entiteten först läss hämtas inte relaterade data. Koden måste skrivas för att hämta relaterade data när den behövs. Explicit inläsning med separata frågor resulterar i flera frågor som skickas till databasen. Med explicit inläsning anger koden de navigeringsegenskaper som ska läsas in. Använd
Load-metoden för att göra en explicit inläsning. Till exempel:
Lat inläsning. När entiteten först läss hämtas inte relaterade data. Första gången en navigeringsegenskap används hämtas de data som krävs för navigeringsegenskapen automatiskt. En fråga skickas till databasen varje gång en navigeringsegenskap används för första gången. Lat inläsning kan skada prestanda, till exempel när utvecklare använder N+1-mönster, läser in en överordnad och itererar genom underordnade.
Skapa kurssidor
Entiteten Course innehåller en navigeringsegenskap som innehåller den relaterade Department entiteten.
Så här visar du namnet på den tilldelade avdelningen för en kurs:
- Läs in den relaterade
Departmententiteten i navigeringsegenskapenCourse.Department. - Hämta namnet från den
Department-entitetensName-egenskap.
Kurssidor för byggnadsställningar
Följ anvisningarna på studentsidor med följande undantag:
- Skapa en mapp för sidor/kurser .
- Använd
Courseför modellklassen. - Använd den befintliga kontextklassen i stället för att skapa en ny.
Öppna
Pages/Courses/Index.cshtml.csoch granskaOnGetAsyncmetoden. Scaffoldingmotorn angav föruppladdning för navigeringsegenskapenDepartment. MetodenIncludeanger ivrig inläsning.Kör appen och välj länken Kurser . I kolumnen Avdelning visas
DepartmentID, vilket inte är användbart.
Visa avdelningsnamnet
Uppdatera sidor/kurser/Index.cshtml.cs med följande kod:
using ContosoUniversity.Models;
using Microsoft.AspNetCore.Mvc.RazorPages;
using Microsoft.EntityFrameworkCore;
using System.Collections.Generic;
using System.Threading.Tasks;
namespace ContosoUniversity.Pages.Courses
{
public class IndexModel : PageModel
{
private readonly ContosoUniversity.Data.SchoolContext _context;
public IndexModel(ContosoUniversity.Data.SchoolContext context)
{
_context = context;
}
public IList<Course> Courses { get; set; }
public async Task OnGetAsync()
{
Courses = await _context.Courses
.Include(c => c.Department)
.AsNoTracking()
.ToListAsync();
}
}
}
Föregående kod ändrar egenskapen Course till Courses och lägger till AsNoTracking.
AsNoTracking förbättrar prestanda eftersom de entiteter som returneras inte spåras. Entiteterna behöver inte spåras eftersom de inte uppdateras i den aktuella kontexten.
Uppdatera Pages/Courses/Index.cshtml med följande kod.
@page
@model ContosoUniversity.Pages.Courses.IndexModel
@{
ViewData["Title"] = "Courses";
}
<h1>Courses</h1>
<p>
<a asp-page="Create">Create New</a>
</p>
<table class="table">
<thead>
<tr>
<th>
@Html.DisplayNameFor(model => model.Courses[0].CourseID)
</th>
<th>
@Html.DisplayNameFor(model => model.Courses[0].Title)
</th>
<th>
@Html.DisplayNameFor(model => model.Courses[0].Credits)
</th>
<th>
@Html.DisplayNameFor(model => model.Courses[0].Department)
</th>
<th></th>
</tr>
</thead>
<tbody>
@foreach (var item in Model.Courses)
{
<tr>
<td>
@Html.DisplayFor(modelItem => item.CourseID)
</td>
<td>
@Html.DisplayFor(modelItem => item.Title)
</td>
<td>
@Html.DisplayFor(modelItem => item.Credits)
</td>
<td>
@Html.DisplayFor(modelItem => item.Department.Name)
</td>
<td>
<a asp-page="./Edit" asp-route-id="@item.CourseID">Edit</a> |
<a asp-page="./Details" asp-route-id="@item.CourseID">Details</a> |
<a asp-page="./Delete" asp-route-id="@item.CourseID">Delete</a>
</td>
</tr>
}
</tbody>
</table>
Följande ändringar har gjorts i den genererade koden:
Ändrade egenskapsnamnet
CoursetillCourses.Lade till en talkolumn som visar egenskapsvärdet
CourseID. Som standard är primära nycklar inte genererade eftersom de normalt är meningslösa för slutanvändarna. I det här fallet är dock den primära nyckeln meningsfull.Ändrade kolumnen Avdelning för att visa avdelningsnamnet. Koden visar
Name-egenskapen förDepartment-entiteten som är inläst iDepartment-navigeringsegenskapen.@Html.DisplayFor(modelItem => item.Department.Name)
Kör appen och välj fliken Kurser för att se listan med avdelningsnamn.
Läser in relaterade data med Select
Metoden OnGetAsync läser in relaterade data med Include -metoden. Metoden Select är ett alternativ som endast läser in de relaterade data som behövs. För enskilda objekt, som det Department.Name, använder det en SQL INNER JOIN. För samlingar använder den en annan databasåtkomst, men det gör även operatorn för Include samlingar.
Följande kod läser in relaterade data med Select metoden:
public IList<CourseViewModel> CourseVM { get; set; }
public async Task OnGetAsync()
{
CourseVM = await _context.Courses
.Select(p => new CourseViewModel
{
CourseID = p.CourseID,
Title = p.Title,
Credits = p.Credits,
DepartmentName = p.Department.Name
}).ToListAsync();
}
Föregående kod returnerar inga entitetstyper, därför görs ingen spårning. Mer information om EF-spårning finns i Spårning jämfört med frågor utan spårning.
Den CourseViewModel:
public class CourseViewModel
{
public int CourseID { get; set; }
public string Title { get; set; }
public int Credits { get; set; }
public string DepartmentName { get; set; }
}
Se IndexSelect.cshtml och IndexSelect.cshtml.cs för ett fullständigt exempel.
Skapa lärarsidor
Det här avsnittet skapar instruktörssidor och lägger till relaterade kurser och registreringar på sidan Instruktörsindex.
Den här sidan läser och visar relaterade data på följande sätt:
- Listan över lärare visar relaterade data från entiteten
OfficeAssignment(Office i föregående bild). EntiteternaInstructorochOfficeAssignmentfinns i en en-till-noll-eller-en-relation. Eager loading används för entiteternaOfficeAssignment. Ivrig inläsning är vanligtvis effektivare när relaterade data behöver visas. I det här fallet visas kontorstilldelningar för instruktörerna. - När användaren väljer en instruktör visas relaterade
Courseentiteter. EntiteternaInstructorochCoursefinns i en många-till-många-relation. Eager loading används förCourseentiteter och deras relateradeDepartmententiteter. I det här fallet kan separata frågor vara mer effektiva eftersom endast kurser för den valda instruktören behövs. Det här exemplet visar hur du använder ivrig inläsning för navigeringsegenskaper i entiteter som finns i navigeringsegenskaper. - När användaren väljer en kurs visas relaterade data från entiteten
Enrollments. I föregående bild visas elevnamn och betyg. EntiteternaCourseochEnrollmentfinns i en en-till-många-relation.
Skapa en vymodell
Sidan lärare visar data från tre olika tabeller. En vymodell krävs som innehåller tre egenskaper som representerar de tre tabellerna.
Skapa SchoolViewModels/InstructorIndexData.cs med följande kod:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace ContosoUniversity.Models.SchoolViewModels
{
public class InstructorIndexData
{
public IEnumerable<Instructor> Instructors { get; set; }
public IEnumerable<Course> Courses { get; set; }
public IEnumerable<Enrollment> Enrollments { get; set; }
}
}
Strukturera instruktörssidor
Följ anvisningarna i Autogenerera studentsidorna med följande undantag:
- Skapa en mapp för sidor/lärare .
- Använd
Instructorför modellklassen. - Använd den befintliga kontextklassen i stället för att skapa en ny.
Om du vill se hur sidan med byggnadsställningar ser ut innan du uppdaterar den kör du appen och går till sidan Instruktörer.
Uppdatera Pages/Instructors/Index.cshtml.cs med följande kod:
using ContosoUniversity.Models;
using ContosoUniversity.Models.SchoolViewModels; // Add VM
using Microsoft.AspNetCore.Mvc.RazorPages;
using Microsoft.EntityFrameworkCore;
using System.Linq;
using System.Threading.Tasks;
namespace ContosoUniversity.Pages.Instructors
{
public class IndexModel : PageModel
{
private readonly ContosoUniversity.Data.SchoolContext _context;
public IndexModel(ContosoUniversity.Data.SchoolContext context)
{
_context = context;
}
public InstructorIndexData InstructorData { get; set; }
public int InstructorID { get; set; }
public int CourseID { get; set; }
public async Task OnGetAsync(int? id, int? courseID)
{
InstructorData = new InstructorIndexData();
InstructorData.Instructors = await _context.Instructors
.Include(i => i.OfficeAssignment)
.Include(i => i.CourseAssignments)
.ThenInclude(i => i.Course)
.ThenInclude(i => i.Department)
.Include(i => i.CourseAssignments)
.ThenInclude(i => i.Course)
.ThenInclude(i => i.Enrollments)
.ThenInclude(i => i.Student)
.AsNoTracking()
.OrderBy(i => i.LastName)
.ToListAsync();
if (id != null)
{
InstructorID = id.Value;
Instructor instructor = InstructorData.Instructors
.Where(i => i.ID == id.Value).Single();
InstructorData.Courses = instructor.CourseAssignments.Select(s => s.Course);
}
if (courseID != null)
{
CourseID = courseID.Value;
var selectedCourse = InstructorData.Courses
.Where(x => x.CourseID == courseID).Single();
InstructorData.Enrollments = selectedCourse.Enrollments;
}
}
}
}
Metoden OnGetAsync accepterar valfria routningsdata för ID:t för den valda instruktören.
Granska frågan i Pages/Instructors/Index.cshtml.cs filen:
InstructorData.Instructors = await _context.Instructors
.Include(i => i.OfficeAssignment)
.Include(i => i.CourseAssignments)
.ThenInclude(i => i.Course)
.ThenInclude(i => i.Department)
.Include(i => i.CourseAssignments)
.ThenInclude(i => i.Course)
.ThenInclude(i => i.Enrollments)
.ThenInclude(i => i.Student)
.AsNoTracking()
.OrderBy(i => i.LastName)
.ToListAsync();
Koden anger eager loading för följande navigeringsegenskaper:
Instructor.OfficeAssignmentInstructor.CourseAssignmentsCourseAssignments.CourseCourse.DepartmentCourse.EnrollmentsEnrollment.Student
Observera upprepningen av Include och ThenInclude metoderna för CourseAssignments och Course. Den här upprepningen är nödvändig för att ange laddning i förväg för de två navigeringsegenskaperna för entiteten Course.
Följande kod körs när en lärare väljs (id != null).
if (id != null)
{
InstructorID = id.Value;
Instructor instructor = InstructorData.Instructors
.Where(i => i.ID == id.Value).Single();
InstructorData.Courses = instructor.CourseAssignments.Select(s => s.Course);
}
Den valda instruktören hämtas från listan över instruktörer i vymodellen. Vy-modellens Courses-egenskap fylls med entiteterna Course från instruktörens navigeringsegenskap CourseAssignments.
Metoden Where returnerar en samling. Men i det här fallet väljer filtret en enda entitet, så Single metoden anropas för att konvertera samlingen till en enda Instructor entitet. Entiteten Instructor ger åtkomst till CourseAssignments egenskapen.
CourseAssignments ger åtkomst till relaterade Course entiteter.
Metoden Single används i en samling när samlingen bara har ett objekt. Metoden Single genererar ett undantag om samlingen är tom eller om det finns fler än ett objekt. Ett alternativ är SingleOrDefault, som returnerar ett standardvärde (null i det här fallet) om samlingen är tom.
Följande kod fyller i vymodellens egenskap när en kurs väljs Enrollments :
if (courseID != null)
{
CourseID = courseID.Value;
var selectedCourse = InstructorData.Courses
.Where(x => x.CourseID == courseID).Single();
InstructorData.Enrollments = selectedCourse.Enrollments;
}
Uppdatera sidan instruktörsindex
Uppdatera Pages/Instructors/Index.cshtml med följande kod.
@page "{id:int?}"
@model ContosoUniversity.Pages.Instructors.IndexModel
@{
ViewData["Title"] = "Instructors";
}
<h2>Instructors</h2>
<p>
<a asp-page="Create">Create New</a>
</p>
<table class="table">
<thead>
<tr>
<th>Last Name</th>
<th>First Name</th>
<th>Hire Date</th>
<th>Office</th>
<th>Courses</th>
<th></th>
</tr>
</thead>
<tbody>
@foreach (var item in Model.InstructorData.Instructors)
{
string selectedRow = "";
if (item.ID == Model.InstructorID)
{
selectedRow = "table-success";
}
<tr class="@selectedRow">
<td>
@Html.DisplayFor(modelItem => item.LastName)
</td>
<td>
@Html.DisplayFor(modelItem => item.FirstMidName)
</td>
<td>
@Html.DisplayFor(modelItem => item.HireDate)
</td>
<td>
@if (item.OfficeAssignment != null)
{
@item.OfficeAssignment.Location
}
</td>
<td>
@{
foreach (var course in item.CourseAssignments)
{
@course.Course.CourseID @: @course.Course.Title <br />
}
}
</td>
<td>
<a asp-page="./Index" asp-route-id="@item.ID">Select</a> |
<a asp-page="./Edit" asp-route-id="@item.ID">Edit</a> |
<a asp-page="./Details" asp-route-id="@item.ID">Details</a> |
<a asp-page="./Delete" asp-route-id="@item.ID">Delete</a>
</td>
</tr>
}
</tbody>
</table>
@if (Model.InstructorData.Courses != null)
{
<h3>Courses Taught by Selected Instructor</h3>
<table class="table">
<tr>
<th></th>
<th>Number</th>
<th>Title</th>
<th>Department</th>
</tr>
@foreach (var item in Model.InstructorData.Courses)
{
string selectedRow = "";
if (item.CourseID == Model.CourseID)
{
selectedRow = "table-success";
}
<tr class="@selectedRow">
<td>
<a asp-page="./Index" asp-route-courseID="@item.CourseID">Select</a>
</td>
<td>
@item.CourseID
</td>
<td>
@item.Title
</td>
<td>
@item.Department.Name
</td>
</tr>
}
</table>
}
@if (Model.InstructorData.Enrollments != null)
{
<h3>
Students Enrolled in Selected Course
</h3>
<table class="table">
<tr>
<th>Name</th>
<th>Grade</th>
</tr>
@foreach (var item in Model.InstructorData.Enrollments)
{
<tr>
<td>
@item.Student.FullName
</td>
<td>
@Html.DisplayFor(modelItem => item.Grade)
</td>
</tr>
}
</table>
}
Föregående kod gör följande ändringar:
Uppdaterar
page-direktivet från@pagetill@page "{id:int?}"."{id:int?}"är en vägmall. Routningsmallen ändrar frågesträngar med heltal i URL:en till routedata. Om du till exempel klickar på länken Välj för en lärare med endast@pagedirektivet skapas en URL som liknar följande:https://localhost:5001/Instructors?id=2När siddirektivet är
@page "{id:int?}"är URL:en:https://localhost:5001/Instructors/2Lägger till en Office-kolumn som endast visar
item.OfficeAssignment.Locationomitem.OfficeAssignmentden inte är null. Eftersom det här är en en-till-noll-eller-en-relation kanske det inte finns någon relaterad OfficeAssignment-entitet.@if (item.OfficeAssignment != null) { @item.OfficeAssignment.Location }Lägger till en kurskolumn som visar kurser som undervisas av varje lärare. Mer information om den här Razor-syntaxen finns i Explicit radövergång.
Lägger till kod som dynamiskt lägger till
class="table-success"i elementet för den valda läraren och kursentr. Detta anger en bakgrundsfärg för den valda raden med hjälp av en Bootstrap-klass.string selectedRow = ""; if (item.CourseID == Model.CourseID) { selectedRow = "table-success"; } <tr class="@selectedRow">Lägger till en ny hyperlänk med etiketten Välj. Den här länken skickar den valda instruktörens
IndexID till metoden och anger en bakgrundsfärg.<a asp-action="Index" asp-route-id="@item.ID">Select</a> |Lägger till en tabell med kurser för den valda instruktören.
Lägger till en tabell med studentregistreringar för den valda kursen.
Kör appen och välj fliken Instruktörer . Sidan visar Location (office) från den relaterade OfficeAssignment entiteten. Om OfficeAssignment är null visas en tom tabellcell.
Klicka på länken Välj för en lärare. Radformatet ändras och kurser som tilldelats den instruktören visas.
Välj en kurs för att se listan över registrerade studenter och deras betyg.
Använda enkel
Metoden Single kan skicka villkoret Where i stället för att anropa Where metoden separat:
public async Task OnGetAsync(int? id, int? courseID)
{
InstructorData = new InstructorIndexData();
InstructorData.Instructors = await _context.Instructors
.Include(i => i.OfficeAssignment)
.Include(i => i.CourseAssignments)
.ThenInclude(i => i.Course)
.ThenInclude(i => i.Department)
.Include(i => i.CourseAssignments)
.ThenInclude(i => i.Course)
.ThenInclude(i => i.Enrollments)
.ThenInclude(i => i.Student)
.AsNoTracking()
.OrderBy(i => i.LastName)
.ToListAsync();
if (id != null)
{
InstructorID = id.Value;
Instructor instructor = InstructorData.Instructors.Single(
i => i.ID == id.Value);
InstructorData.Courses = instructor.CourseAssignments.Select(
s => s.Course);
}
if (courseID != null)
{
CourseID = courseID.Value;
InstructorData.Enrollments = InstructorData.Courses.Single(
x => x.CourseID == courseID).Enrollments;
}
}
Användning av Single med ett Where-villkor är en fråga om personlig preferens. Det ger inga fördelar jämfört med att använda Where metoden.
Explicit inläsning
Den aktuella koden anger ivrig inläsning för Enrollments och Students:
InstructorData.Instructors = await _context.Instructors
.Include(i => i.OfficeAssignment)
.Include(i => i.CourseAssignments)
.ThenInclude(i => i.Course)
.ThenInclude(i => i.Department)
.Include(i => i.CourseAssignments)
.ThenInclude(i => i.Course)
.ThenInclude(i => i.Enrollments)
.ThenInclude(i => i.Student)
.AsNoTracking()
.OrderBy(i => i.LastName)
.ToListAsync();
Anta att användare sällan vill se registreringar i en kurs. I så fall skulle en optimering vara att endast läsa in registreringsdata om det begärs. I det här avsnittet uppdateras OnGetAsync med explicit inläsning av Enrollments och Students.
Uppdatera Pages/Instructors/Index.cshtml.cs med följande kod.
using ContosoUniversity.Models;
using ContosoUniversity.Models.SchoolViewModels; // Add VM
using Microsoft.AspNetCore.Mvc.RazorPages;
using Microsoft.EntityFrameworkCore;
using System.Linq;
using System.Threading.Tasks;
namespace ContosoUniversity.Pages.Instructors
{
public class IndexModel : PageModel
{
private readonly ContosoUniversity.Data.SchoolContext _context;
public IndexModel(ContosoUniversity.Data.SchoolContext context)
{
_context = context;
}
public InstructorIndexData InstructorData { get; set; }
public int InstructorID { get; set; }
public int CourseID { get; set; }
public async Task OnGetAsync(int? id, int? courseID)
{
InstructorData = new InstructorIndexData();
InstructorData.Instructors = await _context.Instructors
.Include(i => i.OfficeAssignment)
.Include(i => i.CourseAssignments)
.ThenInclude(i => i.Course)
.ThenInclude(i => i.Department)
//.Include(i => i.CourseAssignments)
// .ThenInclude(i => i.Course)
// .ThenInclude(i => i.Enrollments)
// .ThenInclude(i => i.Student)
//.AsNoTracking()
.OrderBy(i => i.LastName)
.ToListAsync();
if (id != null)
{
InstructorID = id.Value;
Instructor instructor = InstructorData.Instructors
.Where(i => i.ID == id.Value).Single();
InstructorData.Courses = instructor.CourseAssignments.Select(s => s.Course);
}
if (courseID != null)
{
CourseID = courseID.Value;
var selectedCourse = InstructorData.Courses
.Where(x => x.CourseID == courseID).Single();
await _context.Entry(selectedCourse).Collection(x => x.Enrollments).LoadAsync();
foreach (Enrollment enrollment in selectedCourse.Enrollments)
{
await _context.Entry(enrollment).Reference(x => x.Student).LoadAsync();
}
InstructorData.Enrollments = selectedCourse.Enrollments;
}
}
}
}
Den föregående koden utelämnar metodanropen ThenInclude för registrerings- och studentdata. Om en kurs har valts hämtas den uttryckliga laddningskoden:
- Entiteterna
Enrollmentför den valda kursen. - Entiteterna
Studentför varjeEnrollment.
Observera att föregående kod kommenterar ut .AsNoTracking(). Navigeringsegenskaper kan bara läsas in explicit för spårade entiteter.
Testa appen. Från en användares perspektiv fungerar appen identiskt med den tidigare versionen.
Nästa steg
Nästa handledning visar hur du uppdaterar relaterade data.
I den här handledningen läses och visas relaterade data. Relaterad data är data som EF Core laddas in i navigeringsegenskaper.
Om du stöter på problem som du inte kan lösa ladda ned eller visa den färdiga appen.Ladda ned instruktioner.
Följande bilder visar de slutförda sidorna för den här handledningen.
Ivrig, explicit och lat inläsning av relaterade data
Det finns flera sätt att EF Core läsa in relaterade data i navigeringsegenskaperna för en entitet:
Ivrig inläsning. Ivrig inläsning är när en fråga för en typ av entitet också läser in relaterade entiteter. När entiteten läss hämtas dess relaterade data. Detta resulterar vanligtvis i en enda kopplingsfråga som hämtar alla data som behövs. EF Core kommer att utfärda flera frågekommandon för vissa typer av ivrig inläsning. Att utfärda flera frågor kan vara effektivare än vad som var fallet för vissa frågor i EF6 där det fanns en enda fråga. Ivrig inläsning anges med
Includemetoderna ochThenInclude.
Eager loading skickar flera sökfrågor när en samlingsnavigering ingår.
- En fråga för huvudfrågan
- En fråga för varje "kant" i en samling i lastträdet.
Separata frågor med
Load: Data kan hämtas i separata frågor och EF Core "korrigerar" navigeringsegenskaperna. "fixar till" innebär att EF Core navigeringsegenskaperna fylls i automatiskt. Separata frågor medLoadär mer som explicit inläsning än ivrig inläsning.
Obs! EF Core korrigerar automatiskt navigeringsegenskaper till andra entiteter som tidigare har lästs in i den aktuella kontextinstansen. Även om data för en navigeringsegenskap inte uttryckligen inkluderas kan egenskapen fortfarande fyllas i om vissa eller alla relaterade entiteter har lästs in tidigare.
Explicit inläsning. När entiteten först läss hämtas inte relaterade data. Koden måste skrivas för att hämta relaterade data när den behövs. Explicit inläsning med separata frågor resulterar i flera frågor som skickas till databasen. Med explicit inläsning anger koden de navigeringsegenskaper som ska läsas in. Använd
Load-metoden för att göra en explicit inläsning. Till exempel:
Lat inläsning. Lazy loading lade till EF Core i version 2.1. När entiteten först läss hämtas inte relaterade data. Första gången en navigeringsegenskap används hämtas de data som krävs för navigeringsegenskapen automatiskt. En fråga skickas till databasen varje gång en navigeringsegenskap används för första gången.
Operatorn
Selectläser bara in de relaterade data som behövs.
Skapa en kurssida som visar avdelningsnamn
Entiteten Kurs innehåller en navigeringsegenskap som innehåller Department entiteten. Entiteten Department innehåller den avdelning som kursen är tilldelad till.
Så här visar du namnet på den tilldelade avdelningen i en lista över kurser:
- Hämta egenskapen
Namefrån entitetenDepartment. - Entiteten
Departmentkommer från navigeringsegenskapenCourse.Department.
Skapa ramverk för kursmodellen
Följ anvisningarna i Stödstrukturera studentmodellen och använd Course för model class.
Föregående kommando bygger upp strukturen för Course-modellen. Öppna projektet i Visual Studio.
Öppna Pages/Courses/Index.cshtml.cs och granska OnGetAsync metoden. Scaffoldingmotorn angav föruppladdning för navigeringsegenskapen Department. Metoden Include anger ivrig inläsning.
Kör appen och välj länken Kurser . I kolumnen Avdelning visas DepartmentID, vilket inte är användbart.
OnGetAsync Uppdatera metoden med följande kod:
public async Task OnGetAsync()
{
Course = await _context.Courses
.Include(c => c.Department)
.AsNoTracking()
.ToListAsync();
}
Föregående kod lägger till AsNoTracking.
AsNoTracking förbättrar prestanda eftersom de entiteter som returneras inte spåras. Entiteterna spåras inte eftersom de inte uppdateras i den aktuella kontexten.
Uppdatera Pages/Courses/Index.cshtml med följande markerade markeringar:
@page
@model ContosoUniversity.Pages.Courses.IndexModel
@{
ViewData["Title"] = "Courses";
}
<h2>Courses</h2>
<p>
<a asp-page="Create">Create New</a>
</p>
<table class="table">
<thead>
<tr>
<th>
@Html.DisplayNameFor(model => model.Course[0].CourseID)
</th>
<th>
@Html.DisplayNameFor(model => model.Course[0].Title)
</th>
<th>
@Html.DisplayNameFor(model => model.Course[0].Credits)
</th>
<th>
@Html.DisplayNameFor(model => model.Course[0].Department)
</th>
<th></th>
</tr>
</thead>
<tbody>
@foreach (var item in Model.Course)
{
<tr>
<td>
@Html.DisplayFor(modelItem => item.CourseID)
</td>
<td>
@Html.DisplayFor(modelItem => item.Title)
</td>
<td>
@Html.DisplayFor(modelItem => item.Credits)
</td>
<td>
@Html.DisplayFor(modelItem => item.Department.Name)
</td>
<td>
<a asp-page="./Edit" asp-route-id="@item.CourseID">Edit</a> |
<a asp-page="./Details" asp-route-id="@item.CourseID">Details</a> |
<a asp-page="./Delete" asp-route-id="@item.CourseID">Delete</a>
</td>
</tr>
}
</tbody>
</table>
Följande ändringar har gjorts i den genererade koden:
Ändrade rubriken från Index till Kurser.
Lade till en talkolumn som visar egenskapsvärdet
CourseID. Som standard är primära nycklar inte genererade eftersom de normalt är meningslösa för slutanvändarna. I det här fallet är dock den primära nyckeln meningsfull.Ändrade kolumnen Avdelning för att visa avdelningsnamnet. Koden visar
Name-egenskapen förDepartment-entiteten som är inläst iDepartment-navigeringsegenskapen.@Html.DisplayFor(modelItem => item.Department.Name)
Kör appen och välj fliken Kurser för att se listan med avdelningsnamn.
Läser in relaterade data med Select
Metoden OnGetAsync läser in relaterade data med Include metoden:
public async Task OnGetAsync()
{
Course = await _context.Courses
.Include(c => c.Department)
.AsNoTracking()
.ToListAsync();
}
Operatorn Select läser bara in de relaterade data som behövs. För enskilda objekt, som det Department.Name, använder det en SQL INNER JOIN. För samlingar använder den en annan databasåtkomst, men det gör även operatorn för Include samlingar.
Följande kod läser in relaterade data med Select metoden:
public IList<CourseViewModel> CourseVM { get; set; }
public async Task OnGetAsync()
{
CourseVM = await _context.Courses
.Select(p => new CourseViewModel
{
CourseID = p.CourseID,
Title = p.Title,
Credits = p.Credits,
DepartmentName = p.Department.Name
}).ToListAsync();
}
Den CourseViewModel:
public class CourseViewModel
{
public int CourseID { get; set; }
public string Title { get; set; }
public int Credits { get; set; }
public string DepartmentName { get; set; }
}
Se IndexSelect.cshtml och IndexSelect.cshtml.cs för ett fullständigt exempel.
Skapa en instruktörssida som visar kurser och registreringar
I det här avsnittet skapas sidan Lärare.
Den här sidan läser och visar relaterade data på följande sätt:
- Listan över lärare visar relaterade data från entiteten
OfficeAssignment(Office i föregående bild). EntiteternaInstructorochOfficeAssignmentfinns i en en-till-noll-eller-en-relation. Eager loading används för entiteternaOfficeAssignment. Ivrig inläsning är vanligtvis effektivare när relaterade data behöver visas. I det här fallet visas kontorstilldelningar för instruktörerna. - När användaren väljer en instruktör (Harui i föregående bild) visas relaterade
Courseentiteter. EntiteternaInstructorochCoursefinns i en många-till-många-relation. Eager loading används förCourseentiteter och deras relateradeDepartmententiteter. I det här fallet kan separata frågor vara mer effektiva eftersom endast kurser för den valda instruktören behövs. Det här exemplet visar hur du använder ivrig inläsning för navigeringsegenskaper i entiteter som finns i navigeringsegenskaper. - När användaren väljer en kurs (kemi i föregående bild) visas relaterade data från entiteten
Enrollments. I föregående bild visas elevnamn och betyg. EntiteternaCourseochEnrollmentfinns i en en-till-många-relation.
Skapa en vymodell för vyn Instruktörsindex
Sidan lärare visar data från tre olika tabeller. En vymodell skapas som innehåller de tre entiteterna som representerar de tre tabellerna.
I mappen SchoolViewModels skapar du InstructorIndexData.cs med följande kod:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace ContosoUniversity.Models.SchoolViewModels
{
public class InstructorIndexData
{
public IEnumerable<Instructor> Instructors { get; set; }
public IEnumerable<Course> Courses { get; set; }
public IEnumerable<Enrollment> Enrollments { get; set; }
}
}
Bygg upp instruktörsmodellen
Följ anvisningarna i Stödstrukturera studentmodellen och använd Instructor för model class.
Föregående kommando bygger upp strukturen för Instructor-modellen.
Kör appen och gå till instruktörssidan.
Ersätt Pages/Instructors/Index.cshtml.cs med följande kod:
using ContosoUniversity.Models;
using ContosoUniversity.Models.SchoolViewModels; // Add VM
using Microsoft.AspNetCore.Mvc.RazorPages;
using Microsoft.EntityFrameworkCore;
using System.Linq;
using System.Threading.Tasks;
namespace ContosoUniversity.Pages.Instructors
{
public class IndexModel : PageModel
{
private readonly ContosoUniversity.Data.SchoolContext _context;
public IndexModel(ContosoUniversity.Data.SchoolContext context)
{
_context = context;
}
public InstructorIndexData Instructor { get; set; }
public int InstructorID { get; set; }
public async Task OnGetAsync(int? id)
{
Instructor = new InstructorIndexData();
Instructor.Instructors = await _context.Instructors
.Include(i => i.OfficeAssignment)
.Include(i => i.CourseAssignments)
.ThenInclude(i => i.Course)
.AsNoTracking()
.OrderBy(i => i.LastName)
.ToListAsync();
if (id != null)
{
InstructorID = id.Value;
}
}
}
}
Metoden OnGetAsync accepterar valfria routningsdata för ID:t för den valda instruktören.
Granska frågan i Pages/Instructors/Index.cshtml.cs filen:
Instructor.Instructors = await _context.Instructors
.Include(i => i.OfficeAssignment)
.Include(i => i.CourseAssignments)
.ThenInclude(i => i.Course)
.AsNoTracking()
.OrderBy(i => i.LastName)
.ToListAsync();
Frågan har två inkluderingar:
-
OfficeAssignment: Visas i lärarvyn. -
CourseAssignments: Som introducerar de kurser som undervisas.
Uppdatera sidan instruktörsindex
Uppdatera Pages/Instructors/Index.cshtml med följande markering:
@page "{id:int?}"
@model ContosoUniversity.Pages.Instructors.IndexModel
@{
ViewData["Title"] = "Instructors";
}
<h2>Instructors</h2>
<p>
<a asp-page="Create">Create New</a>
</p>
<table class="table">
<thead>
<tr>
<th>Last Name</th>
<th>First Name</th>
<th>Hire Date</th>
<th>Office</th>
<th>Courses</th>
<th></th>
</tr>
</thead>
<tbody>
@foreach (var item in Model.Instructor.Instructors)
{
string selectedRow = "";
if (item.ID == Model.InstructorID)
{
selectedRow = "table-success";
}
<tr class="@selectedRow">
<td>
@Html.DisplayFor(modelItem => item.LastName)
</td>
<td>
@Html.DisplayFor(modelItem => item.FirstMidName)
</td>
<td>
@Html.DisplayFor(modelItem => item.HireDate)
</td>
<td>
@if (item.OfficeAssignment != null)
{
@item.OfficeAssignment.Location
}
</td>
<td>
@{
foreach (var course in item.CourseAssignments)
{
@course.Course.CourseID @: @course.Course.Title <br />
}
}
</td>
<td>
<a asp-page="./Index" asp-route-id="@item.ID">Select</a> |
<a asp-page="./Edit" asp-route-id="@item.ID">Edit</a> |
<a asp-page="./Details" asp-route-id="@item.ID">Details</a> |
<a asp-page="./Delete" asp-route-id="@item.ID">Delete</a>
</td>
</tr>
}
</tbody>
</table>
Den föregående märkningen gör följande ändringar:
Uppdaterar
page-direktivet från@pagetill@page "{id:int?}"."{id:int?}"är en vägmall. Routningsmallen ändrar frågesträngar med heltal i URL:en till routedata. Om du till exempel klickar på länken Välj för en lärare med endast@pagedirektivet skapas en URL som liknar följande:http://localhost:1234/Instructors?id=2När siddirektivet är
@page "{id:int?}"är den föregående URL:en:http://localhost:1234/Instructors/2Sidrubriken är Instruktörer.
Lade till en Office-kolumn som endast visar
item.OfficeAssignment.Locationomitem.OfficeAssignmentden inte är null. Eftersom det här är en en-till-noll-eller-en-relation kanske det inte finns någon relaterad OfficeAssignment-entitet.@if (item.OfficeAssignment != null) { @item.OfficeAssignment.Location }Lade till en kurskolumn som visar kurser som undervisas av varje instruktör. Mer information om den här Razor-syntaxen finns i Explicit radövergång.
Koden har lagts till som dynamiskt lägger
class="table-success"till elementettrför den valda instruktören. Detta anger en bakgrundsfärg för den valda raden med hjälp av en Bootstrap-klass.string selectedRow = ""; if (item.CourseID == Model.CourseID) { selectedRow = "table-success"; } <tr class="@selectedRow">En ny hyperlänk med etiketten Välj har lagts till. Den här länken skickar den valda instruktörens
IndexID till metoden och anger en bakgrundsfärg.<a asp-action="Index" asp-route-id="@item.ID">Select</a> |
Kör appen och välj fliken Instruktörer . Sidan visar Location (office) från den relaterade OfficeAssignment entiteten. Om OfficeAssignment är null visas en tom tabellcell.
Klicka på länken Välj . Radformatet ändras.
Lägga till kurser som undervisas av vald instruktör
OnGetAsync Uppdatera metoden i Pages/Instructors/Index.cshtml.cs med följande kod:
public async Task OnGetAsync(int? id, int? courseID)
{
Instructor = new InstructorIndexData();
Instructor.Instructors = await _context.Instructors
.Include(i => i.OfficeAssignment)
.Include(i => i.CourseAssignments)
.ThenInclude(i => i.Course)
.ThenInclude(i => i.Department)
.AsNoTracking()
.OrderBy(i => i.LastName)
.ToListAsync();
if (id != null)
{
InstructorID = id.Value;
Instructor instructor = Instructor.Instructors.Where(
i => i.ID == id.Value).Single();
Instructor.Courses = instructor.CourseAssignments.Select(s => s.Course);
}
if (courseID != null)
{
CourseID = courseID.Value;
Instructor.Enrollments = Instructor.Courses.Where(
x => x.CourseID == courseID).Single().Enrollments;
}
}
Lägg till public int CourseID { get; set; }
public class IndexModel : PageModel
{
private readonly ContosoUniversity.Data.SchoolContext _context;
public IndexModel(ContosoUniversity.Data.SchoolContext context)
{
_context = context;
}
public InstructorIndexData Instructor { get; set; }
public int InstructorID { get; set; }
public int CourseID { get; set; }
public async Task OnGetAsync(int? id, int? courseID)
{
Instructor = new InstructorIndexData();
Instructor.Instructors = await _context.Instructors
.Include(i => i.OfficeAssignment)
.Include(i => i.CourseAssignments)
.ThenInclude(i => i.Course)
.ThenInclude(i => i.Department)
.AsNoTracking()
.OrderBy(i => i.LastName)
.ToListAsync();
if (id != null)
{
InstructorID = id.Value;
Instructor instructor = Instructor.Instructors.Where(
i => i.ID == id.Value).Single();
Instructor.Courses = instructor.CourseAssignments.Select(s => s.Course);
}
if (courseID != null)
{
CourseID = courseID.Value;
Instructor.Enrollments = Instructor.Courses.Where(
x => x.CourseID == courseID).Single().Enrollments;
}
}
Granska den uppdaterade frågan:
Instructor.Instructors = await _context.Instructors
.Include(i => i.OfficeAssignment)
.Include(i => i.CourseAssignments)
.ThenInclude(i => i.Course)
.ThenInclude(i => i.Department)
.AsNoTracking()
.OrderBy(i => i.LastName)
.ToListAsync();
Föregående fråga lägger till entiteterna Department .
Följande kod körs när en lärare väljs (id != null). Den valda instruktören hämtas från listan över instruktörer i vymodellen. Vy-modellens Courses-egenskap fylls med entiteterna Course från instruktörens navigeringsegenskap CourseAssignments.
if (id != null)
{
InstructorID = id.Value;
Instructor instructor = Instructor.Instructors.Where(
i => i.ID == id.Value).Single();
Instructor.Courses = instructor.CourseAssignments.Select(s => s.Course);
}
Metoden Where returnerar en samling. I föregående Where metod returneras endast en enda Instructor entitet. Metoden Single konverterar samlingen till en enda entitet Instructor . Entiteten Instructor ger åtkomst till CourseAssignments egenskapen.
CourseAssignments ger åtkomst till relaterade Course entiteter.
Metoden Single används i en samling när samlingen bara har ett objekt. Metoden Single genererar ett undantag om samlingen är tom eller om det finns fler än ett objekt. Ett alternativ är SingleOrDefault, som returnerar ett standardvärde (null i det här fallet) om samlingen är tom. Använda SingleOrDefault i en tom samling:
- Resulterar i ett undantag (från att försöka hitta en
Coursesegenskap på en null-referens). - Undantagsmeddelandet skulle mindre tydligt ange orsaken till problemet.
Följande kod fyller i vymodellens egenskap när en kurs väljs Enrollments :
if (courseID != null)
{
CourseID = courseID.Value;
Instructor.Enrollments = Instructor.Courses.Where(
x => x.CourseID == courseID).Single().Enrollments;
}
Lägg till följande markering i slutet av sidan Pages/Instructors/Index.cshtmlRazor :
<a asp-page="./Delete" asp-route-id="@item.ID">Delete</a>
</td>
</tr>
}
</tbody>
</table>
@if (Model.Instructor.Courses != null)
{
<h3>Courses Taught by Selected Instructor</h3>
<table class="table">
<tr>
<th></th>
<th>Number</th>
<th>Title</th>
<th>Department</th>
</tr>
@foreach (var item in Model.Instructor.Courses)
{
string selectedRow = "";
if (item.CourseID == Model.CourseID)
{
selectedRow = "table-success";
}
<tr class="@selectedRow">
<td>
<a asp-page="./Index" asp-route-courseID="@item.CourseID">Select</a>
</td>
<td>
@item.CourseID
</td>
<td>
@item.Title
</td>
<td>
@item.Department.Name
</td>
</tr>
}
</table>
}
Föregående markering visar en lista över kurser som är relaterade till en instruktör när en instruktör väljs.
Testa appen. Klicka på länken Välj på sidan Lärare.
Visa elevdata
I det här avsnittet uppdateras appen för att visa studentdata för en vald kurs.
Uppdatera frågan i OnGetAsync metoden i Pages/Instructors/Index.cshtml.cs med följande kod:
Instructor.Instructors = await _context.Instructors
.Include(i => i.OfficeAssignment)
.Include(i => i.CourseAssignments)
.ThenInclude(i => i.Course)
.ThenInclude(i => i.Department)
.Include(i => i.CourseAssignments)
.ThenInclude(i => i.Course)
.ThenInclude(i => i.Enrollments)
.ThenInclude(i => i.Student)
.AsNoTracking()
.OrderBy(i => i.LastName)
.ToListAsync();
Uppdatera Pages/Instructors/Index.cshtml. Lägg till följande markering i slutet av filen:
@if (Model.Instructor.Enrollments != null)
{
<h3>
Students Enrolled in Selected Course
</h3>
<table class="table">
<tr>
<th>Name</th>
<th>Grade</th>
</tr>
@foreach (var item in Model.Instructor.Enrollments)
{
<tr>
<td>
@item.Student.FullName
</td>
<td>
@Html.DisplayFor(modelItem => item.Grade)
</td>
</tr>
}
</table>
}
Föregående markering visar en lista över de elever som är registrerade i den valda kursen.
Uppdatera sidan och välj en instruktör. Välj en kurs för att se listan över registrerade studenter och deras betyg.
Använda enkel
Metoden Single kan skicka villkoret Where i stället för att anropa Where metoden separat:
public async Task OnGetAsync(int? id, int? courseID)
{
Instructor = new InstructorIndexData();
Instructor.Instructors = await _context.Instructors
.Include(i => i.OfficeAssignment)
.Include(i => i.CourseAssignments)
.ThenInclude(i => i.Course)
.ThenInclude(i => i.Department)
.Include(i => i.CourseAssignments)
.ThenInclude(i => i.Course)
.ThenInclude(i => i.Enrollments)
.ThenInclude(i => i.Student)
.AsNoTracking()
.OrderBy(i => i.LastName)
.ToListAsync();
if (id != null)
{
InstructorID = id.Value;
Instructor instructor = Instructor.Instructors.Single(
i => i.ID == id.Value);
Instructor.Courses = instructor.CourseAssignments.Select(
s => s.Course);
}
if (courseID != null)
{
CourseID = courseID.Value;
Instructor.Enrollments = Instructor.Courses.Single(
x => x.CourseID == courseID).Enrollments;
}
}
Single Föregående metod ger inga fördelar jämfört med att använda Where. Vissa utvecklare föredrar metodstilen Single .
Explicit inläsning
Den aktuella koden anger ivrig inläsning för Enrollments och Students:
Instructor.Instructors = await _context.Instructors
.Include(i => i.OfficeAssignment)
.Include(i => i.CourseAssignments)
.ThenInclude(i => i.Course)
.ThenInclude(i => i.Department)
.Include(i => i.CourseAssignments)
.ThenInclude(i => i.Course)
.ThenInclude(i => i.Enrollments)
.ThenInclude(i => i.Student)
.AsNoTracking()
.OrderBy(i => i.LastName)
.ToListAsync();
Anta att användare sällan vill se registreringar i en kurs. I så fall skulle en optimering vara att endast läsa in registreringsdata om det begärs. I det här avsnittet uppdateras OnGetAsync med explicit inläsning av Enrollments och Students.
OnGetAsync Uppdatera med följande kod:
public async Task OnGetAsync(int? id, int? courseID)
{
Instructor = new InstructorIndexData();
Instructor.Instructors = await _context.Instructors
.Include(i => i.OfficeAssignment)
.Include(i => i.CourseAssignments)
.ThenInclude(i => i.Course)
.ThenInclude(i => i.Department)
//.Include(i => i.CourseAssignments)
// .ThenInclude(i => i.Course)
// .ThenInclude(i => i.Enrollments)
// .ThenInclude(i => i.Student)
// .AsNoTracking()
.OrderBy(i => i.LastName)
.ToListAsync();
if (id != null)
{
InstructorID = id.Value;
Instructor instructor = Instructor.Instructors.Where(
i => i.ID == id.Value).Single();
Instructor.Courses = instructor.CourseAssignments.Select(s => s.Course);
}
if (courseID != null)
{
CourseID = courseID.Value;
var selectedCourse = Instructor.Courses.Where(x => x.CourseID == courseID).Single();
await _context.Entry(selectedCourse).Collection(x => x.Enrollments).LoadAsync();
foreach (Enrollment enrollment in selectedCourse.Enrollments)
{
await _context.Entry(enrollment).Reference(x => x.Student).LoadAsync();
}
Instructor.Enrollments = selectedCourse.Enrollments;
}
}
Den föregående koden utelämnar metodanropen ThenInclude för registrerings- och studentdata. Om en kurs har valts hämtas den markerade koden:
- Entiteterna
Enrollmentför den valda kursen. - Entiteterna
Studentför varjeEnrollment.
Lägg märke till föregående kodkommentare .AsNoTracking(). Navigeringsegenskaper kan bara läsas in explicit för spårade entiteter.
Testa appen. Från ett användarperspektiv beter sig appen identiskt med den tidigare versionen.
Nästa handledning visar hur du uppdaterar relaterade data.
Ytterligare resurser
ASP.NET Core