13 февраля 2011

Сохранение в базу данных отчета FastReport в формате PDF

   Недавно пришлось писать DLL, одна из функций которой должна была:
1. сформировать отчет в FastReport;
2. экспортировать отчет в файл PDF-формата и сохранить его в базе данных;
3. возвратить идентификатор сохраненного в базе данных файла.
Предположим, что исходная структура таблицы для хранения файла в базе данных была такая (MS SQLServer 2000):
CREATE TABLE X.FILES (
  ID BIGINT IDENTITY NOT NULL,
  FILE_BODY IMAGE NULL,
  CONSTRAINT PK_FILES PRIMARY KEY (ID)),
где FILE_BODY – это поле, в которое сохраняется файл, а ID – идентификатор файла.
   Для начала я создал процедуру, которая будет вставлять файл в базу данных и возвращать идентификатор вставленного файла.
CREATE PROCEDURE X.InsertFile
 @FILE image,
 @ID bigint OUTPUT
AS
 INSERT INTO X.FILES(FILE_BODY) VALUES(@FILE)
 SET @ID = SCOPE_IDENTITY()
GO
   Потом сел писать код DLL. В Delphi вся работа с файлами реализована с помощью потоков, поэтому я немного удивился, когда оказалось, что метод Export у TfrxReport экспортирует отчет только в файл (честно сказать, я ожидал увидеть, что-то в стиле ExportToStream). Т.е. вместо прямой передачи отчета через поток в базу данных, необходимо было сначала сохранить отчет во временный файл, а потом этот файл загрузить в базу данных. Мне это не понравилось, т.к. файловые операции (сначала записи, потом чтения) должны были хоть немного, но тормозить работу. Но нужно было срочно отдать DLL, поэтому я не стал разбираться с экспортом и сделал через файл:

...
 db: TSDDatabase; // база данных SQLDirect
 spInsertFile: TSDStoredProc; // вызов X.InsertFile
 frxReport: TfrxReport;
 frxPDFExport: TfrxPDFExport;
...

Function GetDoc(const sDotName: String; ...): Integer;
begin
 Result := -1;
 // загружаем шаблон отчета
 If frxReport.LoadFromFile(sDotName)
  then begin
   // устанавливаем параметры отчета
   ...
   If frxReport.PrepareReport
    then try
     // получаем имя временного файла
     frxPDFExport.FileName := GetTempFileName;
     Try
      // экспортируем отчет во временный файл
      frxReport.Export(frxPDFExport);
      // загружаем PDF-файл в параметр процедуры
      // из временного файла
      spInsertFile.Params[1].LoadFromFile(frxPDFExport.FileName, ftBlob);
     Except
      on E: Exception do
       WriteErrorMessage(E.Message);
     End;
     // сохраняем PDF-файл в базу данных
     Try
      db.StartTransaction;
      spInsertFile.ExecProc;
      db.Commit;
      Result := spInsertFile.Params[2].AsInteger;
     Except
      on E: ESDEngineError do
       begin
        db.Rollback;
        WriteErrorMessage(E.Message);
       end;
     End;
    Finally
     // удаляем временный файл
     DeleteFile(frxPDFExport.FileName)
    End
    else WriteErrorMessage('Ошибка подготовки отчета')
  end
  else WriteErrorMessage('Файл шаблона не найден')
end;

   После передачи DLL другому программисту, мысли о лишних файловых операциях при сохранении отчета в базу через временный файл, не давала мне покоя. Вечером, не найдя решения в документации по FastReport, я, прежде чем смотреть исходный код экспорта, решил еще раз пройтись по методам и свойствам экспорта. Глаз сразу же зацепился за свойство Stream. Я создал для этого свойства поток и метод Export у TfrxReport выгрузил отчет не в файл, а в поток.

Function GetDoc(const sDotName: String; ...): Integer;
begin
 Result := -1;
 // загружаем шаблон отчета
 If frxReport.LoadFromFile(sDotName)
  then begin
   // устанавливаем параметры отчета
   ...
   If frxReport.PrepareReport
    then begin
     Try
      Try
       // создаём поток в памяти
       frxPDFExport.Stream := TMemoryStream.Create;
       // экспортируем отчет в поток
       frxReport.Export(frxPDFExport);
       // загружаем PDF-файл в параметр процедуры
       // из потока в памяти
       spInsertFile.Params[1].LoadFromStream(frxPDFExport.Stream, ftBlob);
      Except
       on E: Exception do
        WriteErrorMessage(E.Message);
      End;
     Finally
      frxPDFExport.Stream.Free;
     End;
     // сохраняем PDF-файл в базу данных
     Try
      db.StartTransaction;
      spInsertFile.ExecProc;
      db.Commit;
      Result := spInsertFile.Params[2].AsInteger;
     Except
      on E: ESDEngineError do
       begin
        db.Rollback;
        WriteErrorMessage(E.Message);
       end;
     End;
    end
    else WriteErrorMessage('Ошибка подготовки отчета')
  end
  else WriteErrorMessage('Файл шаблона не найден')
end;

   Часто решение задачи лежит на поверхности и нет нужды копаться в чужом коде. Нужно лишь быть внимательным и никогда не сдавайтесь – "ищите и обрящите".

3 комментария:

  1. В FR так сделано специально. Дело в том, что некоторые форматы экспорта позволяют создавать несколько файлов при экспорте одного шаблона (в основном это картинки - по файлу на печатную страницу отчёта).

    Я же, в своей практике, позволяю конечному пользователю настроить формат экспорта, а после экспорта все временные файлы архивируются и отсылаются на e-mail...

    ОтветитьУдалить
  2. Документацию по FastReport можно не смотреть - там мало, что есть. Могли бы для хорошего генератора отчетов и хорошую документацию сделать ;)

    ОтветитьУдалить
  3. Спасибо за полезную информацию! 2 дня уже потратил разбираясь с экспортом в Stream

    ОтветитьУдалить