【问题标题】:Multiple parameterized Delphi SQL updates within a transaction事务中的多个参数化 Delphi SQL 更新
【发布时间】:2015-07-10 18:03:40
【问题描述】:

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

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

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

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 server也可以是SQLite。

【问题讨论】:

  • 您有问题吗?我不是 Delphi 方面的专家,但这对我来说还不错。
  • 代码看起来确实不错,一个小挑剔,你应该重新提出异常......

标签: sql-server delphi sqlite transactions parameterized


【解决方案1】:

您的事务处理逻辑是正确的(@whosrdaddy 提到的丢失异常重新引发除外)。问题是您的数据集实例缺少 try..finally 块。除了您应该停止使用 TSQLConnection 已弃用的方法,这些方法正在使用 TTransactinDesc 记录(在构建应用程序时始终检查编译器警告。)。并且你也可以切换到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 块之前将数据集初始化为 nil,则只需一个 try finally 块即可释放数据集。
  • @House,你仍然应该假设释放内部的异常失败。
  • Free 在释放对象之前检查它们是否为零......如果内部发生了什么事......并且外部永远不会被设置......它的参考。将是 nil 并且免费调用它不会做任何事情......
  • @House,我知道Free 做了什么。但这与初始化无关。是关于释放。如果您的任何析构函数引发异常,以下对象将泄漏。
  • 如果你有会引发异常的析构函数......你有更严重的问题......最后我不会使用初始化尝试的唯一一次是如果我有触发 Destructor 事件代码的代码...在 15 年的 Delphi 编程中,我可以说我做的比我一只手可以指望的要少...
【解决方案2】:

我更喜欢 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 对象一起使用的代码

  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;

【讨论】:

  • 哎呀,真恶心。您正在使用标志将 try-finally 转换为 try-except。你到底为什么要那样做?该语言已经有一个专门为此添加的语言结构。这是您要替换的 try-except 块。
  • 实际上为什么它很讨厌...尝试例外应该是例外而不是常态,不用担心重新引发异常...您正确处理交易无论是好是坏...您应该阅读 Danny Thorpes 的书...在这本书中,应用程序编写者很少应该写 Try except...并且应该写 Try finally...
  • 它还可以正确处理数据集的释放。它比在 Try Try Try except finally finally 中包装代码要快...您应该阅读 Danny Thorpe 的 Delphi 组件设计第 112-114 页。
猜你喜欢
  • 1970-01-01
  • 2021-10-25
  • 1970-01-01
  • 2014-12-13
  • 1970-01-01
  • 2019-12-30
  • 2018-05-11
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多