Это помогает использовать сущности как объекты, а не думать о них как о таблицах. Да, они обычно соотносятся непосредственно с базовыми таблицами, но это средство для достижения цели. Вы можете использовать отношения более напрямую, чем просто рассматривать их как другой способ написания SQL.
Например:
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();
Это будет примерно соответствовать выражению SQL с кучей внутренних объединений и предложением OrderBy. В сфере EF это будет считаться плохой практикой. Причина в том, что, как и в выражении SQL с внутренними объединениями, вы эффективно выполняете «SELECT *» для всех этих таблиц. Вы действительно хотите всех столбцов всех объединенных таблиц?
AsNoTracking()
просто говорит EF, что для полученных данных вы не собираетесь изменять их, поэтому не беспокойтесь о слежении за грязным состоянием. Это настройка производительности для операций чтения.
ToListAsync()
выполняет запрос как ожидаемую операцию, которая освобождает поток, к которому был вызван метод. Здесь нет волшебного многопоточного выполнения, просто вызов может быть передан SQL Server, освободить его поток, а затем назначить новый поток на основе точки продолжения после ожидания.
Одним предупреждающим знаком, который я вижу в примере, является использование нулевых параметров. Может ли этот метод корректно вызываться с помощью:
- Ни ID, ни ID курса?
и
- ID без ID курса?
и
- Идентификатор курса без идентификатора?
и
- И ID, и ID курса?
Если любая из этих комбинаций недопустима, метод должен быть разделен или уточнен.
Возвращаясь к поведению «SELECT *», используя EF, вы получаете много возможностей, скрытых за кулисами и готовых превратить операции Linq map / Reduce в SQL для запуска на сервере и возврата значимого минимального набора данных. .
Например:
var query = _context.Instructors.AsQueryable();
if (id.HasValue)
query = query.Where(i => i.ID == id.Value);
query = query.OrderBy(i => i.LastName);
var instructors = await query.Select(i => new InstructorIndexData
{
InstructorId = i.ID,
// ...
Courses = i.CourseAssignments.Select(ca => new CourseData {
CourseId = ca.Course.ID,
CourseName = ca.Course.Name,
//..
}
}).ToListAsync()
if (courseId.HasValue)
{
var enrollments = await query.SelectMany(i => i.Courses.SingleOrDefault(c => c.CourseID == courseID.Value).Enrollments.Select(e => new EnrollmentData
{
InstructorId = i.ID,
EnrollmentId = e.EnrollmentID,
CourseId = e.Course.CourseID,
//...
}).ToListAsync();
// From here, group the Enrollments by Instructor ID and add them to the Instructor index data.
var groupedEnrollments = enrollments.GroupBy(e => e.InstructorId);
foreach(instructorId in groupedEnrollments.Keys)
{
var instructor = instructors.Single(i => i.InstructorId == instructorId);
instructor.Enrollments = groupedEnrollments[instructorId].ToList();
}
}
Теперь предостережение состоит в том, что я основываю это на памяти и на примерном предположении вашей структуры и желаемого результата. Ключевыми моментами будут использование IQueryable
и выдача операторов Select
, чтобы просто получить точные данные, необходимые для заполнения объектов, которые вы хотите предоставить представлению.
Я делаю это в 2 выполнениях запроса, один для получения инструктора (ов), затем второй для получения заявок, если они запрашиваются на основе предоставленного идентификатора курса. Лично я бы разделил это на два метода, так как ожидал, что регистрация будет необязательной. Также есть разница между выборкой одного инструктора и всех инструкторов. В случаях, когда возвращаются потенциально большие объемы данных, вы должны рассмотреть возможность разбиения на страницы с Skip()
и Take()
, чтобы избежать дорогостоящих запросов, затормаживающих использование ЦП, сети и памяти.