ORM?

Общие вопросы программирования, алгоритмы и т.п.

Модератор: Модераторы

ORM?

Сообщение Brainenjii » 24.01.2011 14:40:41

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

{ TMyObject }

TMyObject = Class
  Private
    fID: Integer;
    fOptions: TList;
    fSomeField1: String;
    fSomeField2: String;
  Public
    Property ID: Integer Read fID;
    Property SomeField1: String Read fSomeField1 Write fSomeField1;
    Property SomeField2: String Read fSomeField2 Write fSomeField2;
    Property Options: TList Read fOptions;
    Procedure Save;
    Constructor Create(Const aID: Integer);
    Constructor Create(Const aID: Integer;
      Const aSomeField1, aSomeField2: String);
    Destructor Destroy;
End;

...

Procedure TMyObject.Save;
Var
  i: Integer;
  aSQL: TSQLClass; // мой обработчик параметризированных запросов
Begin
  if ID = -1 Then
    aSQL := TSQLClass.Build('INSERT INTO TMYOBJECTS VALUES(' +
      'GEN_ID(GEN_TMYOBJECTS_ID, 1), :SOMEFIELD1, :SOMEFIELD2) ' +
      'RETURNING ID')
  Else
    Begin
      aSQL := TSQLClass.Build('UPDATE OR INSERT INTO TMYOBJECTS VALUES(' +
        ':ID, :SOMEFIELD_1, :SOMEFIELD2)');
      aSQL.AddParam('ID', ID);
    End;
  aSQL.AddParam('SOMEFIELD1', SomeField1);
  aSQL.AddParam('SOMEFIELD2', SomeField2);
  With TQueryClass.Create Do // второй уменьшатель кода для работы с БД
    Begin
      Post(aSQL);
      aSQL.Free;
      If ID = -1 Then fID := Integer('ID');
      Post('DELETE FROM TMYOBJECTS_OPTIONS WHERE TMYOBJECT = ' +
        IntToStr(ID));
// Удаляю всю информацию о свойствах и записываю актуальными сведениями, т.к.
//   на данный момент не ведётся учёт изменений Options (да и Properties тоже)
      aSQL := BSQLClass.Build('INSERT INTO TMYOBJECTS_OPTIONS VALUES(' +
        ':ID, :OPTION_ID)');
      aSQL.AddParam('ID', ID);
      For i := 0 To Options.Count - 1 Do
        Begin
          aSQL.SetParam('OPTION_ID', TMyOption(Options[i]).ID);
          Post(aSQL);
        End;
      Go;
      Free;
    End;
End;

Все таблицы имеют сходную структуру, повторяющую поля класса.
Крайне надоело писать один и тот же код. Хочется:
  • иметь инструмент для создания таблиц в базе по описанию класса (а так же изменению их при изменении класса)
  • чтобы объект запоминал, какие поля у него меняются и при вызове Save сохранял только их. Это же относится и к Options
  • всё выполнялось автоматически с минимумом ручного кода (самое главное ^_^)
Вроде бы и ничего сложного. Но как подступиться? ^_^
Аватара пользователя
Brainenjii
энтузиаст
 
Сообщения: 1351
Зарегистрирован: 10.05.2007 00:04:46

Re: ORM?

Сообщение Иван Шихалев » 24.01.2011 16:04:29

1. RTTI, поля объявлять как published.
Аватара пользователя
Иван Шихалев
энтузиаст
 
Сообщения: 1138
Зарегистрирован: 15.05.2006 11:26:13
Откуда: Екатеринбург

Re: ORM?

Сообщение *vmr » 24.01.2011 22:06:11

Было, уже проходили такое.

Параллельно зародилось два подхода. И они использовались некоторое время.

1. Для серилизации в БД использовали RTTI, через published поля. Базовый класс, с методами Save/Load. Пользовались недолго, но было удобно. В итоге отказались(и перешли на третий вариант) из-за непортируемости самого RTTI и еще каких-то причин с ним же связанных.
2. Использовали кодогенератор. По требуемому описанию, генерили юниты для доступа в БД, генерили весь SQL для создания и работы с нужной таблицей (одной таблице соответствовал один юнит).
Использовали долго. Точнее терпели долго (напомнило афоризм про мышей и кактус).
Недостатки:
* Такой подход годится только для простейших таблиц и взаимосвязей
* Для более сложных таблц и запросов была возможность в шаблоне метаописания классов(таблиц-классов) добавлять кастомный код, колторый вставлялся в генерируемые юниты, но на практике это все превращалось в кошмар — чтоб добавить примитивную, но кастомную реализацию чего-то, приходилось писать кучу кода в шаблоне.
* Как следствие ограниченная работа с транзакциями. Фактически было два вида транзакции: readonly и default. А зачем больше, коли это все предназначалось только для чтения и записи целиком строк в одной таблице? Вот тут и поджидала неприятность с кастомным кодом: ему этих типов транзакций было мало, а отходить от стандартной практики означало делать чудовищный оверхед по количеству написанного кода.
* Просто огромное кол-во сгенеренных методов и кода, который по сути являлся примитивным (прочитай да запиши, толко в различных комбинациях)
* Для добавления новых колонок в таблицу или их изменения приходилось перегенеривать весь код, а таблицу ALTER'ить вручную

Сейчас перешли на 3-й вариант, который до того в течении 5-ти лет обкатывался на сайд-проекте.
Есть контейнер иерархической структуры данных, похожей на XML. Только в отличие от последнего заточенный не на разметку, а на хранения данных: минимум потребляемой памяти, все данные лежат компактно и обычно влазят в кеш, возможна иерархия, каждый элемент контейнера имеет свой тип — наподобии варианта только компактней и быстрее.
Под этот контейнер, написан интерфейс работы с БД -- вместо TDataSet используется вышеописанный TDataList.
Ну и вся работа с БД ведется через этот контейнер и эту прослойку к самой БД.
Как показала практика, работа с рекордами как с классами и их свойствами нам нафиг не нужна, хотя это было и совсем не очевидно.
Место класса заменил TDataList, а пропертя — элементы хранящиеся в этом контейнере.
Именно за счет "плавающего" количества "свойств" и достигается подобная гибкость: один TDataList может прочитать любую таблицу, включая составную(View, Join-ы) или служебную (метаинформация о БД; в частности список колонок и их тип для любой таблицы в БД — по этой инфе можно построить ALTER TABLE для добавления(апгрейда версии) новых колонок).

Насчет производительности, то в теории это конечно медленее чем доступ напрямую к пропертям класса, но практика показывает что даже самая топорная реализация "в лоб" оказывается на порядок(!!!) быстрее чем использование RTTI. С применением хеширования строк все становится еще быстрее.

В итоге все выглядит где-то так (это все server-side):
Код: Выделить всё
var
  db: TDBConnection;
  data_src, data_dst: TDataList;
  sql: string;
begin
  ....
  db := nil;
  try
    // Лочим коннект к БД из пула (у нас многопоточный сервер, потому нужна гарантия что с этим соединением больше никто работать не будет)
    db := DBPool.AcquireConnection(ilRepeatableRead); // ilRepeatableRead - будет транзакцией по-дефолту на все дальнейшие действия
   
    sql := Build.SQL.Select('*')                             // А это мой уменьшитель кода ;)
              .From(TABLE_ORDERS, TABLE_CUSTOMERS)
              .JoinLeft(TABLE_CUSTOMERS, ORD_CUSTOMER_ID, CST_ID)
              .Limit(100).toString;

    // читаем все строки в data
    db.SelectRecords(data_src, sql);

    // перебераем все строки, что-нибуть по ним высчитываем
    for i:=0 to data_src.Last do
    begin
      row := data_src.Sections[i];
      // что-нибуть делаем с row (напр. какие-то расчеты)
    end;

    // заполняем поля будущего рекорда в БД
    data_dst.Int['ID'] := db.GenerateID(GENERATOR_NAME); // Методика для FireBird/Interbase - ID получаем перед вставкой, а не после как в MySQL
    data_dst.Int['field1'] := 123;
    data_dst.WideStr['field2'] := 'Something other';
    data_dst['field3'] := 'Value of field 1' ; // просто строка AnsiString
    data_dst.DateTime['field4'] := Now;

    // пишем результат в БД
    db.InsertRecord(data_dst, TABLE_RESULTS);
    // Коммит всего этого
    db.Commit;
  finally
    // Если коммит не случился, то это значит что произошло исключение. Тогда перед освобождением соединения, там делается автоматический RollBack.
    // А само исключение пускай всплывает выше — это не наша забота
    DBPool.ReleaseConnection(db);
  end;
end;
Аватара пользователя
*vmr
постоялец
 
Сообщения: 168
Зарегистрирован: 08.01.2007 01:46:07
Откуда: Киев

Re: ORM?

Сообщение hinst » 30.01.2011 20:14:48

который до того в течении 5-ти лет обкатывался на сайд-проекте.

неслабые у вас там проекты, однако :shock:
Аватара пользователя
hinst
энтузиаст
 
Сообщения: 781
Зарегистрирован: 12.04.2008 18:32:38

Re: ORM?

Сообщение Brainenjii » 30.01.2011 21:20:08

В общем, первоначальная идея претерпела множественные изменения, разгрузившие бедный компилятор, и оставив от себя только ощущение необходимости инструментария, вот только задачи на него возложены значительно большие. План-минимум:
  • ведение учета всех созданных мною классов во всех проектах;
  • создавать для каждого класса таблицу в БД, разбирая объявленные свойства класса, а так же изменять её при изменениях класса;
  • создавать болванку unit'а по описанию класса с реализациями общих для каждого класса методов.
План-максимум:
  • знать о местоположении репозитария системы контроля версий и автоматизировать процесс отслеживания изменения класса;
  • уметь тестировать unit'ы и автоматизированно прогонять тесты при каждом изменении.
Сейчас систему вижу примерно таким образом - загружаю в неё исходник, включающий описание класса. Она проводит анализ, выявляя все поля в секции Published (код, разумеется, писать таким образом, чтобы поля, которые нужно запоминать (а, например, не рассчитывать), записывать в секцию Published - надеюсь это не вызовет проблем производительности?). Разбирая эти свойства в зависимости от типа создаёт поля в таблице БД, одноименной с классом. Для списков (TThreadList/TList) создаёт добавочную таблицу, со связями один ко многим (тип придётся указывать в комментарии). Для свойств со мною созданным классом - целочисленное поле, с дальним ключом на таблицу класса. Ну и со всем этим добром уже не сложно будет написать болванку unit'а, расписав мне метод сохранения (скорее всего уже с сохранением только изменённых полей), ну и т.п. - все что я сейчас всякий раз делаю вручную ^_^
Вроде как не очень и сложно, только вот будет ли всё это работать... И удобно ли будет со всем этим работать...
Буду рад комментариям профессионалов ^_^
Аватара пользователя
Brainenjii
энтузиаст
 
Сообщения: 1351
Зарегистрирован: 10.05.2007 00:04:46

Re: ORM?

Сообщение ViTality » 01.02.2011 15:34:36

я не совсем уверен но вроде по теме...
Man Frames RAD Management Framework
ViTality
постоялец
 
Сообщения: 308
Зарегистрирован: 05.10.2007 15:12:02


Вернуться в Общее

Кто сейчас на конференции

Сейчас этот форум просматривают: нет зарегистрированных пользователей и гости: 20

Рейтинг@Mail.ru