使用 FireDac 在 Delphi 中动态创建和调用存储过程的正确方法是啥?

Posted

技术标签:

【中文标题】使用 FireDac 在 Delphi 中动态创建和调用存储过程的正确方法是啥?【英文标题】:What is the proper way to dynamically create and call a stored procedure in Delphi using FireDac?使用 FireDac 在 Delphi 中动态创建和调用存储过程的正确方法是什么? 【发布时间】:2014-02-11 15:21:11 【问题描述】:

我对 FireDAC 比较陌生。我希望能够动态地“动态”调用存储过程。到目前为止,我有以下内容:

function TForm21.ExecuteStoredProc(aSPName: string; aParams: TADParams): Boolean;
var
  LSP: TADStoredProc;
  i: Integer;
begin
  LSP := TADStoredProc.Create(nil);
  try
    LSP.Connection := ADConnection1;
    LSP.StoredProcName := aSPName;
    LSP.Prepare;
    for i := 0 to aParams.Count - 1 do
    begin
      LSP.Params[i].Value := aParams[i].Value;
    end;
    LSP.ExecProc;
  finally
    LSP.Free;
  end;
  Result := True;
end;

我叫它

procedure TForm21.Button1Click(Sender: TObject);
var
  LParams: TADParams;
begin
  LParams := TADParams.Create;
  LParams.Add.Value := 612;
  LParams.Add.Value := '2008';

  ExecuteStoredProc('HDMTEST.dbo.spARCHIVE_HISTORY_DATA', LParams);
end;

但是,存储过程无法执行。也就是说,代码运行正常,没有显示错误信息,但是存储过程没有运行。

更多信息 -- 如果我删除一个组件并在代码中设置参数,它运行良好。

有人知道我错过了什么吗?

【问题讨论】:

失败怎么办?有什么错误信息吗? 对不起,什么都没做。没有错误信息。代码只是执行。我会更新问题。 尝试使用 ParamByName 并手动定义参数的所有值。您的代码很好,问题应该取决于参数顺序。 我希望 LSP.Params[0] 是 '@RETURN_VALUE',它返回 SP 错误代码。因此,您的代码应该看起来像“LSP.Params[i+1].Value := aParams[i].Valie”。 @MatheusFreitas -- ParamByName 的事情奏效了。但我认为它有效,因为我没有将“@”放在参数的开头。傻逼我。 【参考方案1】:

看到这个 q 已经有一段时间没有得到答复,我想我会尝试在不使用 cmets 的线索的情况下让代码正常工作,但发现它并不像我想象的那么容易。

我立即被 SP 参数卡住了。我找到了这个

http://docwiki.embarcadero.com/RADStudio/XE5/en/TFDQuery,_TFDStoredProc_and_TFDUpdateSQL_Questions

上面写着

"If you have difficulties with manual definition of parameters,
populate the Params collection automatically and check how the
parameters are defined. Then compare that to your code. "

但我找不到“自动”填充参数的方法。我在EMBA上问过 FireDac 新闻组和 FD 作者 Dimitry Arefiev 亲切地解释说 您可以通过检查 FetchOptions 是否包含 fiMeta,然后清除并设置 FDStoredProc 的 StoredProcName 来做到这一点。

在我的 SqlServer 上的 pubs 演示数据库中使用 StoredProc,定义如下:

create procedure test(@ANumber int, @AName varchar(20))
as
begin
  select
   @ANumber * 2 as "Number",
   @AName + @AName as "Name"
end

我像这样更改了 OP 代码的几个部分

[...]
  LSP.Params.Clear;
  LSP.StoredProcName := '';
  LSP.FetchOptions.Items := LSP.FetchOptions.Items + [fiMeta];
  LSP.StoredProcName := aSPName;
  LSP.Prepare;
  Assert(LSP.ParamCount > 0);

  for i := 0 to aParams.Count - 1 do
  begin
    LSP.Params.ParamByName(aParams[i].Name).Value := aParams[i].Value;
  end;
[...]

procedure TForm21.Button1Click(Sender: TObject);
var
  LParams: TFDParams;
  Param : TFDParam;
begin
  LParams := TFDParams.Create;
  Param := LParams.Add;
  Param.Name := '@ANumber';
  Param.Value := 612;

  Param := LParams.Add;
  Param.Name := '@AName';
  Param.Value := '2008';

  ExecuteStoredProc('test', LParams);
end;

效果很好。

OP 在 q 中提到他首先遇到了 SP 无法执行的问题 但是他发现如果他“[删除]一个组件并在代码中设置参数”它会起作用,所以我想我会在这里包含一个控制台应用程序,当然所有事情都必须在代码中完成。这并不难,但是我花费时间来正确使用 Uses 子句是我将其发布为答案以供将来参考的主要原因。如果没有正确使用,您会收到抱怨各种类工厂丢失的错误。

控制台应用(在 XE6 中创建和测试):

program ConsoleStoredProcProject3;

$APPTYPE CONSOLE

$R *.res

uses
  System.SysUtils, FireDac.DApt, FireDAC.Stan.Def, FireDAC.Stan.ASync,
  FireDAC.Stan.Param, FireDAC.Stan.Option, FireDAC.Comp.Client,
  FireDAC.Phys.MSSQL, VCL.ClipBrd;

procedure TestSP;
var
  Connection : TFDConnection;
  StoredProc : TFDStoredProc;
  Param : TFDParam;
begin
  Connection := TFDConnection.Create(Nil);
  Connection.DriverName := 'MSSQL';
  Connection.Params.Values['Server'] := // your server name'';
  Connection.Params.Values['Database'] := 'pubs';
  Connection.Params.Values['user_name'] := 'user';    // adjust to suit
  Connection.Params.Values['password'] := 'password'; // ditto
  Connection.LoginPrompt := False;
  Connection.Connected := True;

  StoredProc := TFDStoredProc.Create(Nil);
  StoredProc.Connection := Connection;
  StoredProc.FetchOptions.Items := StoredProc.FetchOptions.Items + [fiMeta];
  StoredProc.StoredProcName := 'test';
  StoredProc.Prepare;
  Param := StoredProc.Params.ParamByName('@ANumber');
  Param.Value := 333;
  Param := StoredProc.Params.ParamByName('@AName');
  Param.Value := 'A';

  StoredProc.Active := True;

  WriteLn(StoredProc.FieldByName('Number').AsInteger);
  WriteLn(StoredProc.FieldByName('Name').AsString);

  ReadLn;
end;

begin
  try
    TestSP;
  except
    on E: Exception do
      Clipboard.AsText := E.Message;
  end;
end.

【讨论】:

以上是关于使用 FireDac 在 Delphi 中动态创建和调用存储过程的正确方法是啥?的主要内容,如果未能解决你的问题,请参考以下文章

Delphi Firedac 在 docker 容器中连接到 MySQL

Delphi 和 Firedac:查询活动与查询打开

Delphi:从 IBO 迁移到 FireDac

Delphi:FireDac 连接阻止应用程序

delphi7能用firedac吗

Delphi XE FireDac 连接池