某些文件被SQL Server FileStream破坏

4

我正在使用FILESTREAM将文件保存到SQL server 2008(Express)数据库中,但一些文件似乎在此过程中出现了损坏。

例如,如果我以较新的格式(docx或xslx)保存Word或Excel文档,则尝试打开该文件时会收到错误消息,提示数据已损坏,是否要尝试恢复。如果我选择是,Office就能够“恢复”数据并在兼容模式下打开文件。

但是,如果我先压缩文件,然后提取内容,那么我就能够毫无问题地打开文件。奇怪的是,如果我将mp3文件保存到数据库中,我会遇到相反的问题,我可以毫无问题地打开该文件,但是如果我保存了mp3的压缩版本,则无法提取该zip的内容。当我尝试保存pdf或PowerPoint文件时,我也遇到了类似的问题(只有将pdf压缩后才能阅读,而ppt根本无法阅读)。

更新:这是我用来写入数据库和读取的代码:

写入数据库:

SQL = "SELECT Attachment.PathName(), GET_FILESTREAM_TRANSACTION_CONTEXT() FROM Activity " +
               "WHERE RowID = CAST(@RowID as uniqueidentifier)";
           transaction = connection.BeginTransaction();

           command.Transaction = transaction;
           command.CommandText = SQL;
           command.Parameters.Clear();
           command.Parameters.Add(rowIDParam);

           SqlDataReader readerFS = null;
           readerFS= command.ExecuteReader();

   string path = (string)readerFS[0].ToString();
   byte[] context = (byte[])readerFS[1];
   int length = context.Length;

   SqlFileStream targetStream = new SqlFileStream(path, context, FileAccess.Write);

         int blockSize = 1024 * 512; //half a megabyte
            byte[] buffer = new byte[blockSize];
            int bytesRead = sourceStream.Read(buffer, 0, buffer.Length);
            while (bytesRead > 0)
            {
                targetStream.Write(buffer, 0, bytesRead);
                bytesRead = sourceStream.Read(buffer, 0, buffer.Length);
            }

            targetStream.Close();
            sourceStream.Close();
            readerFS.Close();
            transaction.Commit();

并阅读:

        SqlConnection connection = null;
        SqlTransaction transaction = null;

        try
        {
            connection = getConnection();
            connection.Open();
            transaction = connection.BeginTransaction();

            SQL = "SELECT Attachment.PathName(), + GET_FILESTREAM_TRANSACTION_CONTEXT() FROM Activity"
          +  " WHERE ActivityID = @ActivityID";


            SqlCommand command = new SqlCommand(SQL, connection);
            command.Transaction = transaction;

            command.Parameters.Add(new SqlParameter("ActivityID", activity.ActivityID));

            SqlDataReader reader = command.ExecuteReader();

            string path = (string)reader[0];
            byte[] context = (byte[])reader[1];
            int length = context.Length;
            reader.Close();

            SqlFileStream sourceStream = new SqlFileStream(path, context, FileAccess.Read);

            int blockSize = 1024 * 512; //half a megabyte
            byte[] buffer = new byte[blockSize];
           List<byte> attachmentBytes = new List<byte>();

            int bytesRead = sourceStream.Read(buffer, 0, buffer.Length);

            while (bytesRead > 0)
            {
                bytesRead = sourceStream.Read(buffer, 0, buffer.Length);
                foreach (byte b in buffer)
                {
                    attachmentBytes.Add(b);
                }

            }

            FileStream outputStream = File.Create(outputPath);

            foreach (byte b in attachmentBytes)
            {
                 byte[] barr = new byte[1];

                 barr[0] = b;

                 outputStream.Write(barr, 0, 1);
            }

            outputStream.Close();
            sourceStream.Close();
            command.Transaction.Commit();

可能是由于客户端处理插入时出现了损坏?例如,过早截断,或者使用传递给SQL的varchar() vs. nvarchar()参数类型,或char vs. binary?您是否愿意发布用于将内容上传到文件流的代码以及用于访问文件流的代码? - Remus Rusanu
谢谢回复,我已经编辑了我的问题并附上了代码。我不确定您所说的使用错误类型参数是什么意思,因为在 SQL Server 中该字段的类型为 Varbinary(max) FILESTREAM。 - Jack
2个回答

6

您的阅读代码不正确:

  while (bytesRead > 0)
        {
            bytesRead = sourceStream.Read(buffer, 0, buffer.Length);
            foreach (byte b in buffer)
            {
                attachmentBytes.Add(b);
            }

        }

如果bytesRead少于buffer.Length,则仍将整个缓冲区添加到attachementBytes中。 因此,您始终会在最后一个缓冲区后面添加任何垃圾,从而损坏返回的文档。
除此之外,让我有一个非常奇怪的时刻。 将流读取为List<byte>??天哪!首先,我不知道为什么你需要一开始就读取到中间内存存储中。 您可以逐个缓冲区地读取并将每个缓冲区直接写入outputStream中。 其次,如果您必须使用中间内存存储,请使用{{link1:MemoryStream}},而不是List<byte>

谢谢,这解决了问题。我已经更改了代码,现在直接从输入流写入输出流。 - Jack
1
暂且不考虑在读取时抛弃了前buffer.length字节的事实(bytesRead = ..., while (bytesRead > 0), bytesRead = ...)。 - Damien_The_Unbeliever
有趣的是,最近我正在阅读 Jon SkeetC# in Depth,他在第86页提到了泛型,许多方面上 List<byte> 实际上就像一个 MemoryStream。我并不是说在这种情况下这是正确的选择,我唯一能够为自己辩护的是当我编写它时,我经验不足得多,如果我记得正确的话,我基于我在网上读到的一个片段编写了我的原始代码的一部分。 - Jack
@Jack 很好的回应 :) - Remus Rusanu
谢谢,我一直对这个问题(或者说那个方面)感到有点尴尬,所以它一直在我脑海中挥之不去... - Jack

2

我之前遇到了同样的问题,后来发现是在从FILESTREAM读取文件时末尾多加了一个字节。


1
我仔细阅读了您的帖子,发现其中包含有用信息。我一直在想为什么只有特定的文件格式出现问题(尤其是旧的办公室格式似乎没有被损坏)。 - Jack
遇到了同样的问题,但是博客文章已经失效了。你能否提供更多细节,说明发生了什么以及你是如何解决的? - CooPzZ

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