Отменить изменения

IBX, FIBPlus, UIB, ADO, .Net и прочее-прочее-прочее, в общем все, что относится к созданию приложений, работающих с InterBase, Firebird и Yaffil - клиент-серверных, трехзвенных, консольных и т.п.

Модератор: kdv

santilaas
Сообщения: 51
Зарегистрирован: 27 авг 2005, 21:05

Отменить изменения

Сообщение santilaas » 09 апр 2007, 17:31

Привет всем!!!
Firebird 1.5.3, FibPlus 5.2
Вопрос такой:
- нужно, чтобы, если в блоке Try произошла ошибка, то данные никуда не записывались и выполнился код except, - у меня же даже при ошибке (скажем произошедшей после DM.DataSet.Post;) происходит запись в Датасет - вот код:

Код: Выделить всё

DM.DS_A_User2.Transaction.StartTransaction;
              try
                        SecurityService1.AddUser;
                        DM.DataSet.Post;
                        //еще кое-какие операции
                        DM.DataSet.Transaction.CommitRetaining;
                        FForm.ModalResult:= mrOK;
              except
                        Application.MessageBox(PChar('Ошибка при добавлении пользователя!' + #13 +
                        ' Обратитесь к администратору!'),'Ошибка приложения!',MB_OK+MB_ICONERROR);
                        DM.DataSet.Cancel;
                        DM.DataSet.Transaction.RollbackRetaining;
              end
- что здесь неправильно? мож чего не так с транзакциями (кстати, AutoCommit поставил в False)?
Заранее благодарен

WildSery
Заслуженный разработчик
Сообщения: 1738
Зарегистрирован: 05 июн 2006, 16:19

Сообщение WildSery » 09 апр 2007, 18:10

CommitRetaining - дурной тон.
Может ошибка в "FForm.ModalResult:= mrOK;"
Может коммит где-то был в "//еще кое-какие операции"
И, кстати, а где собственно DM.DataSet.Transaction.StartTransaction?

santilaas
Сообщения: 51
Зарегистрирован: 27 авг 2005, 21:05

Сообщение santilaas » 10 апр 2007, 18:10

при постановке вопроса я немного ошибся - вот так выглядит код:

Код: Выделить всё

DM.DataSet.Append;
DM.DataSet.Transaction.StartTransaction; 
try 
      SecurityService1.AddUser; 
      DM.DataSet.Post; 
      //еще кое-какие операции 
      DM.DataSet.Transaction.CommitRetaining; 
      FForm.ModalResult:= mrOK; 
except 
      Application.MessageBox(PChar('Ошибка при добавлении пользователя!' + #13 + 
      ' Обратитесь к администратору!'),'Ошибка приложения!',MB_OK+MB_ICONERROR); 
     DM.DataSet.Cancel; 
     DM.DataSet.Transaction.RollbackRetaining; 
end
- меня интересует, вообще мой подход правильный или нет?

kdv
Forum Admin
Сообщения: 6595
Зарегистрирован: 25 окт 2004, 18:07

Сообщение kdv » 10 апр 2007, 23:18

в корне неверно.
управление пользователями (Services API) никак не связано с транзакциями. То есть, вначале надо try добавить пользователя, а потом, если все прошло Ok, уже заниматься всякими Post и другими операциями с базой.
А то, ты небось еще не знаешь, что пользователи не в БД хранятся :)
Ну и про commitRetaining и тем более rollbackretaining уже сказали.

Dimitry Sibiryakov
Заслуженный разработчик
Сообщения: 1436
Зарегистрирован: 15 сен 2005, 09:05

Сообщение Dimitry Sibiryakov » 11 апр 2007, 07:51

А я еще добавлю про Append перед StrartTransaction. Не то, чтобы совсем уж "так делать нельзя", но... нехорошо это. Может обрушиться в самый неподходящий момент.

santilaas
Сообщения: 51
Зарегистрирован: 27 авг 2005, 21:05

Сообщение santilaas » 11 апр 2007, 18:30

В общем с транзакцией я все разрулил - надо было сделать так:
у DataSet-а ставим такие свойства:
"Options" - "poStartTransaction" = "False";
"AutoCommit" = "True"
и юзаем транзакцию на запись "Tr_Write"
Сам код такой:

Код: Выделить всё

DM.Tr_Write.StartTransaction;
DM.DataSet.Append; 
try 
      SecurityService1.AddUser; 
      DM.DataSet.Post; 
      //еще кое-какие операции 
      //если не произошло ошибок, подтверждаем транзакцию
      if DM.Tr_Write.InTransaction then DM.Tr_Write.Commit; 
      FForm.ModalResult:= mrOK; 
except 
      DM.DataSet.Cancel;
      //при ошибке откатываем транзакцию
      DM.Tr_Write.Rollback;
      Application.MessageBox(PChar('Ошибка при добавлении пользователя!' + #13 + 
      ' Обратитесь к администратору!'),'Ошибка приложения!',MB_OK+MB_ICONERROR);  
end
- по крайней мере так у меня все работает на ура.
а насчет замечания про Post я приму к сведению - спасибо за советы

kdv
Forum Admin
Сообщения: 6595
Зарегистрирован: 25 окт 2004, 18:07

Сообщение kdv » 12 апр 2007, 02:07

забодал. ну порнуха ведь полная. нетранзакционные действия смешаны с транзакционными. Например. AddUser проходит, а Post вызывает exception. И что у тебя по роллбэку отменится? НИЧЕГО. Юзер останется добавленным, а действие по Post все равно не прошло.
И с автокоммитом код получился еще более хреновым. Короче, чешуя...

CyberMax
Заслуженный разработчик
Сообщения: 638
Зарегистрирован: 31 янв 2006, 09:05

Сообщение CyberMax » 12 апр 2007, 04:32

Код: Выделить всё

      DM.DataSet.Post; 
      //еще кое-какие операции 
      //если не произошло ошибок, подтверждаем транзакцию
      if DM.Tr_Write.InTransaction then DM.Tr_Write.Commit; 
Неверно. У тебя AutoCommit = True, а это значит, что в Post пишущая транзакция будет автоматически подтверждена. Таким образом, в проверке DM.Tr_Write.InTransaction будет всегда False.

Код: Выделить всё

      DM.DataSet.Cancel;
      //при ошибке откатываем транзакцию
      DM.Tr_Write.Rollback;
Неверно. Cancel никакого отношения к WriteTransaction не имеет, так как происходит всего лишь удаление буфера под новую запись и возврат к состоянию dsBrowse. Поэтому RollBack бессмысленен.

Код: Выделить всё

      Application.MessageBox(PChar('Ошибка при добавлении пользователя!' + #13 + 
      ' Обратитесь к администратору!'),'Ошибка приложения!',MB_OK+MB_ICONERROR);  
Ты в курсе про MessageBox?

santilaas
Сообщения: 51
Зарегистрирован: 27 авг 2005, 21:05

Сообщение santilaas » 15 апр 2007, 19:17

В общем, по каждому пункту конкретно:
управление пользователями (Services API) никак не связано с транзакциями. То есть, вначале надо try добавить пользователя, а потом, если все прошло Ok, уже заниматься всякими Post и другими операциями с базой
AddUser проходит, а Post вызывает exception. И что у тебя по роллбэку отменится? НИЧЕГО. Юзер останется добавленным, а действие по Post все равно не прошло
- не спорю, но как тогда поступить в моем случае: я при нажатии на кнопку хочу, чтобы пользователь добавлялся и в таблицу в БД "security.fdb" (через SecurityService1.AddUser) и в мою таблицу "Users" в моей БД и чтобы этот юзер включался в определенную роль (grant 'role' to 'user'), притом, если происходит ошибка при добавлении - в любую таблицу - мне нужно, чтобы отменялась операция полностью (т.е. данные не добавились ни туда, ни сюда). Как это реализовать? - решение же должно быть - хотя опять же я понимаю, что grant я смогу сделать только после того, как добавится моя запись в таблицу БД "security.fdb" - как в таком случае выйти из ситуации?
И с автокоммитом код получился еще более хреновым
AutoCommit поставил в False
Ты в курсе про MessageBox?
а что с ним то не так?
У меня просьба - если не трудно - кто-нибудь, поправьте, пожалуйста, мой код, исходя из поставленной задачи - а то я чувствую я еще долго здесь буду зависать?! :roll:

CyberMax
Заслуженный разработчик
Сообщения: 638
Зарегистрирован: 31 янв 2006, 09:05

Сообщение CyberMax » 16 апр 2007, 01:57

santilaas писал(а):
Ты в курсе про MessageBox?
а что с ним то не так?
Он несколько неудобен. В модуле Dialogs есть хороший метод MessageDlg.

CyberMax
Заслуженный разработчик
Сообщения: 638
Зарегистрирован: 31 янв 2006, 09:05

Сообщение CyberMax » 16 апр 2007, 01:57

santilaas писал(а):
Ты в курсе про MessageBox?
а что с ним то не так?
Он несколько неудобен. В модуле Dialogs есть хороший методы для работы с боксами, например, MessageDlg.

santilaas
Сообщения: 51
Зарегистрирован: 27 авг 2005, 21:05

Сообщение santilaas » 16 апр 2007, 16:30

в добавление к моему предыдущему ответу - у меня как всегда масса вопросов - хочу добить эти транзакции раз и навсегда:
1) вопрос еще такой - если у меня 2 транзакции (Tr_Read и Tr_Write), то при ручном управлении транзакциями нужно писать так:

Код: Выделить всё

 DM.DataSet.UpdateTransaction.StartTransaction; //здесь для DataSet-а UpdateTransaction поставлена в Tr_Write
или так

Код: Выделить всё

DM.Tr_Write.StartTransaction?
или это без разницы?

2) и потом надо ли вообще активизировать транзакцию для записи Tr_Write сразу после коннекта к БД или просто делать
StartTransaction по мере необходимости? -

3) если поставлю для всех DataSet-ов свойство poStartTransaction в True (в инспекторе объектов Delphi), то можно ли будет уже при кодировании не писать
DataSet.Transaction.StartTransaction и будет ли это правильным?

4)

Код: Выделить всё

 if DM.DataSet.UpdateTransaction.InTransaction then
       DM.DataSet.UpdateTransaction.Commit;
- обязательно ли здесь делать проверку на InTransaction?

5) добавление записи заключаю в блок try ... except --- вопрос в том, надо или нет в except откатывать транзакцию или нет - какой из вариантов правильный:

Код: Выделить всё

DM.DataSet.Cancel;
или

Код: Выделить всё

if DM.DataSet.UpdateTransaction.InTransaction then
      DM.DataSet.UpdateTransaction.Rollback;
DM.DataSet.Cancel;
если не трудно ответьте, пожалуйста, на каждый вопрос

kdv
Forum Admin
Сообщения: 6595
Зарегистрирован: 25 окт 2004, 18:07

Сообщение kdv » 16 апр 2007, 16:42

1.
то при ручном управлении транзакциями нужно писать так
пофиг. собственно, здесь просто удобство и область действия Tr_write. Если она привязана только как updatetransaction, то необязательно ее звать по имени.

2.
и потом надо ли вообще активизировать транзакцию для записи Tr_Write сразу после коннекта к БД или просто делать
StartTransaction по мере необходимости?
в фибплюсах, если я правильно помню, update transaction по умолчанию стартует само, где быстро выполняется оператор изменения, и тут же вызывается commitretaining. который опциями надо заменить на commit. После чего можно updatetransaction вообще не трогать.

3. см. пункт 2 по поводу UpdateTransaction.
Для всех остальных транзакций это НЕПРАВИЛЬНО.

я так и не пойму, ты ibx.htm читал про транзакции?

4. см. пункт 2.

5. сначала операции с датасетом, потом с транзакцией, к которой он привязан.
Кроме того, Cancel у DataSet это альтернатива Post. Соответственно, зачем тут rollback, если dataset.post не вызывался?

CyberMax
Заслуженный разработчик
Сообщения: 638
Зарегистрирован: 31 янв 2006, 09:05

Сообщение CyberMax » 17 апр 2007, 02:43

kdv писал(а):2.
и потом надо ли вообще активизировать транзакцию для записи Tr_Write сразу после коннекта к БД или просто делать
StartTransaction по мере необходимости?
в фибплюсах, если я правильно помню, update transaction по умолчанию стартует само, где быстро выполняется оператор изменения, и тут же вызывается commitretaining. который опциями надо заменить на commit. После чего можно updatetransaction вообще не трогать.
Давайте разберем этот момент.

В TpFIBDataSet две транзакции:
1) Постоянно открытая читающая (Transaction).
2) Короткая ("быстрая") пишущая (UpdateTransaction).

Существует две настройки для управления стартом и подтверждением транзакций.
1. Опция poStartTransaction (в Options). Используется для читающей и пишущей транзакций. Предназначена для автозапуска (при необходимости) транзакции, если до того она была неактивна.

Пример: Transaction неактивна. Набор закрыт.
Вызываем pFIBDataSet.Open.
При poStartTransaction автоматически выполнится Transaction.StartTransaction и набор откроется, иначе будет возбуждено исключение о неактивной транзакции.

Пример: вызван оператор pFIBDataSet.Post. Так как изменения происходят в контексте пишущей транзакции, то она должна быть активна. При poStartTransaction UpdateTransaction будет автоматически запущена. При выключенной опции ее надо вручную стартовать.

2. Свойство AutoCommit. Относится только к UpdateTransaction. Эта транзакция может быть любой, в том числе и ReadOnly :). В последнем случае вы просто не сможете подтверждить изменения и будут возбуждаться соответствующие исключения.
Это свойство предназначено для автоматического вызова UpdateTransaction.Commit (не CommitRetaining) после каждого Post и Delete.

Есть еще одно свойство - RefreshTransaction, которое принимает два значения: tkReadTransaction и tkUpdateTransaction. Оно указывает, в какой транзакции делать обновления записи. Для чего это надо: допустим, вы работаете со справочником, в котором изменения подтверждаются только после нажатия кнопки "Сохранить". При tkReadTransaction после refresh'а записи получаешь версию записи до изменения (изменения "исчезнут"), а при tkUpdateTransaction - измененную.

Таким образом, при poStartTransaction и AutoCommit (а также при правильных настройках транзакций) получаем автоматически срабатывающий механизм обновления записей, который может работать бесконечно долго без ущерба для производительности.

P.S. Читающая транзакция обычно одна на все датасеты и стартует сразу же после подключения к базе, коммитясь при выходе из приложения.

kdv
Forum Admin
Сообщения: 6595
Зарегистрирован: 25 окт 2004, 18:07

Сообщение kdv » 17 апр 2007, 10:01

Эта транзакция может быть любой, в том числе и ReadOnly. В последнем случае вы просто не сможете подтверждить изменения и будут возбуждаться соответствующие исключения.
более точно - commit/rollback для readonly можно сделать всегда, вот любые операторы, изменяющие БД, не пройдут в такой транзакции.

CyberMax
Заслуженный разработчик
Сообщения: 638
Зарегистрирован: 31 янв 2006, 09:05

Сообщение CyberMax » 17 апр 2007, 10:55

kdv писал(а):
Эта транзакция может быть любой, в том числе и ReadOnly. В последнем случае вы просто не сможете подтверждить изменения и будут возбуждаться соответствующие исключения.
более точно - commit/rollback для readonly можно сделать всегда, вот любые операторы, изменяющие БД, не пройдут в такой транзакции.
Там "подтвердить изменения" были в смысле отпостить, а не откоммитить :). Виноват, неправильно высказался.

santilaas
Сообщения: 51
Зарегистрирован: 27 авг 2005, 21:05

Сообщение santilaas » 17 апр 2007, 17:53

смотрю интересная беседа получается - кстати, спасибо за развернутые ответы - многое мне стало понятным., но кое-что надо еще переварить, а потом комментировать. А ibx.htm я читал, но там не все описано, что мне нужно.
И все-таки:
управление пользователями (Services API) никак не связано с транзакциями. То есть, вначале надо try добавить пользователя, а потом, если все прошло Ok, уже заниматься всякими Post и другими операциями с базой
AddUser проходит, а Post вызывает exception. И что у тебя по роллбэку отменится? НИЧЕГО. Юзер останется добавленным, а действие по Post все равно не прошло

- не спорю, но как тогда поступить в моем случае: я при нажатии на кнопку хочу, чтобы пользователь добавлялся и в таблицу в БД "security.fdb" (через SecurityService1.AddUser) и в мою таблицу "Users" в моей БД и чтобы этот юзер включался в определенную роль (grant 'role' to 'user'), притом, если происходит ошибка при добавлении - в любую таблицу - мне нужно, чтобы отменялась операция полностью (т.е. данные не добавились ни туда, ни сюда). Как это реализовать? - решение же должно быть - хотя опять же я понимаю, что grant я смогу сделать только после того, как добавится моя запись в таблицу БД "security.fdb" - как в таком случае выйти из ситуации?
А MessageBox я юзаю, т.к. он заточен под русский язык - и в принципе мне он никаких проблем не доставляет , наоборот, очень даже неплохо

kdv
Forum Admin
Сообщения: 6595
Зарегистрирован: 25 окт 2004, 18:07

Сообщение kdv » 17 апр 2007, 23:09

надо еще переварить, а потом комментировать. А ibx.htm я читал, но там не все описано, что мне нужно.
интересно, что же там может быть "не описано"?
- не спорю, но как тогда поступить в моем случае: я при нажатии на кнопку хочу, чтобы пользователь добавлялся и в таблицу в БД "security.fdb" (через SecurityService1.AddUser) и в мою таблицу "Users" в моей БД и чтобы этот юзер включался в определенную роль (grant 'role' to 'user'), притом, если происходит ошибка при добавлении - в любую таблицу - мне нужно, чтобы отменялась операция полностью
надо понять сначала, что AddUser это нетранзакционный вызов. Т.е. управлять транзакциями при вызове функций Services API и некоторых операторов DDL невозможно (или они не подчиняются транзакциям или им невозможно сделать rollback). И следовательно, если AddUser выполнилось, а последующая взаимосвязанная операция не прошла, то надо
а) отменять взаимосвязанную операцию, допустим по rollback
б) удалять пользователя, если "транзакцией" является И AddUser, И эта "взаимосвязанная операция".

santilaas
Сообщения: 51
Зарегистрирован: 27 авг 2005, 21:05

Сообщение santilaas » 18 апр 2007, 18:22

и потом надо ли вообще активизировать транзакцию для записи Tr_Write сразу после коннекта к БД или просто делать
StartTransaction по мере необходимости?
- я ведь почему задал такой вопрос - я не могу понять одного: вроде как пишущую транзакцию нежелательно долго держать открытой,
а если я уже при коннекте её активизирую, то пока я доберусь до датасета, пройдет некоторое время (понимаю, что пример не ахти какой, но все же). Поэтому, ещё раз спрашиваю: в FIB-ах надо ли мне при коннекте активировать (стартовать) пишущую транзакцию или достаточно активировать её при открытии датасетов (poStartTransaction = True) - как правильнее?
update transaction по умолчанию стартует само, где быстро выполняется оператор изменения, и тут же вызывается commitretaining. который опциями надо заменить на commit. После чего можно updatetransaction вообще не трогать.
- где это такое и какими опциями можно изменить?
сначала операции с датасетом, потом с транзакцией, к которой он привязан. Кроме того, Cancel у DataSet это альтернатива Post. Соответственно, зачем тут rollback, если dataset.post не вызывался?
- ну дело в том, что как я понимаю, запущенную транзакцию (например, у меня poStartTransaction у DataSet-а стоит в True) надо
обязательно закрыть, т.е. либо подтвердить, либо откатить, или я не прав?
Есть еще одно свойство - RefreshTransaction, которое принимает два значения: tkReadTransaction и tkUpdateTransaction.
- это про RefreshTransactionKind?
Оно указывает, в какой транзакции делать обновления записи. Для чего это надо: допустим, вы работаете со справочником, в котором изменения подтверждаются только после нажатия кнопки "Сохранить". При tkReadTransaction после refresh'а записи получаешь версию записи до изменения (изменения "исчезнут"), а при tkUpdateTransaction - измененную.
-не совсем понял, зачем нам получать неизмененные данные?
(а также при правильных настройках транзакций)
- CyberMax, какие настройки являются правильными для тебя, на основе твоего опыта разработки приложений БД?
удалять пользователя, если "транзакцией" является И AddUser, И эта "взаимосвязанная операция".
- так скорее всего и сделаю.

kdv
Forum Admin
Сообщения: 6595
Зарегистрирован: 25 окт 2004, 18:07

Сообщение kdv » 18 апр 2007, 20:54

я не могу понять одного: вроде как пишущую транзакцию нежелательно долго держать открытой
ага, потому что она будет удерживать потенциальный мусор от сборки
Поэтому, ещё раз спрашиваю: в FIB-ах надо ли мне при коннекте активировать (стартовать) пишущую транзакцию или достаточно активировать её при открытии датасетов (poStartTransaction = True) - как правильнее?
я начинаю впадать в ступор. Какой нафиг старт транзакции при коннекте? ЗАЧЕМ? Зачем тебе ДЛИННЫЕ ПИШУЩИЕ ТРАНЗАКЦИИ? Ладно бы если бы они были нужны на самом деле. Зачем они тебе лично? Более того, тебе же показали пример, когда UpdateTransaction стартует и завершается в совершенно нужный момент, т.е. при Post ?

я не пойму - ты в транзакции вообще не врубаешься, или конкретно в управление ими в FIBPlus?

Если второе, то
http://www.devrace.com/ru/fibplus/articles/2291.php

Если первое, то статей на ibase.ru достаточно, в частности я уже наверное второй или третий раз даю посыл в
http://www.ibase.ru/devinfo/ibx.htm#tran_use

Ответить