事务中的多个参数化 Delphi SQL 更新

Multiple parameterized Delphi SQL updates within a transaction

我正在尝试使用 Delphi XE8 中的参数化查询在同一循环中更新两个不同的 SQL table。我还想将整个事情包装在一个事务中,这样如果循环中的任何事情失败,table 都不会更新。

我真的不知道自己在做什么,希望得到一些帮助。

下面的代码是我要实现的目标的简化版本,也是我对如何实现它的最佳猜测。但我完全不确定,尤其是使用连接到 'SQL connection' 组件的两个数据集。

SQL_transaction.TransactionID :=1;
SQL_transaction.IsolationLevel:=xilREADCOMMITTED;
SQL_connection.BeginTransaction;
Try
  { Create connections }

  SQL_dataset1              :=TSQLDataSet.Create(nil);  
  SQL_dataset1.SQLConnection:=SQL_connection;

  SQL_dataset2              :=TSQLDataSet.Create(nil);  
  SQL_dataset2.SQLConnection:=SQL_connection;

  { Create queries }

  SQL_dataset1.CommandType:=ctQuery;
  SQL_dataset1.CommandText:={ some parameterized query updating table A }

  SQL_dataset2.CommandType:=ctQuery;
  SQL_dataset2.CommandText:={ some parameterized query updating table B }

  { Populate parameters and execute }

  For I:=0 to whatever do
  begin
    SQL_dataset1.ParamByName('Table A Field 1').AsString:='Value';
    SQL_dataset1.ExecSQL; 

    SQL_dataset2.ParamByName('Table B Field 1').AsString:='Value';
    SQL_dataset2.ExecSQL; 
  end;

  SQL_connection.Commit(SQL_transaction);
except
  SQL_connection.Rollback(SQL_transaction);  
end;

我正在使用 Delphi XE8,数据库可以是 SQL 服务器或 SQLite。

您的事务处理逻辑是正确的(除了@whosrdaddy 提到的缺失异常重新引发)。错误的是您的数据集实例缺少 try..finally 块。除了您应该停止使用使用 TTransactinDesc 记录的 TSQLConnection 已弃用方法(在构建应用程序时始终检查编译器警告。)。你也可以切换到 TSQLQuery 组件。尝试这样的事情:

var
  I: Integer;
  Query1: TSQLQuery;
  Query2: TSQLQuery;
  Connection: TSQLConnection;
  Transaction: TDBXTransaction;
begin
  ...  
  Query1 := TSQLQuery.Create(nil);
  try
    Query1.SQLConnection := Connection;
    Query1.SQL.Text := '...';

    Query2 := TSQLQuery.Create(nil);
    try
      Query2.SQLConnection := Connection;
      Query2.SQL.Text := '...';

      Transaction := Connection.BeginTransaction;
      try
        // fill params here and execute the commands
        for I := 0 to 42 to
        begin
          Query1.ExecSQL;
          Query2.ExecSQL;
        end;
        // commit if everything went right
        Connection.CommitFreeAndNil(Transaction);
      except
        // rollback at failure, and re-raise the exception
        Connection.RollbackFreeAndNil(Transaction);
        raise;
      end;
    finally
      Query2.Free;
    end;
  finally
    Query1.Free;
  end;
end;

我更喜欢 try finally 而不是 try except

这里是如何让它在 try finally 块中工作的方法

var
  a_Error: boolean;
begin
a_Error := True;//set in error state...
SQL_dataset1 := nil;
SQL_dataset2 := nil;
SQL_transaction.TransactionID :=1;
SQL_transaction.IsolationLevel:=xilREADCOMMITTED;
SQL_connection.BeginTransaction;

Try
  { Create connections }

  SQL_dataset1              :=TSQLDataSet.Create(nil);  
  SQL_dataset1.SQLConnection:=SQL_connection;

  SQL_dataset2              :=TSQLDataSet.Create(nil);  
  SQL_dataset2.SQLConnection:=SQL_connection;

  { Create queries }

  SQL_dataset1.CommandType:=ctQuery;
  SQL_dataset1.CommandText:={ some parameterized query updating table A }

  SQL_dataset2.CommandType:=ctQuery;
  SQL_dataset2.CommandText:={ some parameterized query updating table B }

  { Populate parameters and execute }

  For I:=0 to whatever do
  begin
    SQL_dataset1.ParamByName('Table A Field 1').AsString:='Value';
    SQL_dataset1.ExecSQL; 

    SQL_dataset2.ParamByName('Table B Field 1').AsString:='Value';
    SQL_dataset2.ExecSQL; 
  end;

  a_Error := False;//if you don't get here you had a problem
finally
  if a_Error then
    SQL_connection.Rollback(SQL_transaction)
  else
    SQL_connection.Commit(SQL_transaction);
  SQL_dataset1.Free;
  SQL_dataset2.Free;

end;    
end;

我添加了一些关于 Try Finally 如何将 init 对象用于 nil 的代码

  TMyObject = class(TObject)
    Name: string;
  end;

procedure TForm11.Button1Click(Sender: TObject);
var
  a_MyObject1, a_MyObject2: TMyObject;
begin
  a_MyObject1 := nil;
  a_MyObject2 := nil;
  try
    a_MyObject1 := TMyObject.Create;
    a_MyObject1.Name := 'Object1';
    if Sender = Button1 then    
      raise exception.Create('Object 2 not created');
    ShowMessage('We will not see this');
    a_MyObject2 := TMyObject.Create;
    a_MyObject2.Name := 'Object2';
  finally
    a_MyObject2.Free;
    ShowMessage('We will see this even though we called a_MyObject2.free on a nil object');
    a_MyObject1.Free;
  end;
end;