EF Core 3.0 — преобразование SQL в LINQ

Пример, приведенный в в блоге есть следующее

from e in s.StudentCourseEnrollments where courseIDs.Contains(e.Course.CourseID) select e 

Логика contains не будет работать, когда мы ищем точное совпадение. Если студент записался на 6 курсов (пример: 1,2,3,4,5,6), а запрошенный список содержит 5 (пример: 1,2,3,4,5), запрос вернет совпадение, когда он не должна. Другой способ хорошо работает, когда учащийся зачислен в подмножество запрошенного списка.

Приведенное ниже решение работает, но нужна помощь для преобразования приведенного ниже sql в LINQ (EF Core 3.0)?

Create TABLE dbo.Enrollments (StudentId INT NOT NULL, CourseId INT NOT NULL)
insert into dbo.Enrollments values (1,1)
insert into dbo.Enrollments values (1,2)
insert into dbo.Enrollments values (1,3)
insert into dbo.Enrollments values (1,4)
insert into dbo.Enrollments values (1,5)
insert into dbo.Enrollments values (1,6)

DECLARE @TempCourses TABLE
(
   CourseId INT
);

INSERT INTO @TempCourses (CourseId) VALUES (1), (2), (3),(4),(5);

SELECT t.StudentId
FROM
(
  SELECT StudentId, cnt=COUNT(*)
  FROM dbo.Enrollments
  GROUP BY StudentId
) kc
INNER JOIN
(
  SELECT cnt=COUNT(*)
  FROM @TempCourses
) nc ON nc.cnt = kc.cnt
JOIN dbo.Enrollments t ON t.StudentId = kc.StudentId
JOIN @TempCourses n ON n.CourseId = t.CourseId
GROUP BY t.StudentId
HAVING COUNT(*) = MIN(nc.cnt);

drop table dbo.Enrollments

db‹>Fiddle


person sarvpk    schedule 07.01.2020    source источник


Ответы (2)


Я не знаю насчет SQL-запроса, но запрос EF Core 3.0 LINQ для той же задачи выглядит примерно так:

var matchIds = new[] { 1, 2, 3, 4, 5 }.AsEnumerable();
var query = dbContext.Students
    .Where(s => s.Enrollments.All(e => matchIds.Contains(e.CourseId)) 
        && s.Enrollments.Count() == matchIds.Count());

Основная работа по сопоставлению выполняется с подзапросом All. К сожалению, этого недостаточно для случая, когда связанных записей ссылок больше, чем совпадающих идентификаторов, поэтому дополнительное сравнение счетчиков решает эту проблему.

person Ivan Stoev    schedule 07.01.2020
comment
AsEnumerable() здесь не требуется. - person JohnyL; 07.01.2020
comment
@JohnyL Это указывает на то, что для идентификаторов не требуется специальный тип коллекции (массив, список и т. Д.) - достаточно любого перечисляемого. - person Ivan Stoev; 08.01.2020
comment
отлично, это работает... но всего пара небольших изменений... новый int [] и ToList() - person sarvpk; 20.09.2020

Вы можете добиться этого с помощью простого способа: живая демонстрация здесь

Допустим, у вас есть список зачислений таким образом

var enrollments  = from s in dc.Students
                   from c in s.Courses
                   select new { StudentID = s.StudentID, CourseID = c.CourseID };

Затем получить результат таким образом

    var groupedEnrollment = enrollments.GroupBy(p => p.StudentId)
                                        .Select(g => new 
                                        {
                                            StudentId = g.Key,
                                            Courses = g.Select(p => p.CourseId).ToArray() 
                                        });
    var result = groupedEnrollment.Where(g => 
                                         g.Courses.Length == courses.Length && 
                                         g.Courses.Intersect(courses).Count() == courses.Length);
person Nguyễn Văn Phong    schedule 07.01.2020
comment
вы вообще не используете таблицу Enrollments. Пробовал ваше решение с ошибками LINQ. Решение @Ivan отлично работает. Я принял его / ее ответ - person sarvpk; 20.09.2020