Mikhail писал(а): - Назначение поля GeneratorField, зачем оно нужно?
GenerartorField - используется для получения первичного ключа на клиенте. В своем примере я показал как обойтись без GenerartorField, воспользовавшись возможностями сервера используя выражение RERURNING для передачи первичного ключа клиенту. В свойстве Generator указывается имя генератора (последовательности). В свойстве ApplyOnEvent - оно задает когда компонента будет запрашивать значение ключа для новой записи. Если значение gaeOnNewRecord - то GenerartorField запросит значения с сервера в момент добавления новой записи, недостаток - если вы отмените запись - значение пропадет. Если gaeOnPostRecord - значение первичного ключа будет запрошено в момент Post новой записи, для того чтобы GeneratorField работал, все свойства GenerartorField должны быть заполнены. У GenerartorField есть ещё одно назначение - вернее у его свойство Field (где указывается имя поля первичного ключа) - оно используется для FullRefresh и OrderFields, чтобы курсор всегда оставался на выбранной записи и не убегал в начало после обновления всего набора данных. Даже если вы захотите использовать RETURNING в SQL запросах для получения первичного ключа и не будете использовать GenerartorField, его свойство Field все равно лучше заполнить, указав имя поля первичного ключа, т.к. может понадобиться для FullRefresh или OrderFields.
Mikhail писал(а): - как лучше сделать master-detail, использовать свойство datasource TIBDataset, менять запросы "налету"?
Лучше использовать свойство DataSource, механизм master-detail существенно переработан и жизнь облегчает значительно. Обратите внимание на DetailCondition - используется для управления detail в связке с мастер. В нем если включен dcForceOpenClose - открывает detail автоматически при открытии master и автоматически закрывает его при закрытии master. Если включен dcForceMasterRefresh - вызывает у master.Refresh после detail.Post или detail.Delete. Если включен dcForceMasterPost - то в момент detail.BeforeInsert или detail.BeforeEdit проверяет, если master.State in [dsInsert, dsEdit] то делает ему master.Post.
Mikhail писал(а): - можно ли завести свои переменные (параметры) в запросе и менять их из Lazarus?
Не совсем понял вопрос.
- Код: Выделить всё
IBDataSet.SelectSQL.Text := 'SELECT FROM MYTABLE WHERE MYFIELD = :MYPARAM';
IBDataSet.PN('MYPARAM').AsString := Value;
IBDataSet.Open;
....
IBDataSet.Close;
IBDataSet.PN('MYPARAM').AsString := NewValue;
IBDataSet.Open;
Mikhail писал(а): - компоненты вроде TDBEdit закрывают транзакцию на запись сразу же после потери фокуса? А читающая транзакция транзакция когда закрывается, в конце работы программы? TDataSet.Edit открывает транзакцию на запись?
- лучше использовать компоненты TDBEditXXX или использовать обычные и добавлять все результаты в следующем коде
trWrite.StartTransaction; // это нужно?
DB.Edit;
Db.FieldByName('name').AsString:='skdjfksdjl';
...
Db.Post;
trWrite.Commit;
[/code]
Лучше конечно использовать компоненты для работы с данными.
По поводу транзакций.
В тестовом приложении вы можете увидеть 2 TIBTransaction - IBRead и IBWrite.
IBRead - читающая транзакция, ей заданы параметры:
- Код: Выделить всё
read
read_commited
rec_varsion
nowait
С такими параметрами транзакция стартует в режиме только чтение, не создает версии записей, соответственно не оказывает ни какого влияния на работу сервера и поэтому может жить сколь угодно долго, месяцами, возможно годами. Сделайте такую-же в своем приложении и все что нужно читать, читайте через неё, т.е. у всех TIBDataSet.Transaction должно быть IBRead. У меня эта транзакция всегда открыта, я стартую её руками после подключения к базе данных, и завершаю руками при закрытии приложения. В противном случае, если читающую транзакцию не держать всё время открытой, это буде приводить к тому, что если понадобится повторно открыть Select запрос, который уже ранее открывался, то у него буде всё время вызываться метод Prepare перед каждым открытием запроса Select, что приведет к сбросу списка параметров и их значения каждый раз нужно будет устанавливать по новой.
Пример:
- Код: Выделить всё
IBDataSet.PN('GOD').AsInteger := 2015;
IBDataSet.Open;
IBDataSet.Close;
//Если после Close читающая транзакция остается открытой, то параметр GOD ни куда не денется
//Можно смело открывать запрос повторно, значение 2015 на месте
IBDataSet.Open;
- Код: Выделить всё
IBDataSet.PN('GOD').AsInteger := 2015;
IBDataSet.Open;
IBDataSet.Close;
//Если после Close читающая транзакция закрылась, то значение параметра GOD тоже ликвидируется
//Поэтому потребуется
IBDataSet.PN('GOD').AsInteger := 2015;
IBDataSet.Open;
IBWrite - пишущая транзакция, ей заданы параметры:
- Код: Выделить всё
write
wait
no_rec_version
read_commited
lock_timeout = 10
Я использую такую транзакцию для всех коротких пишущих запросов, для TIBDataSet, TIBStoredProc, TIBQuery с опцией AutoCommit := True. Т.е. для всех TIBDataSet в своем приложении, у которых AutoCommit = True (включено по умолчанию) назначьте в свойстве UpdateTransaction - IBWrite. Пишущая транзакция будет сама стартовать и завершаться во время метода Post или Delete. Пока вы редактируете запись, ни чего не происходит. Когда вы вызываете метод Post или Delete, стартует транзакция, данные пересылаются на сервер, транзакция подтверждается.
Смысл работы в рамках 2х транзакций в том, чтобы снизить "затыки" в многопользовательской работе и снизить возможность появления deadlock. Эти параметры позволят "писателям" не блокировать "читателей", в случае если "писатель" нарвется на запись заблокированную другим "писателем", сработает режим ожидания, пока запись не освободится и вы избежите ошибку блокировки. Короткие пишущие транзакции выполняются быстро, обычно пользователи даже не догадываются что там какие-то конфликты транзакций происходят.
Частные случаи.
Иногда нужно в рамках одной транзакции записать блок записей в одну или несколько таблиц, и в случае ошибки, для сохранения логической целостности данных, откатить транзакцию, вернув всё в исходное состояние. Для таких случаев, стоит завести отдельную пишущую TIBTransaction. У всех компонент, которые будут её использовать поставить AutoCommit = False. Запускать транзакцию и подтверждать (откатывать) её нужно будет вручную.
- Код: Выделить всё
IBDataSet1.UpdateTransaction := DocWriteTransaction;
IBDataSet2.UpdateTransaction := DocWriteTransaction;
MyProc.Transaction := DocWriteTransaction;
IBDataSet1.Autocommit := False;
IBDataSet2.Autocommit := False;
MyProc.AutoCommit := False;
DocWriteTransaction.StartTransaction;
try
IBDataSet1.Append;
....................
IBDataSet1.Post;
IBDataSet2.Append;
...............
IBDataSet2.Post;
IBDataSet2.Append;
...............
IBDataSet2.Post;
IBDataSet2.Append;
...............
IBDataSet2.Post;
MyProc.ExecProc;
DocWriteTransaction.commit;
finally
//Если на этом этапе DocWriteTransaction активна, значит произошла ошибка, откатываем изменения.
if DocWriteTransaction.InTransaction then DocWriteTransaction.RollBack;
end;
С транзакциями нужно быть очень осторожным, чтобы избежать застревание Oldest Active Transaction (OAT), описанный подход поможет в этом, я постарался сделать так чтобы избежать застревание OAT. В любом случае почаще заглядывайте в монитор транзакций, что-бы увидеть не создают ли какие-либо операции проблем с OAT.