如何通过 FireDac 从 FireBird 数据库流式传输使用 zlib 压缩的图像 (Delphi)

How to stream an image that zipped with zlib from FireBird Database by FireDac (Delphi)

其实我之前问过这个问题,但我删除了这个问题,因为可能我问错了,或者没有正确表达我的问题或目标。

我正在连接到我们的 ERP 软件数据库 (FireBird v2.1) 以检索数据以导出为 XML。但同时我需要将所有产品图片保存到单独的文件夹中(我将总结所有数据和图片并上传到网络服务器以导入电子商务应用程序)。

我们的 ERP 软件公司将数据库分为 2 个独立的部分(1 个用于信息,1 个用于文件(图像、文件等)- FILES 数据库)
问题是,FILES 数据库中的所有资产都用 zlib 压缩,所以我不能直接通过 LiveBindings 流式传输这些图像(LinkPropertyToFieldBitmap 中的评估错误:加载位图失败)我知道这是正常的,因为文件被压缩并作为 BLOB 插入。

我需要将这些 zlib 压缩数据作为流来使用它作为解压缩过程的输入。

我打算使用以下程序将解压缩的图像保存为文件。

procedure TForm1.DecompressStream(Stream: TStream);
var
  LOutput: TFileStream;
  LUnZip: TZDecompressionStream;
begin
  Stream := TStream.Create();
  { Create the Output and Decompressed streams. }
  LOutput := TFileStream.Create('SKU OF PRODUCT.jpg', fmCreate);
  LUnZip := TZDecompressionStream.Create(Stream);

  { Decompress data. }
  LOutput.CopyFrom(LUnZip, 0);

  { Free the streams. }
  Stream.Free;
  LUnZip.Free;
  LOutput.Free;
end;

注意:也许上述过程不正确,但在能够将 zlib 数据作为流获取之后,我可以调试以更正它。 谢谢..

更新:我正在使用 LiveBindings 从数据库中获取数据,但压缩数据和图像处理并不强制使用 LiveBindings。

您说过您将仅使用该数据集进行读取(不会写回 DBMS)并且您的目标实际上只是解压缩获取到客户端的 BLOB 流。目前无法以某种舒适的方式拦截 BLOB 获取(类似于 OnBlobFetching 事件)。

拦截 BLOB 流存储的最近路径将在 TFDDatSRow.InternalSetData 方法中(这是转换获取数据的理想位置,就在它们存储在 FireDAC 的内部之前数据存储)。但这需要修改源代码。

无需修改源代码,您可以编写例如AfterGetRecord 事件的事件处理程序并从那里解压缩流。如果您决定直接在字段中覆盖获取的流(最好将数据集设置为只读模式),请非常小心不要将修改后的数据更改提交到数据库中。

一个例子:

procedure TForm1.FDQuery1AfterGetRecord(DataSet: TFDDataSet);
var
  BlobStream: TFDBlobStream;
  HelpStream: TMemoryStream;
begin
  { create BLOB stream for reading and writing }
  BlobStream := DataSet.CreateBlobStream(DataSet.FieldByName('Data'), bmReadWrite) as TFDBlobStream;
  try
    { create intermediate stream }
    HelpStream := TMemoryStream.Create;
    try
      { decompress BLOB stream into helper one }
      ZDecompressStream(BlobStream, HelpStream);
      { and overwrite the original BLOB stream content with uncompressed data; note, that
        TFDBlobStream must know about the modification, otherwise it won't store the data
        into the storage when this stream is released (LoadFromStream won't work here) }
      BlobStream.Clear;
      BlobStream.Write(HelpStream.Memory^, HelpStream.Size);
    finally
      HelpStream.Free;
    end;
  finally
    BlobStream.Free;
  end;
end;

或类似的较低级别:

procedure TForm1.FDQuery1AfterGetRecord(DataSet: TFDDataSet);
var
  DataRow: TFDDatSRow;
  DataCol: TFDDatSColumn;
  InLength: LongWord;
  InBuffer: Pointer;
  OutLength: Integer;
  OutBuffer: Pointer;
begin
  { get the current row storage object }
  DataRow := DataSet.GetRow;
  { for column indexing in following calls find column by name }
  DataCol := DataSet.Table.Columns.ColumnByName('Data');
  { try to get pointer to the raw data buffer for the column with given index }
  if DataRow.GetData(DataCol.Index, rvDefault, InBuffer, 0, InLength, False) then
  begin
    { decompress the data buffer into another allocated by this procedure }
    ZDecompress(InBuffer, InLength, OutBuffer, OutLength);
    try
      { start editing this storage row }
      DataRow.BeginEdit;
      try
        { let the storage copy the decompressed data from the buffer }
        DataRow.SetData(DataCol.Index, OutBuffer, OutLength);
      finally
        { finish this storage row editing without creating new row version, so the engine
          won't take the data modification as update }
        DataRow.EndEdit(True);
      end;
    finally
      { and release the buffer allocated by ZLib library function call, input buffer used
        here belongs to FireDAC's storage }
      FreeMem(OutBuffer);
    end;
  end;
end;