ExcelReaderFactory,从 SftpFileStream 读取

ExcelReaderFactory, reading from SftpFileStream

我在使用 ExcelReaderFactory.CreateOpenXmlReader(streamReader) 读取 .xlsx 文件时出错,流 reader 是 SftpFileStream (SftpClient.OpenRead(filePath)).

我收到的错误消息是

Renci.SshNet.Common.SshException: General failure

我无法通过第一行代码。

using (var reader = ExcelReaderFactory.CreateOpenXmlReader(streamReader))
{
     //Get reader as DataSet
     var result = reader.AsDataSet(new ExcelDataSetConfiguration()
     {
        ConfigureDataTable = (tableReader) => new ExcelDataTableConfiguration()
        {
           UseHeaderRow = true
        }
                    });
.......
}

但是当我使用 System.IO.File.Open 的代码时,它工作正常。

堆栈:

at Renci.SshNet.Sftp.SftpSession.RequestRead(Byte[] handle, UInt64 offset, UInt32 length)
   at Renci.SshNet.Sftp.SftpFileStream.Read(Byte[] buffer, Int32 offset, Int32 count)
   at System.IO.Compression.ZipHelper.ReadBytes(Stream stream, Byte[] buffer, Int32 bytesToRead)
   at System.IO.Compression.ZipHelper.SeekBackwardsAndRead(Stream stream, Byte[] buffer, Int32& bufferPointer)
   at System.IO.Compression.ZipHelper.SeekBackwardsToSignature(Stream stream, UInt32 signatureToFind)
   at System.IO.Compression.ZipArchive.ReadEndOfCentralDirectory()
   at System.IO.Compression.ZipArchive.Init(Stream stream, ZipArchiveMode mode, Boolean leaveOpen)
   at System.IO.Compression.ZipArchive..ctor(Stream stream, ZipArchiveMode mode, Boolean leaveOpen, Encoding entryNameEncoding)
   at ExcelDataReader.Core.ZipWorker..ctor(Stream fileStream)
   at ExcelDataReader.ExcelOpenXmlReader..ctor(Stream stream)
   at ExcelDataReader.ExcelReaderFactory.CreateOpenXmlReader(Stream fileStream, ExcelReaderConfiguration configuration)

SftpFileStream.Seek 代码中显然存在错误。当用 SeekOrigin.End 调用时,它从文件末尾的位置减去 offset,而不是添加它。

如果您可以修改 SSH.NET 代码,请在 SftpFileStream.Seek 中更改此语句的两个实例:

newPosn = attributes.Size - offset;

newPosn = attributes.Size + offset;

我已将 pull request with this fix 提交到 SSH.NET 存储库。


如果您无法更改 SSH.NET 代码,则必须解决该问题。

  1. SftpFileStream 内容复制到临时 MemoryStream 并将其用于 ExcelReaderFactory.

     using (var memoryStream = new MemoryStream())
     {
         sftpFileStream.CopyTo(memoryStream);
         memoryStream.Position = 0;
         using (var reader = ExcelReaderFactory.CreateOpenXmlReader(memoryStream))
         {
             // ...
         }
     }
    
  2. 或者如果您不想浪费内存来保存文件的另一个副本,您可以实现自己的 Stream 围绕 SftpFileStream 的包装器,它转换 SeekOrigin.End SeekOrigin.Begin 用适当的 offset.

    有关此类实施的示例,请参阅:


注意ZipArchiveExcelReaderFactory内部使用)使用Stream.SeekSeekOrigin.End,因为ZIP中心目录在末尾压缩文件。 – XLSX 文件基本上是一个具有特定结构的 ZIP 文件。