Здравствуйте, Terix, Вы писали:
T>Ну, какого надо, такого и будет. Предлагаю StudentAverage{Student student, Integer avg}
То есть мы обязаны изобретать целый отдельный класс на каждый результат проекции/группировки. Это как бы тупик.
В Linq для этого есть анонимные типы.
T>В памяти приложения. Если ты сделаешь не lazy load, они тоже будут лежать в памяти приложения. lazy load тут ничего не меняет.
Это и есть кэш — потому, что повторное обращение к "сущности", которая понадобилась ещё раз, обязано вернуть тот же экземпляр. Иначе будет совсем плохо.
T>Это ты о том, что можно сделать один sql запрос, который все операции проведёт не выходя за пределы СУБД, а можно слать несколько отдельных запросов из приложения? И, так как ORM не поддерживает ничего, кроме базового sql — с помощью ORM такого сделать нельзя?
Обычные ORM вообще ничего не поддерживают. Там каждая модификация — это отдельное
update student set lastname = ? where id = ?.
T>Мне кажется мы как-то по разному используем термин lazy load. Вот смотри, допустим, мы вытащили запросом студентов из первого пункта. Плюс мы знаем, что CourseMark загружаются лениво. Мы у половины студентов посмотрели оценки за курсы, а у половины — нет. Теперь у нас у половины студентов CourseMark есть в памяти приложения, а у половины — нет. А дальше мы отсылаем запрос с каунтом по всем студентам. Запрос пойдёт мимо приложения, сразу в БД. Подсчитает результат и вернёт его нам. То, что по факту у половины студентов CourseMark не загружен ни на что не повлияет.
Ну, во-первых, это уже повлияло на то, что у нас вместо одного запроса, который бы всосал студентов вместе с оценками, уехало N+1 запросов. Что сразу же уронило производительность примерно в N раз.
Во-вторых, нам просто повезло на синтетической задаче — нам не потребовалось ничего изменять, т.к. оценки студентов уже известны. А вот если бы мы должны были заниматься чем-то типа комплектования заказа, каждый раз выбирая склад с максимальными остатками для каждого товара, то у нас после первой же модификции, проведённой в памяти, результаты "запроса из базы" станут нерелевантными. И придётся по честному протаскивать все вот эти вот остатки на складах через тот же lazy-load кэш.
T>А вот, если ты напишешь код типа
T>T>foreach(student in students) {
T> foreach (course in student.courseMarks){
T> sum += course.mark;
T> }
T>}
T>
T>то нарвёшься на проблему N + 1. Из-за lazy load.
Надо понимать, что именно такой код не пишут почти никогда. Мы ж программисты — мы делаем Инкапсуляцию! Нужно среднее — добавляем студенту вычисляемое свойство AverageMark, с геттером, который бежит по коллекции StudentMarks.
И биндим к гую вот эту вот коллекцию студентов, где в колонке 1 будет LastName, а в колонке 2 — AverageMark.
Всё красиво, абстрактно, гибко, тестируемо и пр. Вот только под капотом у нас N+1, который с первого взгляда и не увидишь. Leaky, мать её, abstraction.
А использование прямого SQL — это как раз отказ от всей инкапсуляции.
В SQL Server я могу определить view, в котором будет вычисляемое поле AverageMark. И оно будет извлекаться за 1 стейтмент; при этом если я делаю запрос к этому view, в котором проекция отрезает AverageMark, то оптимизатор даже лезть в табличку StudentMarks не будет. Не будет ни Join, ни Aggregate.
Это — роскошь, недоступная традиционным ORM. Вот к чему надо стремиться.
в Linq изобразить что-то подобное довольно-таки тяжело, но можно. Ну, то есть можно сварганить прямой аналог — IQueryable<T> StudentsWithMarks. Но это не так красиво и ООПшно, как возможность объявить вычисляемое свойство прямо на студенте.