Commit для изменений свойств объектов

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

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

Re: Commit для изменений свойств объектов

Сообщение Шурик Сетевой » 19.05.2011 13:59:14

Brainenjii писал(а):да, я действительно хочу (в конечном итоге) построить ORM с кешированием. Правда о разруливании конфликтов речи ещё нет ^_^


Но кеш, по определению, неслабый источник конфликтов. Так что меры надо принимать сразу, если уж решились использовать кеширование. А у вас вообще в задаче, что преобладает, чтение или запись? Просто при преобладании записи, затраты на поддержку валидности кеша заметно растут и может оказаться выгодным убрать кеширование.

По теме, лучше разнесите кэш и ORM.
Кэш - отдельная подсистема, как в ASP.NET, кеширует сериализуемые объекты любого типа. Доступ через глобальный статический класс Cache, реализующий интерфейс ICache. Свойство ICache.Items("key") - чтение кешируемого объекта по строковому ключу, ICache.Remove("key") - удаление из кеша. ICache.Add(key, valueObject) - кеширование объекта, плюс перегруженные методы Add с заданием таймаута кеширования или других параметров валидности кеша. ICache.Cleanup - очистка кеша от устаревших данных. Remove, Add должны быть строго потокобезопасными.

С кеширующим ORM больше вопросов, чем ответов. Но ORM должен уметь полностью или выборочно очищать кеш при записи в БД. Таким образом, кеш не станет источником проблем с целостностью данных.

vada писал(а):Просто Божественному Убийце лень читать книжки. Да и понятно. Книжка по EJB толстенная, страниц 600 с описанием классов. А свой шестиколесный велик с квадратными колесами хочется :) Да и пусть себе. Это нормально.

Просто тема очень серьезная, но интересная.
Шурик Сетевой
новенький
 
Сообщения: 11
Зарегистрирован: 05.03.2009 21:42:42

Re: Commit для изменений свойств объектов

Сообщение Brainenjii » 19.05.2011 15:00:12

Ну, если вспоминть, что любое обсуждение должно начинаться с определения предмета, то что понимается под конфликтами? ^_^ Я в это понятие вкладываю одновременное редактирование одного объекта несколькими потоками (в моём случае потоки равнозначно пользователю). Или, объектов, управляемых одним менеджером.
Так вот, о таком разруливании я пока даже не задумываюсь - реализовать блокировку на уровне менеджера - с оповещением об ошибке - дело элементарное.
При более широком понимании конфликтов (вообще всех возможных ошибок, возникших из-за Commit'a) - проблемы действительно есть и топик-то начат, чтобы спросить совета, как их решать ^_^
Как сейчас происходит (в общем случае) -
  • при запуске все объекты создаются, согласно сохранённым данным в БД, и заносятся в основной список (назовём его так ^_^).
  • При создании нового объекта - он добавляется не сразу в основной список, доступный всем, а в приватный список измененных объектов менеджера, проводящего изменения.
  • При изменении уже существующего объекта - он копируется и копия добавляется в тот же список. Основной, доступный всем, список остаётся без изменений.
  • При удалении - опять же создаётся копия, но помещается уже в приватный список удаляемых объектов. Основной список по-прежднему нетронут.
Вот, как понимаю до этого момента (если всё потокобезопасно ^_^), всё хорошо, каждый менеджер видит только свои изменения и, по идее, для каждого конкретного изменяемого объекта должен существовать только один менеджер, который может проводить изменения.
Во время Commit'а: основной список блокируется (ThreadList.LockList), новосозданные объекты сохраняются в базу и переносятся в основной список, изменённые объекты передают все свои изменения в свои оригиналы и обновляют базу, для удалённых объектов удаляются и оригиналы и удаляется из базы.

Какие сейчас я вижу проблемы:
  • 1. самая очевидная (и проблемная) ситуация: один управляемый объект (объект1) является полем другого объекта (объект2). Менеджер удалил управляемый объект и закоммитил удаление - объект1 уничтожен, но объект2 об этом не знает и при обращении к своему полю (где должен быть объект1) ловит Access Violation. В эту же проблему можно отнести и изменение. Решение которое я вижу - держать объект, в котором регистрируются все изменения, и механизм оповещения - при коммите все заинтересованные менеджеры резвенько подрываются проверять свои управляемые объекты на предмет ошибок, и обрабатывают их {вполне возможно что и через интерфейс ICache}. Но мне представляется этоне очень хорошим решением (особенно если хотеть избавиться от ручного набора кода для реализации ORM);
  • 2. самоорганизация - нужно постоянное помнить, что нельзя менять свойства объекта напрямую, а только через запрашивание копии изменяемого объекта. Можно попробовать решить через упаковывания все обращения к полям в Set и Get методы, с поднятием Exception, если при Set состояние объекта отлично от Commited. Но это приведёт к огромной массе ручного кода, которого-то я и хочу избежать ^_^ В мечтах - система навроде RoR'овских моделей с миграциями и автоматической кодогенерацией. Хотя здесь предупреждают, что кодогенерация - зло, я надеюсь это возможно обернуть всё так, чтобы было мило, изящно и удобно ^_^ ;
  • 3. общая хлипкость ^_^ Проблема вполне решаема, но есть ли смысл браться до решения проблемы 1...

Вот как-то так.. Буду рад услышать возможные решения проблем (ну, или неучтённые) ^_^ По JPA и JBI - не слишком ли сложно? Да и без RTTI не обойтись (неоднократно слышал отзывы о неоптимальности данной технологии)... На днях постараюсь почитать про эти технологии подробнее чем рассказано в обзорах.
З.Ы. книжки читать я люблю, но 600 страниц описаний классов - это не для людей ^_^
З.З.Ы. Lexx рулит ^_^
Аватара пользователя
Brainenjii
энтузиаст
 
Сообщения: 1351
Зарегистрирован: 10.05.2007 00:04:46

Re: Commit для изменений свойств объектов

Сообщение vada » 20.05.2011 14:58:49

Ух! Полная каша! :(
•при запуске все объекты создаются, согласно сохранённым данным в БД, и заносятся в основной список (назовём его так ^_^).

Из базы данные дергаются ТОЛЬКО по запросу конечного юзверя. Заранее все объекты грузить маразм.
•При создании нового объекта - он добавляется не сразу в основной список, доступный всем, а в приватный список измененных объектов менеджера, проводящего изменения.

При создании объекта он должен СРАЗУ комитеться в базу, кроме помещения в ваши списки. После соммита он становится достоянием общества.
•При изменении уже существующего объекта - он копируется и копия добавляется в тот же список. Основной, доступный всем, список остаётся без изменений.

Очень спорное утверждение. Оно означает что другие пользователи выполненного изменения не увидят, и некоторые, самые умные, зная что такие изменения нужно сделать, дружно начнут колупать один и тот-же объект. Вы голову сломаете решая какое изменения является самым главным и нужным изменением. Будите соммитеть все подряд? Юзвери прийдут и морду вам набью. Причем, будут правы.
Если привелигерованный, я подчеркиваю, ПРИВЕЛЕГИРОВАННЫЙ (ибо остальным этот объек RO) пользователь внес измениня в объект, он должен коммитеться, и после этого обновлятся объект который видят ВСЕ. После этого вы должны всем активным клиентам которые в данный момент вылупив глаза зырят на ваш объект, послать месагу на обновление видимой ими информации. Клиент это должен отработать, иначе будут возникать врпросы с угрозой разобраться с этим гребанным программистом. :)
•При удалении - опять же создаётся копия, но помещается уже в приватный список удаляемых объектов. Основной список по-прежднему нетронут.

С удалением это вообще отдельная песня! Если вы решили грохнуть запись, например, в справочнике, вам придется каскадно грохнуть и все записи которые имеют реляцию на эту запись. Подозреваю, что после этого, озверевшие юзвери прийдут всей толпой и оторвут вам все половые признаки. Причем, будут правы.
А если вы каскадно ничего не удалите (ну нет у вас в базе соответствующего тригера, и некому срыгнуть вам ексепшн), то вы нарушите целостность реляционной сруктуры. Проблем поимееете!!!!!!!
Я очень быстро пришел к выводу, что удалять из базы ничего нельзя. Просто запись помечается флажком в булевом поле что она удалена, и больше юзверям не показывается.
Представьте такую ситуацию на складе. Вам поставщик перестал поставлять товар с единицами измерения "коробка". Все! Не будет больше коробок никогда! Директор натопал ногами и накричал что он больше не хочет видеть в отчете кучу пустых листов с заголовкам "КОРОБКА". Самое простое решение - удалить такую единицу из справочника. Но при этом у вас обязаны удалиться все записи в которых встречались коробки. Как вы потом баланс по складу сведете без мата и битья ногами в углу склада программиста, я не представляю.
•1. самая очевидная (и проблемная) ситуация: один управляемый объект (объект1) является полем другого объекта (объект2). Менеджер удалил управляемый объект и закоммитил удаление - объект1 уничтожен, но объект2 об этом не знает и при обращении к своему полю (где должен быть объект1) ловит Access Violation. В эту же проблему можно отнести и изменение. Решение которое я вижу - держать объект, в котором регистрируются все изменения, и механизм оповещения - при коммите все заинтересованные менеджеры резвенько подрываются проверять свои управляемые объекты на предмет ошибок

Такого просто не должно происходить! Вопервых, тригер вам не должен дать удалить такую запись. Но если вы таки каскадно удалили запись, у вас и объект1 должен быть удален.
•2. самоорганизация - нужно постоянное помнить, что нельзя менять свойства объекта напрямую, а только через запрашивание копии изменяемого объекта. Можно попробовать решить через упаковывания все обращения к полям в Set и Get методы, с поднятием Exception, если при Set состояние объекта отлично от Commited. Но это приведёт к огромной массе ручного кода, которого-то я и хочу избежать ^_^ В мечтах - система навроде RoR'овских моделей с миграциями и автоматической кодогенерацией. Хотя здесь предупреждают, что кодогенерация - зло, я надеюсь это возможно обернуть всё так, чтобы было мило, изящно и удобно ^_^ ;

Вот это я вааще ни слова не понял :( Причем тут кодогенерация? Get да Set. Что может быть проще?
•3. общая хлипкость ^_^ Проблема вполне решаема, но есть ли смысл браться до решения проблемы 1...

Не беритесь. У вас серьезные проблемы в постановке задачи.
Аватара пользователя
vada
энтузиаст
 
Сообщения: 691
Зарегистрирован: 14.02.2006 13:43:17

Re: Commit для изменений свойств объектов

Сообщение Brainenjii » 20.05.2011 15:45:54

Из базы данные дергаются ТОЛЬКО по запросу конечного юзверя. Заранее все объекты грузить маразм.

Есть объект - справочник (к примеру). Он содержит 100500 значений.
Сам справочник создастся при старте. Из 100500 значений будут загружены только те, которые потребуются пользователю.
При создании объекта он должен СРАЗУ комитеться в базу, кроме помещения в ваши списки. После соммита он становится достоянием общества.

Возможно. Это не меняет сути - незакомиченный объект невидим для окружающих. Как ведёт себя объект до коммита (записывается он в базу с флагом UNCOMITTED/без флага COMMITED, или просто висит в памяти как временный) - зависит от реализации менеджера - наследника от BManagerClass.
...другие пользователи выполненного изменения не увидят, и некоторые, самые умные, зная что такие изменения нужно сделать, дружно начнут колупать один и тот-же объект.

Об одновременном колупании одного объекта несколькими пользователями - тоже уже говорилось во втором абзаце прошлого поста. Это не та проблема, которая действительно проблема ^_^ Решить её можно (начиная от блокировки (просто), заканчивая совместным редактированием (сложно)).
С удалением это вообще отдельная песня...

Тоже вполне очевидная вещь ^_^ Под удалением из базы я вкладывал вызов метода Delete у управляемого объекта, перекрытого в наследнике BObjectClass. Будет там DELETE FROM SOMETABLE WHERE ID = :ID, или UPDATE SOMETABLE SET DELETED = TRUE WHERE ID = :ID, или ещё что - опять же, зависит от наследника ^_^
Такого просто не должно происходить! Вопервых, тригер вам не должен дать удалить такую запись. Но если вы таки каскадно удалили запись, у вас и объект1 должен быть удален.

Вот, а это уже похоже на ещё один красивый вариант решения ^_^ Перенести флаг DELETED из БД в объектную модель. Т.е. при commit'e удаления не уничтожать объект, просто помечать его удалённым. Правда при при коммитах других объектов нужно будет проверять - а не перешли ли его поля в состояние "Удалён"... Ну да этого, видимо, вообще избежать невозможно...
Причем тут кодогенерация? Get да Set. Что может быть проще?

Я всё это начал потому что поднадоело писать из проекта в проект один и тот же код. Если мне придётся из проекта в проект писать
Код: Выделить всё
Procedure TMyClass.SetProperty(Const aValue: Variant);
Begin
  If bORMState = ormCommited Then Raise Exception.Create('Object should be uncommited');
End;

то проблема, будем считать, что не решена... Под кодогенерацией я полагал инструмент (пакет к IDE) для формирования наследников базовых классов и управляющих автоматически. Т.е. выбираю набор полей, их типов - и этот инструмент сгенерировал мне модуль с болванкой классов и DDL запрос к базе, создающий таблицу к этому классу. В общем, описывал здесь.
Не беритесь. У вас серьезные проблемы в постановке задачи.

Тоже верно... Попробую систематизировать в этом топике ^_^
Аватара пользователя
Brainenjii
энтузиаст
 
Сообщения: 1351
Зарегистрирован: 10.05.2007 00:04:46

Re: Commit для изменений свойств объектов

Сообщение vada » 20.05.2011 16:07:35

Это не та проблема, которая действительно проблема

Напрасно вы так думаете. Это очень серьезная проблема, и начинать решать ее надо с разбора бизнес-процессов.
Типа, кто чего в каком порядке делает, и какие события должны его действиям предшествовать. Зачастую, существующая в конторе модель не поддается автоматизации. Хоть в лепешку разбейся. Многие руководители этого не хотят понимать. Сруктуру приходится менять, иначе автоматизацию задачи просто не удастся реализовать. Бордак автоматизировать нельзя. Мало написать офигенную программу, нужно еще привести в порядок существующие бизнес-процессы. Распределить роли, распределить права доступа, и.т.д. (я писал об этом где-то в начале топика). Желательно добиться такой структуры ролей и правил доступа чтоб в любой момент времени у любого объекта базы данных был только один овнер. (Это очень непростая задача связанная с ломкой сложившейся структуры и даже иногда увольнением старых спитых работников) Иначе серьезного гемороя в релизации и в дальнейшей работе системы трудно избежать.
Мало того что придется писать "Войну и мир" кода, так он еще и работать быдет весьма криво.
Аватара пользователя
vada
энтузиаст
 
Сообщения: 691
Зарегистрирован: 14.02.2006 13:43:17

Re: Commit для изменений свойств объектов

Сообщение Padre_Mortius » 20.05.2011 22:29:09

Имхо, но удаления физических данных, тем более каскадных быть не должно. Вместо удаления у каждого объекта программы должен быть статус, который должен меняться.
Padre_Mortius
энтузиаст
 
Сообщения: 1265
Зарегистрирован: 29.05.2007 17:38:07
Откуда: Спб

Re: Commit для изменений свойств объектов

Сообщение Шурик Сетевой » 23.05.2011 12:08:08

Brainenjii писал(а):Я всё это начал потому что поднадоело писать из проекта в проект один и тот же код. Если мне придётся из проекта в проект писать

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

Выбрать подходящий паттерн ORM, скажем, если нравится RoR, то взяться за реализацию ActiveRecord. Потом написать скрипты под какой-нибудь шаблонизатор, наподобие T4 или MyGeneration для генерации исходников на лету. Я одно время им пользовался, весьма помогает.
Шурик Сетевой
новенький
 
Сообщения: 11
Зарегистрирован: 05.03.2009 21:42:42

Re: Commit для изменений свойств объектов

Сообщение Brainenjii » 22.07.2011 09:58:36

Продолжаем разговор ^_^
Пока пару объектов использовал только в 3 проектах - проблем с использованием обнаружено не было. Разве что ещё сильнее ощутил потребность в кодогенерации.
Например, есть у меня класс Task, у него несколько свойств, у неё есть свойства - описание (Caption) и срок (Term):
Код: Выделить всё
BTaskClass = Class(BObjectClass, IStatus)
  Private
    ...
    Function Save(aQuery: BQueryClass=Nil): Boolean; override;
    Procedure Load(Const aObject: BObjectClass); override;
    Constructor InitialBuild(Const aID: Integer; Const aCaption: String;
      Const aTerm: TDateTime);
  Public
    Property Caption: String Read bCaption Write bCaption;
    Property Term: TDateTime Read bTerm Write bTerm;
    Constructor Build(Const aID: Integer; Const aCaption: String;
      Const aTerm: TDateTime);
    Destructor Burn; Override;
End;
...
Function BTaskClass.Save(aQuery: BQueryClass): Boolean;
Var
  aSQL: BSQLClass;
  aNeedCommit: Boolean;
Begin
  Result := FALSE;
  aNeedCommit := aQuery = nil;
  If aNeedCommit Then aQuery := BQueryClass.Build(DB_SCHEDULE);
  If ID < 0 Then aSQL := BSQLClass.Build(SQL_TASK_INSERT, TRUE)
  Else
    Begin
      aSQL := BSQLClass.Build(SQL_TASK_UPDATE, TRUE);
      aSQL.AddParam('ID', ID);
    End;
  aSQL.AddParam('CAPTION', Caption);
  aSQL.AddParam('TASK_TERM', Term, 'dd.mm.yy');
  aQuery.Post(aSQL);
  If ID < 0 Then bID := aQuery.ByInteger('ID');
  If aNeedCommit Then
    Begin
      aQuery.Go;
      aQuery.Burn;
    End;
  Result := TRUE;
End;

Procedure BTaskClass.Load(Const aObject: BObjectClass);
Var
  aCount: Integer;
Begin
  bID := BTaskClass(aObject).ID;
  bCaption := BTaskClass(aObject).Caption;
  bTerm := BTaskClass(aObject).Term;
End;

Function BTaskClass.GetJSON: String;
Begin
  Result := '';
  AddJSItem(Result, 'jid', ID);
  AddJSItem(Result, 'Caption', Caption, TRUE);
  AddJSItem(Result, 'Term', Term, 'dd.mm.yy');
End;

Constructor BTaskClass.InitialBuild(Const aID: Integer;
  Const aCaption: String; Const aTerm: TDateTime);
Begin
  Inherited Build;
  Build(aID, aCaption, aTerm);
End;

Constructor BTaskClass.Build(Const aID: Integer;
  Const aCaption: String; Const aTerm: TDateTime);
Begin
  bID := aID;
  bCaption := aCaption;
  bTerm := aTerm;
End;

Что мне уже не нравится - дублирующиеся конструкторы: Build и InitialBuild. Это сделано, чтобы разделить объекты, созданные в процессе работы, и объекты, загруженные из базы. Inherited Build устанавливает bORMStatus в ormCommited. Отдельным параметром не передаю, поскольку хочу скрыть конструктор с предустановкой ORMStatus в приватном разделе.
Далее, осознал я, что мне жутко необходимо не только срок сдачи задачи, но и время постановки. Для этого мне придётся поправить объявление в InitialBuild, Build, реализации для Save, Load, InitialBuild, Build, GetJSON и метод AddTask у менеджера. И так для каждого нового свойства! Вот только времени на написание своего кодогенератора найти не могу >_< Т4 и MyGeneration, как понял, это только MS VS бирюлька? Или я ошибаюсь? Хотя в выходные, скорее всего, засяду за свой плагин к лазарю...
Но пост не об этом ^_^ Захотел я реализовать механизм Undo в своём новом проекте. И внезапно осознал, что сделать это будет довольно легко - у меня есть метод Commit у базового класса менеджера:
Код: Выделить всё
Procedure BManagerClass.Commit;
Var
  i, aIndex: Integer;
  aAllRight: Boolean;
  aQuery: BQueryClass;
  aDeleted, aNonComitted, aManaged: TList;
  aObject, aDeletedObject, aManagedObject: BObjectClass;
Begin
  If Not(bModified) Then Exit;
  aQuery := BQueryClass.Build(bDBIndex);
  aDeleted := bDeleted.LockList;
  aNonComitted := bNonCommited.LockList;
  aManaged := bManaged.LockList;

  // Attempt to process all deletions
  For i := 0 To aDeleted.Count - 1 Do
    Begin
      // If ID negative then skip - it should me remove at NonCommited check
      If BObjectClass(aDeleted[i]).ID > DEFAULT_ID Then
        aAllRight :=  BObjectClass(aDeleted[i]).Delete(aQuery);
      If Not(aAllRight) Then Break;
    End;
  // Attempt to process all NonCommited Objects
  For i := 0 To aNonComitted.Count - 1 Do
    If FindByID(bDeleted, BObjectClass(aNonComitted[i]).ID) = nil Then
      Begin
        aAllRight := BObjectClass(aNonComitted[i]).Save(aQuery);
        If Not(aAllRight) Then Break;
      End;
  If Not(aAllRight) Then Exit;
  aQuery.Go;
  aQuery.Burn;

  // Clean up NonCommited and Managed Lists for Deleted Items
  For i := 0 To aDeleted.Count - 1 Do
    Begin
      aDeletedObject := BObjectClass(aDeleted[i]);
      aNonComitted.Remove(aDeletedObject);
      aObject := FindByID(aNonComitted, aDeletedObject.ID);
      If Not(aObject = nil) Then
        Begin
          aNonComitted.Remove(aObject);
          aObject.Burn;
        End;
      aManaged.Remove(aDeletedObject);
      aObject := FindByID(aManaged, aDeletedObject.ID);
      If Not(aObject = nil) Then
        Begin
          aManaged.Remove(aObject);
          aObject.Burn;
        End;
    End;

  // Apply changes of NonCommited Items to Managed Items
  For i := 0 To aNonComitted.Count - 1 Do
    Begin
      aObject := BObjectClass(aNonComitted[i]);
      Case aObject.ORMState Of
        ormChanged:
          Begin
            aManagedObject := FindByID(aManaged, aObject.ID);
            If Not(aManagedObject = nil) Then aManagedObject.Load(aObject);
            aObject.Burn;
            ProcessCommit(aManagedObject, ormChanged);
          End;
        ormAdded:
          Begin
            aObject.bORMState := ormCommited;
            aManaged.Add(aObject);
            ProcessCommit(aObject, ormAdded);
          End;
      End;
    End;

  For i := 0 To aDeleted.Count - 1 Do
    BObjectClass(aDeleted[i]).Burn;

  aDeleted.Clear;
  aNonComitted.Clear;

  bManaged.UnlockList;
  bDeleted.UnlockList;
  bNonCommited.UnlockList;
  bModified := FALSE;
End;

Пока для всех своих пар классов, наследуюемых от BManagerClass и BObjectClass мне ни разу не приходилось его переписывать/перекрывать, я считаю это определённо победа :-D Но, если я дополню код
Код: Выделить всё
        ormChanged:
          Begin
            aManagedObject := FindByID(aManaged, aObject.ID);
            If Not(aManagedObject = nil) Then
              Begin
                aManagedObject.SaveForHistory; // <- bingo
                aManagedObject.Load(aObject);
              End;
            aObject.Burn;
            ProcessCommit(aManagedObject, ormChanged);
          End;

где SaveForHistory - повтор Save только в другую таблицу, то я получу информацию о всех полях, что были у объекта до Commit'a! А затем так же просто (загрузив с базы и сделав Load) смогу вернуть объект к предыдущему состоянию! Но как обычно я волнуюсь за реляции... Что я мог упустить в этой схеме?

P.S. почему я не хочу обычную ORM, вроде ActiveRecord... Как понимаю, в паскале это можно реализовать только с использованием RTTI. А использовать эту жуткую штуку я не умею и не хочу :-D Да и вариант с кодогенератором мне представляется гибче... Хотя возможно и ошибаюсь :-D
Аватара пользователя
Brainenjii
энтузиаст
 
Сообщения: 1351
Зарегистрирован: 10.05.2007 00:04:46

Re: Commit для изменений свойств объектов

Сообщение Brainenjii » 07.01.2012 13:45:21

Не прошло и полгода (а не, прошло ^_^), как я все-таки засел за свой кодогенератор. Получилось довольно-таки приятно (хотя ещё пилить и пилить):
Код: Выделить всё
Unit BTemplateUnit;

{$mode objfpc}{$H+}

Interface

Uses
  sysutils, Classes, BCommonUnit, BListsUnit, BObjectUnit, BTreeObjectUnit,
    BQueryUnit, BSQLUnit
//@UsesStart
  , BSubPatternUnit, BMultiPatternUnit
//@UsesStop
;

Type BTemplateClass = Class;

Type BTemplatesList = Specialize BList<BTemplateClass>;
Type BTemplatesThreadList =
  Specialize BThreadList<BTemplateClass>;

Type BTemplatesLinkedList = Specialize BLinkedList<BTemplateClass>;
Type BTemplatesLinkedThreadList =
  Specialize BLinkedThreadList<BTemplateClass>;

Type ITemplateInterface = Specialize IGenericInterface<BTemplateClass>;
Type BTemplateBlank = Specialize BTreeObject<BTemplateClass>;

Type

  { BTemplateClass }

  BTemplateClass = Class(BTemplateBlank, ITemplateInterface)
    Private
      //@PropertiesPrivateStart
      bCaption: String;
      bShortCaption: String;
      bAmount: Integer;
      bPrice: Double;
      bSubPattern: BSubPatternClass;
      bMultiPatterns: BMultiPatternsThreadList;
      //@PropertiesPrivateStop
      Procedure Load(Const aObject: BTemplateClass);
      Procedure InitializeObjects;
    Public
      //@PropertiesPublicStart
      Property Caption: String Read bCaption Write bCaption;
      Property ShortCaption: String Read bShortCaption Write bShortCaption;
      Property Amount: Integer Read bAmount Write bAmount;
      Property Price: Double Read bPrice Write bPrice;
      Property SubPattern: BSubPatternClass Read bSubPattern Write bSubPattern;
      Property MultiPatterns: BMultiPatternsThreadList Read bMultiPatterns Write bMultiPatterns;
      //@PropertiesPublicStop

      Function Save(Const aQuery: BQueryClass): Boolean;
      //@InitializeDeclareStart
      Procedure Initialize(Const aCaption: String; Const aShortCaption: String; Const aAmount: Integer; Const aPrice: Double; Const aSubPattern: BSubPatternClass);
      //@InitializeDeclareStop
      Destructor Burn; Override;
End;

Type BTemplatesManagerBlank = Specialize BTreeManagerGeneric<BTemplateClass>;

Type

  { BTemplatesManagerClass }

  BTemplatesManagerClass = Class(BTemplatesManagerBlank)
    Private
      bTemplates: BTemplatesThreadList; Static;
    Public
      Procedure Load;
      Constructor Build;
      Destructor Burn; Override;
End;

Implementation

Const
  SQL_ID = 'ID';
  SQL_DELETED_NOT = 'DELETED = 0';
  SQL_PARENT = 'PARENT';
  SQL_PRIOR = 'PRIOR';
  SQL_NEXT = 'NEXT';
  //@ConstStart
  SQL_NAME = 'TEMPLATE';
  SQL_TABLE_NAME = 'BTEMPLATECLASS';
  SQL_CAPTION = 'CAPTION';
  SQL_SHORT_CAPTION = 'SHORT_CAPTION';
  SQL_AMOUNT = 'AMOUNT';
  SQL_PRICE = 'PRICE';
  SQL_SUB_PATTERN = 'SUB_PATTERN';
  SQL_MULTI_PATTERN = 'MULTI_PATTERN';
  //@ConstStop

{ BTemplateClass }

Procedure BTemplateClass.Load(Const aObject: BTemplateClass);
Begin
  //@LoadStart
  Caption := aObject.Caption;
  ShortCaption := aObject.ShortCaption;
  Amount := aObject.Amount;
  Price := aObject.Price;
  SubPattern := aObject.SubPattern;
  MultiPatterns.Load(aObject.MultiPatterns.LockList);
  aObject.MultiPatterns.UnlockList;
  //@LoadStop
End;

Procedure BTemplateClass.InitializeObjects;
Begin
  If Not(bInitialized) Then
    Begin
      //@InitializeObjectsStart
      bMultiPatterns := BMultiPatternsThreadList.Build;
      //@InitializeObjectsStop
      bInitialized := TRUE;
    End;
End;

Function BTemplateClass.Save(Const aQuery: BQueryClass): Boolean;
Var
  aSQL: BSQLClass;
Begin
  aSQL := BSQLClass.Build;
  //@SaveStart
  If ID < 0 Then
    aSQL.Builder.Insert(SQL_TABLE_NAME).AddGenerator.AddParam(SQL_CAPTION).AddParam(SQL_SHORT_CAPTION).AddParam(SQL_AMOUNT).AddParam(SQL_PRICE).AddParam(SQL_SUB_PATTERN).AddParam(SQL_MULTI_PATTERN)
  Else
    Begin
      aSQL.Builder.Update(SQL_TABLE_NAME).AddParam(SQL_ID).AddParam(SQL_CAPTION).AddParam(SQL_SHORT_CAPTION).AddParam(SQL_AMOUNT).AddParam(SQL_PRICE).AddParam(SQL_SUB_PATTERN).AddParam(SQL_MULTI_PATTERN);
      aSQL.AddParam(SQL_ID, ID);
    End;

  aSQL.AddParam(SQL_CAPTION, Caption);
  aSQL.AddParam(SQL_SHORT_CAPTION, ShortCaption);
  aSQL.AddParam(SQL_AMOUNT, Amount);
  aSQL.AddParam(SQL_PRICE, Price);
  If Not(SubPattern = nil) Then aSQL.AddParam(SQL_SUB_PATTERN, SubPattern.ID)
  Else aSQL.AddParam(SQL_SUB_PATTERN, -1);
  //@SaveStop
  Result := aQuery.Post(aSQL);
  aSQL.Burn;
End;

//@InitializeStart
Procedure BTemplateClass.Initialize(Const aCaption: String; Const aShortCaption: String; Const aAmount: Integer; Const aPrice: Double; Const aSubPattern: BSubPatternClass);
Begin
  InitializeObjects;
  bCaption := aCaption;
  bShortCaption := aShortCaption;
  bAmount := aAmount;
  bPrice := aPrice;
  bSubPattern := aSubPattern;
End;
//@InitializeStop

Destructor BTemplateClass.Burn;
Begin
  //@BurnStart
  bMultiPatterns.Burn;
  //@BurnStop
End;

{ BTemplatesManagerClass }

Procedure BTemplatesManagerClass.Load;
Var
  aSQL: BSQLClass;
  aTemplate: BTemplateClass;
  //@ManagerLoadVarsStart
  aSubPatternsManager: BSubPatternsManagerClass;
  aMultiPatternsManager: BMultiPatternsManagerClass;
  //@ManagerLoadVarsStop
Begin
  EnterState(msLoading);
  aSQL := BSQLClass.Build;
  With BQueryClass.Build(bManager.bDBIndex) Do
    Begin
      aSQL.Builder.Select(SQL_TABLE_NAME).All.
        Where(SQL_DELETED_NOT).OrderBy(SQL_ID);
      Get(aSQL);
      While Not(EOF) Do
        Begin
          //@ManagerLoadStart
          AddObject(ByInteger(SQL_ID), ByInteger(SQL_PARENT),
            ByInteger(SQL_PRIOR), ByInteger(SQL_NEXT)).Initialize(ByString(SQL_CAPTION), ByString(SQL_SHORT_CAPTION), ByInteger(SQL_AMOUNT), ByDouble(SQL_PRICE), aSubPatternsManager.GetObject(ByInteger(SQL_SUB_PATTERN)));
          //@ManagerLoadStop
          Next;
        End;
      //@ManagerLoadPeasStart
      aSQL.Clear;
      aSQL.Builder.Select(SQL_TABLE_NAME + '_' + SQL_MULTI_PATTERN).All.
        OrderBy(SQL_NAME);
      Get(aSQL);
      While Not(EOF) Do
        Begin;
          aTemplate := GetObject(ByInteger(SQL_NAME));
          aTemplate.bMultiPatterns.Add(aMultiPatternsManager.GetObject(ByInteger(SQL_MULTI_PATTERN)));
          Next;
        End;
      //@ManagerLoadPeasStop
      Burn
    End;
  aSQL.Burn;
  LeaveState;
End;

Constructor BTemplatesManagerClass.Build;
Begin
  Inherited Build(bTemplates, 0);
End;

Destructor BTemplatesManagerClass.Burn;
Begin
  Inherited Burn;
End;

Initialization
Begin
  BTemplatesManagerClass.bTemplates := BTemplatesThreadList.Build;
End;

Finalization
Begin
  BTemplatesManagerClass.bTemplates.Purge;
  BTemplatesManagerClass.bTemplates.Burn;
End;

End.

Весь этот код был сгенерирован из
Код: Выделить всё

Procedure BCodeGenClass.Test;
Var
  aPea: BPeaClass;
Begin
  bPea := BPeaClass.Build(-1, 'Template');
  bPea.AddProperty(BPeaPropertyClass.Build('Caption', 'CAPTION', ppkString));
  bPea.AddProperty(BPeaPropertyClass.Build('ShortCaption', 'SHORT_CAPTION',
    ppkString));
  bPea.AddProperty(BPeaPropertyClass.Build('Amount', 'AMOUNT', ppkInteger));
  bPea.AddProperty(BPeaPropertyClass.Build('Price', 'PRICE', ppkDouble));
  aPea := BPeaClass.Build(-2, 'SubPattern');
  bPea.AddProperty(BPeaPropertyClass.Build('SubPattern', 'SUB_PATTERN', aPea));
  aPea := BPeaClass.Build(-3, 'MultiPattern');
  bPea.AddProperty(BPeaPropertyClass.Build('MultiPattern', 'MULTI_PATTERN',
    aPea, TRUE));
  InitializeModel;
  WriteLn(Model);
End;

Вот только как теперь воткнуть это дело в IDE >_< Не могу разобраться - как удалить, вставить и изменить строчку в редакторе, так чтобы лазарь не ругался на то что файлы на диске изменены? Вопрос уже задавал когда-то давно, но так и не продвинулся в решении :-(
Аватара пользователя
Brainenjii
энтузиаст
 
Сообщения: 1351
Зарегистрирован: 10.05.2007 00:04:46

Пред.

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

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

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

Рейтинг@Mail.ru