Структура Entity Framework - PullRequest
       3

Структура Entity Framework

0 голосов
/ 08 ноября 2018

Я прохожу этот урок, чтобы помочь мне лучше понять структуру EF. В настоящее время я использую SQL.

https://docs.microsoft.com/en-us/aspnet/core/data/ef-rp/read-related-data?view=aspnetcore-2.1&tabs=visual-studio

В этом примере показаны инструктор, офис, студент, курс, класс и задания

    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;
            Instructor.Enrollments = Instructor.Courses.Where(
                x => x.CourseID == courseID).Single().Enrollments;
        }
    }

Чтобы помочь мне лучше понять синтаксис, будет ли этот оператор SQL эквивалентным?

SELECT        *
FROM            Instructor INNER JOIN
                         OfficeAssignment ON Instructor.ID = OfficeAssignment.InstructorID INNER JOIN
                         Department ON Instructor.ID = Department.InstructorID INNER JOIN
                         Course ON Department.DepartmentID = Course.DepartmentID INNER JOIN
                         Enrollment ON Course.CourseID = Enrollment.CourseID INNER JOIN
                         CourseAssignment ON Course.CourseID = CourseAssignment.CourseID INNER JOIN
                         Student ON Enrollment.StudentID = Student.ID
WHERE Instructor.ID = @ID AND Course.CourseID = @CourseID ORDER BY Instructor.Lastname

1 Ответ

0 голосов
/ 09 ноября 2018

Это помогает использовать сущности как объекты, а не думать о них как о таблицах. Да, они обычно соотносятся непосредственно с базовыми таблицами, но это средство для достижения цели. Вы можете использовать отношения более напрямую, чем просто рассматривать их как другой способ написания 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(), чтобы избежать дорогостоящих запросов, затормаживающих использование ЦП, сети и памяти.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...