Сравнение SubjectId
не является проблемой, поскольку c.SubjectId
является значением примитивного типа (я думаю, int
).Исключение жалуется на Equals(c.Students)
.c.Students
является константой (относительно запроса duplicates
), но не является примитивным типом.
Я бы также попытался провести сравнение в памяти, а не в базе данных.В любом случае вы загружаете все данные в память, когда запускаете свой первый цикл foreach
: он выполняет запрос allClasses
.Затем внутри цикла вы расширяете IQueryable allClasses
до IQueryable duplicates
, который затем выполняется во внутреннем цикле foreach
.Это один запрос к базе данных на элемент вашего внешнего цикла!Это может объяснить низкую производительность кода.
Поэтому я бы попытался выполнить содержимое первого foreach
в памяти.Для сравнения списка Students
необходимо сравнивать элемент за элементом, а не ссылки на коллекции учеников, поскольку они наверняка различны.
var sb = new StringBuilder();
using (var ctx = new Ctx())
{
ctx.CommandTimeout = 10000; // Perhaps not necessary anymore
var allClasses = ctx.Classes.Include("Students").OrderBy(o => o.Id)
.ToList(); // executes query, allClasses is now a List, not an IQueryable
// everything from here runs in memory
foreach (var c in allClasses)
{
var duplicates = allClasses.Where(
o => o.SubjectId == c.SubjectId &&
o.Id != c.Id &&
o.Students.OrderBy(s => s.Name).Select(s => s.Name)
.SequenceEqual(c.Students.OrderBy(s => s.Name).Select(s => s.Name)));
// duplicates is an IEnumerable, not an IQueryable
foreach (var d in duplicates)
sb.Append(d.LongName)
.Append(" is a duplicate of ")
.Append(c.LongName)
.Append("<br />");
}
}
lblResult.Text = sb.ToString();
Упорядочение последовательностей по имени необходимо, потому что,Я полагаю, SequenceEqual
сравнивает длину последовательности, а затем элемент 0 с элементом 0, затем элемент 1 с элементом 1 и т. Д.
Редактировать На ваш комментарий, что первый запрос все еще медленный.
Если у вас 1300 классов с 30 учениками в каждом, производительность стремительной загрузки (Include
) может пострадать отумножение данных, которые передаются между базой данных и клиентом.Это объясняется здесь: Сколько Включить я могу использовать для ObjectSet в EntityFramework для сохранения производительности? .Запрос является сложным, поскольку ему требуется JOIN
между классами и учащимися, а материализация объекта также сложна, поскольку EF должен отфильтровывать дублированные данные при создании объектов.
Альтернативный подход заключается в загрузке толькоклассы без учеников в первом запросе, а затем явно загружать учеников один за другим внутри цикла.Это выглядело бы так:
var sb = new StringBuilder();
using (var ctx = new Ctx())
{
ctx.CommandTimeout = 10000; // Perhaps not necessary anymore
var allClasses = ctx.Classes.OrderBy(o => o.Id).ToList(); // <- No Include!
foreach (var c in allClasses)
{
// "Explicite loading": This is a new roundtrip to the DB
ctx.LoadProperty(c, "Students");
}
foreach (var c in allClasses)
{
// ... same code as above
}
}
lblResult.Text = sb.ToString();
В этом примере вы бы имели 1 + 1300 запросов к базе данных вместо одного, но у вас не будет умножения данных, которое происходит при активной загрузке, а запросы проще(нет JOIN
между классами и учениками).
Подробная загрузка объясняется здесь:
Если вы работаете с LazyЗагрузка первой foreach
с помощью LoadProperty
не потребуется, поскольку коллекции Students
будут загружены при первом обращении к ней.Это должно привести к тем же 1300 дополнительным запросам, как явная загрузка.