2014-10-03 3 views
1

Я использую MySQL, и я знаю, что вложенное соединение не разрешено - используйте «save points» для этого, но я хотел бы создать более общий код, который также можно использовать с другими СУБД.Как правильно начать, работать и завершить транзакцию?

Итак, я хотел бы знать, как правильно начать, работать и завершить транзакцию в коде ниже?

После ExampleDAO.Save() функция может быть использована внутри другой функции, например OtherExampleDAO.Save().

Строки с верификацией if Assigned(dbTransaction) then всегда возвращают true, поэтому как правильно проверить, был ли создан экземпляр dbTransaction?

function TExampleDAO.Save(const Example: TExample): Boolean; 
var 
    dbxTransaction: TDBXTransaction; 
begin 
    if Assigned(Example) then // prevents invalid object, like ExampleDAO.Save(nil); 
    begin 
    try 
     if (_connection.TransactionsSupported) AND 
     ((not _connection.InTransaction) OR (_connection.MultipleTransactionsSupported)) then 
     begin 
     dbxTransaction := _connection.BeginTransaction(TDBXIsolations.ReadCommitted); 
     end; 

     try 
     // example 
     _sqlQuery.Close; 
     _sqlQuery.SQL.Clear; 
     _sqlQuery.SQL.Add('INSERT INTO example(a, b) ' 
         + 'VALUES(:a, :b)'); 
     _sqlQuery.ParamByName('a').AsAnsiString := Example.A; 
     _sqlQuery.ParamByName('b').AsDateTime := Example.B; 
     _sqlQuery.ExecSQL(False); 

     // example info 
     _sqlQuery.Close; 
     _sqlQuery.SQL.Clear; 
     _sqlQuery.SQL.Add('INSERT INTO example_info(c, d) ' 
         + 'VALUES(:c, :d)'); 
     _sqlQuery.ParamByName('c').AsInteger := Example.Info.C; 
     _sqlQuery.ParamByName('d').AsFloat := Example.Info.D; 
     _sqlQuery.ExecSQL(False); 

     if Assigned(dbxTransaction) then 
      _connection.CommitFreeAndNil(dbxTransaction); 

     Result := True; 
     except 
     on Exc:Exception do 
     begin 
      if Assigned(dbxTransaction) then 
      _connection.RollBackFreeAndNil(dbxTransaction); 

      raise Exc; 
      Result := False; 
     end; 
     end; 
    finally 
     if Assigned(dbxTransaction) then 
     FreeAndNil(dbxTransaction); 
    end;  
    end 
    else 
    begin 
    Result := False; 
    end; 
end; 
+0

'Результат: = False;' после 'raise' бесполезен. Я бы поднял «EArgumentNilException», если «Example» не назначен и превратил его из функции в процедуру –

+0

http://stackoverflow.com/questions/8548843/why-should-i-not-use-if-assigned- до использования или освобождения вещей –

+0

Присвоение результата бесполезно перед рейзом! –

ответ

1

Вам необходимо правильно инициализировать dbxTransaction к nil в начале вашей функции. Локальные переменные в Delphi (по крайней мере, на платформе Win32) не инициализируются до тех пор, пока им не будет присвоено значение, что означает, что содержимое неизвестно. Передача любых значений, отличных от nil, до Assigned приведет к True. Я рекомендую никогда не тестировать содержимое локальной переменной на на любой платформе до тех пор, пока она не получит значение, указанное в вашем коде.

Вот пример того, как заставить его работать. (Я также удалил ненужное задание Result в блоке исключения.)

function TExampleDAO.Salve(const Example: TExample): Boolean; 
var 
    dbxTransaction: TDBXTransaction; 
begin 
    dbxTransaction := nil;   // Initialize the transaction variable here 

    if Assigned(Example) then // prevents invalid object, like ExampleDAO.Save(nil); 
    begin 
    try 
     if (_connection.TransactionsSupported) AND 
     ((not _connection.InTransaction) OR (_connection.MultipleTransactionsSupported)) then 
     begin 
     dbxTransaction := _connection.BeginTransaction(TDBXIsolations.ReadCommitted); 
     end; 

     try 
     // example 
     _sqlQuery.Close; 
     _sqlQuery.SQL.Clear; 
     _sqlQuery.SQL.Add('INSERT INTO example(a, b) ' 
         + 'VALUES(:a, :b)'); 
     _sqlQuery.ParamByName('a').AsAnsiString := Example.A; 
     _sqlQuery.ParamByName('b').AsDateTime := Example.B; 
     _sqlQuery.ExecSQL(False); 

     // example info 
     _sqlQuery.Close; 
     _sqlQuery.SQL.Clear; 
     _sqlQuery.SQL.Add('INSERT INTO example_info(c, d) ' 
         + 'VALUES(:c, :d)'); 
     _sqlQuery.ParamByName('c').AsInteger := Example.Info.C; 
     _sqlQuery.ParamByName('d').AsFloat := Example.Info.D; 
     _sqlQuery.ExecSQL(False); 

     if Assigned(dbxTransaction) then 
      _connection.CommitFreeAndNil(dbxTransaction); 

     Result := True; 
     except 
     on Exc:Exception do 
     begin 
      if Assigned(dbxTransaction) then 
      _connection.RollBackFreeAndNil(dbxTransaction); 

      raise Exc; 
     end; 
     end; 
    finally 
     if Assigned(dbxTransaction) then 
     FreeAndNil(dbxTransaction); 
    end;  
    end 
    else 
    begin 
    Result := False; 
    end; 
end; 

Как было отмечено @SirRufo в комментариях к вашему вопросу, в противном случае передать Example в качестве параметра, вероятно, следует вызвать исключение как ну, это означало бы, что это может стать процедурой вместо функции, а Result больше не будет применяться.

+0

Спасибо @ KenWhite. На данный момент я не могу изменить функцию, чтобы быть процедурой, потому что другие объекты ожидают, что результат True продолжит выполнение. Итак, я также начинаю результат: = False, в начале функции. Вы можете проверить окончательный код здесь: https://gist.github.com/anonymous/c1ce27723cf793828c66 – rodrigopandini

Смежные вопросы