Каков правильный способ динамического создания и вызова хранимой процедуры в Delphi с использованием FireDac?

Я относительно новичок в 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;

Однако хранимая процедура не выполняется. То есть код работает нормально, сообщение об ошибке не отображается, но хранимая процедура не запускается.

Дополнительная информация - он работает нормально, если я удаляю компонент и устанавливаю параметры в коде.

Кто-нибудь знает, что мне не хватает?


person Nick Hodges    schedule 11.02.2014    source источник
comment
Не получается как? любое сообщение об ошибке?   -  person EProgrammerNotFound    schedule 11.02.2014
comment
Извините, ничего не получается. Нет сообщения об ошибке. Код просто выполняется. Я обновлю вопрос.   -  person Nick Hodges    schedule 11.02.2014
comment
Попробуйте использовать ParamByName и вручную определить все значения для параметров. Ваш код в порядке, проблема должна зависеть от порядка параметров.   -  person EProgrammerNotFound    schedule 11.02.2014
comment
Я ожидаю, что LSP.Params[0] будет '@RETURN_VALUE', который возвращает код ошибки SP. Итак, ваш код скорее должен выглядеть как LSP.Params[i+1].Value := aParams[i].Valie.   -  person da-soft    schedule 11.02.2014
comment
@MatheusFreitas - Параметр ParamByName сработал. Но я думаю, что это сработало, потому что я не ставил «@» в начале параметров. Одурмани меня.   -  person Nick Hodges    schedule 11.02.2014
comment
@da-soft -- Дмитрий, ты тоже был прав. Вот почему ParamByName сработал. Спасибо.   -  person Nick Hodges    schedule 11.02.2014
comment
Кстати, если кто-то хочет сформулировать официальный ответ, я выберу его. Спасибо еще раз.   -  person Nick Hodges    schedule 11.02.2014
comment
Это должно быть da-soft, я имею в виду, я не могу знать больше, чем создатель   -  person EProgrammerNotFound    schedule 11.02.2014


Ответы (1)


Увидев, что этот вопрос какое-то время оставался без ответа, я подумал, что попытаюсь заставить код работать, не используя подсказки из комментариев, и обнаружил, что это не так просто, как я себе представлял.

Я сразу застрял с параметрами 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, Дмитрий Арефьев, любезно объяснил, что вы можете сделать это, проверив, что FetchOptions включает fiMeta, а затем очистив и установив StoredProcName FDStoredProc.

Использование StoredProc в демонстрационной базе данных pubs на моем SqlServer определяется следующим образом:

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;

и это работало нормально.

ОП упоминает в вопросе, что у него сначала возникла проблема с тем, что SP не удалось выполнить, но он обнаружил, что это сработало, если он «[отбросил] компонент и настроил параметры в коде», поэтому я подумал, что я бы включите сюда консольное приложение, где конечно обязательно все делается в коде. Это было несложно, но время, которое мне потребовалось, чтобы правильно понять пункт «Использование», является моей основной причиной публикации этого ответа в качестве ответа для дальнейшего использования. Без правильного использования вы получаете ошибки, жалующиеся на отсутствие различных фабрик классов.

Консольное приложение (создано и протестировано в 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.
person MartynA    schedule 30.07.2014