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

3

我相对于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;

然而,存储过程执行失败。也就是说,代码运行正常,没有显示错误消息,但存储过程没有运行。

进一步的信息是——如果我删除一个组件并在代码中设置参数,则它可以正常运行。

有人知道我漏掉了什么吗?


失败的原因是什么?有出现任何错误信息吗? - EProgrammerNotFound
抱歉,无法执行任何操作。没有错误信息。代码只是执行而已。我会更新问题。 - Nick Hodges
1
尝试使用ParamByName并手动为参数定义所有值。您的代码没问题,问题应该在于参数顺序。 - EProgrammerNotFound
2
我希望LSP.Params [0]是“@RETURN_VALUE”,它返回SP错误代码。因此,您的代码应该像“LSP.Params [i + 1] .Value:= aParams [i] .Valie”这样。 - da-soft
@MatheusFreitas -- ParamByName这个东西起作用了。但我认为它之所以起作用是因为我没有在参数的开头加上'@'。我真是太蠢了。 - Nick Hodges
显示剩余3条评论
1个回答

3
鉴于这个问题已经很长时间没有得到回答,我想尝试着让代码正常运行,不使用评论中的提示,但发现并不像我想象的那么容易。
我立刻遇到了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在问题中提到他最初遇到的问题是SP无法执行,但他发现如果他“删除一个组件并在代码中设置参数”,那么它就可以工作。因此,我想在这里包括一个控制台应用程序,在其中当然必须全部使用代码。这并不困难,但是我花费的时间来正确获取Uses子句是我发布此答案的主要原因,供将来参考。如果没有正确的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.

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接