Информация об изменениях

Сообщение Re[11]: В России опять напишут новый объектно-ориентированны от 14.02.2018 7:32

Изменено 14.02.2018 8:08 Sinclair

Re[11]: В России опять напишут новый объектно-ориентированны
Здравствуйте, 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 раз.
Во-вторых, нам просто повезло на синтетической задаче — нам не потребовалось ничего изменять, т.к. оценки студентов уже известны. А вот если бы мы должны были заниматься чем-то типа комплектования заказа, каждый раз выбирая склад с максимальными остатками для каждого товара, то у нас после первой же модификции, проведённой в памяти.

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. Но это не так красиво и ООПшно, как возможность объявить вычисляемое свойство прямо на студенте.
Re[11]: В России опять напишут новый объектно-ориентированны
Здравствуйте, 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. Но это не так красиво и ООПшно, как возможность объявить вычисляемое свойство прямо на студенте.