Мини версия класса
От: corpse56  
Дата: 15.12.21 19:07
Оценка:
Здравствуйте!

Есть такой вопрос, даже и не знаю как сформулировать строку поиска в гугл, поэтомц решил спросить здесь.

Допустим есть класс, который представляет некую сущность из БД. Полей у класса очень много. Вопрос заключается в том, как поступить, когда требуется загрузить только определённые поля из БД? Для примера возьмём такой класс:

class Person
{
    int id;
    string name;
    string field1;
    ...
    string fieldN;
}


Для одних задач нужны все поля, для других достаточно двух-трёх. Если для большого количества объектов типа Person загружать все поля в задаче, где требуется только одно поле, то производительность сильно падает, да и не логично это. Вопрос состоит в том, как сделать так, чтобы можно было загрузить только определённые поля. У меня есть только один вариант. Примерно такой:
(псевдокод)
class Person
{
    public Person(string[] filedsToLoad)
    {
        foreach(string field in fields)
        {
             switch (field)
             {
                 case "id":
                     this.Id = this.loadId();
                     break;
                 case "name":
                     this.Name = this.loadName();
                     break;
                 //loadOtherFields
             }
        }
    }

}

Но он мне не нравится, потому что в этом случае будет происходить обращение к базе за каждым полем. Это, конечно, не все поля загружать, но получается очень много обращений к базе. Скорее всего, тоже не очень хорошо. Хочется сделать один запрос.

Ещё вариант — иметь разные классы Person под разные задачи, но это совсем не то.

Может быть стоит воспользоваться конструктором sql-запросов? Если да, то какой посоветуете? Если нет, то как можно ещё этого добиться?

Спасибо!
Re: Мини версия класса
От: vsb Казахстан  
Дата: 15.12.21 21:23
Оценка: +1 -2
В целом это не имеет особого смысла. Большинство полей из базы грузятся очень быстро и ты не заметишь разницы. Есть поля вроде Blob-ов, которые могут содержать десятки килобайтов данных и более, есть поля-ссылки на другие таблицы, которые в ORM часто представляются, как члены класса. Вот эти поля действительно нужно грузить не всегда.

Реализуется это через ленивую загрузку. Когда ты помечаешь некоторые тяжёлые поля, как загружающиеся лениво. При этом ты можешь в запросе указать, что эти поля нужно загрузить, тогда они загрузятся сразу. Если не пометишь, то они загрузятся при первом обращении (или выбросят ошибку, если сессия закрыта).

По крайней мере в Java/Hibernate так разумно делать. Думаю, в .NET будет примерно то же.
Отредактировано 15.12.2021 21:23 vsb . Предыдущая версия .
Re: Мини версия класса
От: hi_octane Беларусь  
Дата: 15.12.21 21:53
Оценка: 2 (1)
C>Допустим есть класс, который представляет некую сущность из БД. Полей у класса очень много. Вопрос заключается в том, как поступить, когда требуется загрузить только определённые поля из БД? Для примера возьмём такой класс:

В .NET для этого есть Linq2DB — грузишь только то что надо загрузить, обновляешь только то что надо поменять, и т.д.
Re: Мини версия класса
От: velkin Удмуртия http://blogs.rsdn.org/effective/
Дата: 15.12.21 23:13
Оценка: 2 (1)
Здравствуйте, corpse56, Вы писали:

C>Для одних задач нужны все поля, для других достаточно двух-трёх. Если для большого количества объектов типа Person загружать все поля в задаче, где требуется только одно поле, то производительность сильно падает, да и не логично это. Вопрос состоит в том, как сделать так, чтобы можно было загрузить только определённые поля.


Мне думается нужно разделить часть относящуюся к запросам такую как оператор SELECT и расположение объектов в базе данных. Современные фреймворки разбаловали людей обёртывая запросы SQL, хотя вопрос оптимального чтения и записи в базу данных остаётся открытым. К примеру, в Qt предоставленные модели загружают данные не оптимально, в .NET не знаю.

А теперь самое главное, выбор решения зависит от используемого типа загрузки. Я рекомендую почитать книгу "Архитектура корпоративных программных приложений" она же "Шаблоны корпоративных приложений" Мартина Фаулера. Краткое объяснение это не то, что можно изложить в коротком комментарии.

Глава 9. Представление бизнес логики
9.1 Сценарий транзакции (Transaction Script)
9.2 Модель предметной области (Domain Model)
9.3 Модуль таблицы (Тablе Module)
9.4 Слой служб (Selvice Layer)

10. Архитектурные типовые решения источников данных
10.1 Шлюз таблицы данных (Таble Data Gateway)
10.2 Шлюз записи данных (Row Data Gateway)
10.3 Активная запись (Active Record)
10.4 Преобразователь данных (Data Mapper)

11. Объектно-реляционные типовые решения поведения
11.1 Единица работы (Unit of Work)
11.2 Коллекция объектов (Identity Мар)
11.3 Загpузка по требованию (Lazy Load)


Самый простой способ работы это сценарий транзакции и на мой взгляд это важно для понимания баз данных. По сути это просто вызов каких-то запросов в одной транзакции. Как известно запись рядов таблицы в одной транзакции ускоряет запись в ту же базу SQLite на порядки и заодно не насилует диск.

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

Вообще-то изначально в базах данных была идея создать универсальный язык запросов, чтобы интерфейс доступа к ним был везде одинаков, но она не сработала. Так что теперь имеем нечто не очень универсальное и зависящее от диалекта SQL, зато тормозящее запросы и ответы.

Потому на вопрос должны ли мы распарсить полученные данные из базы данных и поместить их куда-либо, ответ да. Вот здесь на помощь приходит глава 10. Архитектурные типовые решения источников данных.

Я как-то пытался сравнить источники данных, но не додумал до конца.
************************************************************************
Сравнение источников данных
************************************************************************
Тип: Источники данных (управление данными dml)

*******************    *******************
* предм. область  * <= * шлюз таблицы    *
*******************    *******************
* данные          *    * источник+поиск *
*******************    *******************

*******************    *******************    *******************
* активная запись *    * шлюз записи     * <= * поиск           *
*******************    *******************    *******************
* данные          *    * данные          *    * выбрать          *
*******************    *******************    *******************
* источник+поиск  *    * источник        *
*******************    *******************
* логика          *
*******************

*******************    *******************
* предм. область  * <= * преобр. данных  *
*******************    *******************
* данные          *    * источник        *
*******************    *******************
* логика          *
*******************

************************************************
*                 * источник * данные * логика *
************************************************
* шлюз таблицы    *    x     *   -    *        *
* шлюз записи     *    x     *   x    *        *
* активная запись *    x     *   x    *   x    *
* преобр. данных  *    -     *   x    *   x    *
************************************************
примечание: x содержит внутри, - содержит снаружи

*****************************************************************
* шлюз таблицы * шлюз записи * активная запись * преобр. данных *
*****************************************************************
* источник     * источник    * источник        * данные         *
*              * данные      * данные          * логика         *
*              *             * логика          *                *
*****************************************************************
* данные       *             *                 * источник       *
*****************************************************************


C>Хочется сделать один запрос.


Вот это очень правильная идея, один запрос на чтение из базы, один запрос на запись в базу. Но вопрос в том, какой выбран способ для синхронизации объектов в оперативной памяти с базой данных чтобы сформировать этот запрос. Синхронизация данных построчно будет отличаться от синхронизации по отдельным полям. Если считывать данные по полям, то в оперативной памяти должна храниться информация об изменениях на каждое поле. Следовательно логика единицы работы изменится.

В общем я рекомендую читать книгу Мартина Фаулера. У него огромный опыт на эту тему и исследованы всевозможные комбинации синхронизации базы данных и объектов в оперативной памяти. Но тема это не простая, нужно приспособить стиль мышления к новым понятиям.

Альтернативный вариант использовать готовое решение, которое будет использовать какой-то из вышеуказанных способов. Многие так и делают, хотя уровень контроля на объектами в оперативной памяти и базой данных будет ниже.
Re[2]: Мини версия класса
От: corpse56  
Дата: 16.12.21 11:48
Оценка:
Здравствуйте, vsb, Вы писали:

vsb>Большинство полей из базы грузятся очень быстро и ты не заметишь разницы.


Тут мне следовало уточнить. Запрос на получение сущности из базы довольно быстрый, но данные не в нормальной форме (плюс в разных базах) и собираются не по полям, а по записям (одна запись — одно поле). С быстродействием выполнения запросов всё в порядке. Проседать по времени начинает процедура распарсивания этих данных на объекты бизнеслогики. Если мне достаточно 2-3 (а не все 200) поля для одной задачи, то чтобы выиграть во времени, можно их только и парсить. Ну допустим, что логика распарсивания только выбранных полей есть. Но как сделать так, чтобы избежать множественных обращений к БД? Вот понадобилось мне 100 записей. На данный момент я их получаю по такому сценарию:
1. Отбор Id сущностей по заданным условиям. получаем массив Id.
2. Получение непосредственно сущностей в цикле по одной со всеми полями.


Получается, что нужно изменить сценарий на такой:
1. Отбор Id сущностей по заданным условиям. получаем массив Id.
2. Составление запроса на получение всех полей всех сущностей
3. Распарсивание этого большого объёма полученных данных с учётом того, какие поля нужны.

Вроде бы звучит логично и должно выигрывать по времени. Поправтье меня пожалуйста, если я заблуждаюсь.


vsb>Реализуется это через ленивую загрузку.

спасибо за совет с тяжёлыми полями.
Re[2]: Мини версия класса
От: corpse56  
Дата: 16.12.21 12:02
Оценка:
Здравствуйте, hi_octane, Вы писали:

C>>Допустим есть класс, который представляет некую сущность из БД. Полей у класса очень много. Вопрос заключается в том, как поступить, когда требуется загрузить только определённые поля из БД? Для примера возьмём такой класс:


_>В .NET для этого есть Linq2DB — грузишь только то что надо загрузить, обновляешь только то что надо поменять, и т.д.

Спасибо.

Тут надо посмотреть эту библиотеку, поизучать. Я так понимаю это более высокоуровневая надстройка для EF? Предлагает, при linq запросе создавать динамический объект, где можно не указывать все поля?
Например, имеется класс:
class Person
{
    int id;
    string name;
    ing age;
}

и если мы хотим не весь объект, то при linq-запросе создавать объект анонимного типа?, что-то вроде этого:

var items = from з in зукыщты 
            select new
            { 
                id = u.id, 
                age = u.Age 
            };


Если я правильно понял, то это мне поможет. спасибо.

ps. А можно бегло пояснить, чем эта библиотека лучше EF?
спасибо.
Re[2]: Мини версия класса
От: Ночной Смотрящий Россия  
Дата: 16.12.21 13:50
Оценка: +1
Здравствуйте, vsb, Вы писали:

vsb>В целом это не имеет особого смысла. Большинство полей из базы грузятся очень быстро и ты не заметишь разницы.


Наводящие вопросы:
1) Может ли быть дедлок на операции select?
2) Для чего нужны index includes?

vsb>Реализуется это через ленивую загрузку.


После чего кто то берет рекордсет размером, скажем, в 100К записей, и ... Отличный совет!

vsb>По крайней мере в Java/Hibernate так разумно делать. Думаю, в .NET будет примерно то же.


Слава богу там получше средства доступны, позволяющие не писать в запросах select *. Например анонимные типы и рекорды.
... << RSDN@Home 1.3.17 alpha 5 rev. 62>>
Re[3]: Мини версия класса
От: Ночной Смотрящий Россия  
Дата: 16.12.21 13:50
Оценка: +4
Здравствуйте, corpse56, Вы писали:

C>Я так понимаю это более высокоуровневая надстройка для EF?


Нет. Вообще никак не связана с EF.

C>ps. А можно бегло пояснить, чем эта библиотека лучше EF?


Бегло — всем
... << RSDN@Home 1.3.17 alpha 5 rev. 62>>
Re[4]: Мини версия класса
От: Sinclair Россия https://github.com/evilguest/
Дата: 16.12.21 14:23
Оценка:
Здравствуйте, Ночной Смотрящий, Вы писали:

НС>Бегло — всем

С языка снял!
Уйдемте отсюда, Румата! У вас слишком богатые погреба.
Re[3]: Мини версия класса
От: hi_octane Беларусь  
Дата: 16.12.21 15:21
Оценка: 1 (1)
C>Тут надо посмотреть эту библиотеку, поизучать. Я так понимаю это более высокоуровневая надстройка для EF?
Это вообще не EF, а переосмысление linq2sql — библиотеки без которой самого linq не появилось бы. Взята концептуально правильная идея, и сделана с нуля прямыми руками и соображающими головами.

C>Предлагает, при linq запросе создавать динамический объект, где можно не указывать все поля?

Ну как-то так, да. Больше чем в доках написано я на форуме не скажу.

C>ps. А можно бегло пояснить, чем эта библиотека лучше EF?

Бегло — всем
EF это уродец который родился чёрт знает когда директивой менеджера "у Java есть ORM Hibernate, а про .NET пишут что мы отсталые потому что ORM нет. Срочно исправить!". 15 лет назад создатели замечтали что будут ORM-объекты биндить прямо к контролам WinForms/WPF (MVVM ещё был не канон). Посмотреть на уже существовавшие ORM для .NET, включая code-first типа DevExpress XPO, ниасилили.

EF сойдёт для самых простых сценариев в руках джуна с трёхмесячными курсами за плечами. Ну или если тебе нужно запилить что-то простое и маленькое, типа там "реестр школьной библиотеки". Сложное тоже можно сделать на EF, но вместо работы над задачей будешь превозмогать архитектурные решения косяки EF. Твоя проблема "как бы это не грузить слишком много" — первый шаг на практически бесконечном пути самурая превозможения.
Re: Мини версия класса
От: scf  
Дата: 17.12.21 07:29
Оценка: 6 (2)
Здравствуйте, corpse56, Вы писали:

C>Допустим есть класс, который представляет некую сущность из БД. Полей у класса очень много. Вопрос заключается в том, как поступить, когда требуется загрузить только определённые поля из БД?


Декомпозировать класс Person и работать с нужным подмножеством
class Person {
  PersonDetails details;
  PersonAddress address;
  PersonPassport passport;
}

И загружать только нужные объекты а-ля getPersonAddressByPersonId, в идеале, никогда не запрашивая Person целиком.

Для совсем уж специфичных случаев можно прикрутить что-то вроде GraphQL, его идея в том, что пользователь запрашивает список полей, коротый ему нужен.
Re: Мини версия класса
От: Sharov Россия  
Дата: 20.12.21 17:12
Оценка: 1 (1)
Здравствуйте, corpse56, Вы писали:

C>Ещё вариант — иметь разные классы Person под разные задачи, но это совсем не то.


Почему нет, тем более если не все поля нужны, а под каждый случай нужны определенные поля?
Можно было бы использовать https://automapper.org/, например.
К тому же с тз бд, что прочитали отдельные поля-колонки, что всю строку -- особой разницы вообще нет.
Это же будет последовательное чтение. Я бы создал бы необходимые наследники от класса Person, у которого
были бы все поля, а наследники имели бы соотв. специфичные поля.
Кодом людям нужно помогать!
 
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.