【问题标题】:Getting binary data using SqlDataReader使用 SqlDataReader 获取二进制数据
【发布时间】:2011-07-19 06:40:40
【问题描述】:

我有一个名为 Blob (Id (int), Data (Image)) 的表。我需要使用 SqlDataReader 来获取该图像数据。请注意,我不想将 Response.Binarywrite() 数据发送到浏览器。我只需要将二进制数据作为 byte[] 就可以将其用于某些内部操作。我能想到的唯一方法是使用 SqlDataReader 获取 id,然后再次使用 SqlCommand.ExecuteScalar() 将其作为给定 id 的 byte[] 获取。我可以只使用 SqlDataReader (SqlCommand.ExecuteReader) 将图像数据作为字节 [] 获取吗?我错过了什么吗?

【问题讨论】:

    标签: c# ado.net


    【解决方案1】:

    您应该可以通过以下方式获得它:(byte[])reader["Data"]。

    另请注意,image 数据类型已弃用,并将在 SQL Server 的未来版本中删除;请改用varbinary(max)。

    【讨论】:

    • 我什至没有在 .NET 4.0 中看到 SqlDataReader 上的 Items 集合。
    • Items 集合是否已弃用?
    【解决方案2】:

    是的,您可以使用SqlDataReader.GetBytes。您可能希望在第一次调用中为缓冲区传递null,以了解有多少数据,然后使用适当大小的缓冲区再次调用它。

    您可能只是能够使用索引器并将结果转换为字节数组 - 我不确定。值得一试:)

    【讨论】:

    • ind ndx = rdr.GetOrdinal(""); if(!rdr.IsDBNull(ndx)) { long size = rdr.GetBytes(ndx, 0, null, 0, 0); //获取数据长度 byte[] values = new byte[size]; int 缓冲区大小 = 1024;长字节读取 = 0; int curPos = 0; while (bytesRead
    • @ydobonmai:既然你已经有了一个大小合适的缓冲区,为什么还要为不同的缓冲区大小而烦恼呢?你应该使用bytesRead 而不是curPos;你目前总是假设它读取了一个完整的缓冲量。
    • SqlDataReader.GetBytes() 听起来是一种非常有效的方法,因为您可以重用相同的数组(如果它具有适当的大小)。因此,您不会像 (byte[])reader["Data"] 那样为每一列创建一个新数组。
    【解决方案3】:

    在 .NET Framework 4.5 中,您可以使用GetStream 方法以流的形式访问二进制数据。

    【讨论】:

      【解决方案4】:

      来自MSDN。不知道为什么我以前找不到。

          SqlConnection pubsConn = new SqlConnection("Data Source=localhost;Integrated Security=SSPI;Initial Catalog=pubs;");
          SqlCommand logoCMD = new SqlCommand("SELECT pub_id, logo FROM pub_info", pubsConn);
      
          FileStream fs;                          // Writes the BLOB to a file (*.bmp).
          BinaryWriter bw;                        // Streams the BLOB to the FileStream object.
      
          int bufferSize = 100;                   // Size of the BLOB buffer.
          byte[] outbyte = new byte[bufferSize];  // The BLOB byte[] buffer to be filled by GetBytes.
          long retval;                            // The bytes returned from GetBytes.
          long startIndex = 0;                    // The starting position in the BLOB output.
      
          string pub_id = "";                     // The publisher id to use in the file name.
      
          // Open the connection and read data into the DataReader.
          pubsConn.Open();
          SqlDataReader myReader = logoCMD.ExecuteReader(CommandBehavior.SequentialAccess);
      
          while (myReader.Read())
          {
            // Get the publisher id, which must occur before getting the logo.
            pub_id = myReader.GetString(0);  
      
            // Create a file to hold the output.
            fs = new FileStream("logo" + pub_id + ".bmp", FileMode.OpenOrCreate, FileAccess.Write);
            bw = new BinaryWriter(fs);
      
            // Reset the starting byte for the new BLOB.
            startIndex = 0;
      
            // Read the bytes into outbyte[] and retain the number of bytes returned.
            retval = myReader.GetBytes(1, startIndex, outbyte, 0, bufferSize);
      
            // Continue reading and writing while there are bytes beyond the size of the buffer.
            while (retval == bufferSize)
            {
              bw.Write(outbyte);
              bw.Flush();
      
              // Reposition the start index to the end of the last buffer and fill the buffer.
              startIndex += bufferSize;
              retval = myReader.GetBytes(1, startIndex, outbyte, 0, bufferSize);
            }
      
            // Write the remaining buffer.
            if(retval > 0) // if file size can divide to buffer size
                bw.Write(outbyte, 0, (int)retval); //original MSDN source had retval-1, a bug
            bw.Flush();
      
            // Close the output file.
            bw.Close();
            fs.Close();
          }
      
          // Close the reader and the connection.
          myReader.Close();
          pubsConn.Close();
      

      【讨论】:

      • 对此有一点注意:如果您没有在 ExecuteReader 调用上设置 CommandBehavior.SequentialAccess(例如,从数据层的另一部分获取读取器),它会对您的记忆造成可怕的影响(创建每次调用 GetBytes()) 的 blob 大小的字节数组;此解决方案只能与 CommandBehavior.SequentialAccess 一起使用,如此处所示。
      • 谁能提供关于如何决定缓冲区大小的cmets?该示例使用 100,但为什么不是 10,为什么不是 10,000?需要考虑哪些因素?
      【解决方案5】:

      使用此功能进行安全灵活的字节读取:

          /// <summary>
          /// Reads all available bytes from reader
          /// </summary>
          /// <param name="reader"></param>
          /// <param name="ordinal"></param>
          /// <returns></returns>
          private byte[] GetBytes(SqliteDataReader reader, int ordinal)
          {
              byte[] result = null;
      
              if (!reader.IsDBNull(ordinal))
              {
                  long size = reader.GetBytes(ordinal, 0, null, 0, 0); //get the length of data 
                  result = new byte[size];
                  int bufferSize = 1024;
                  long bytesRead = 0;
                  int curPos = 0;
                  while (bytesRead < size)
                  {
                      bytesRead += reader.GetBytes(ordinal, curPos, result, curPos, bufferSize);
                      curPos += bufferSize;
                  }
              }
      
              return result;
          }
      

      【讨论】:

      • 既然你已经有了正确的尺寸,最好还是int bufferSize = size &gt; int.MaxValue ? int.MaxValue : (int)size;
      • 实际上不需要循环。只需使用正确的大小参数调用 reader.GetBytes 一次: reader.GetBytes(ordinal, 0, result, 0, result.Length)
      【解决方案6】:

      无需使用阅读器。只需使用数据集从数据库中获取值(使用存储的 Proc 或任何其他方法),然后使用字节(下面的代码)类型转换并将其存储在字节数组中。你的工作已经完成了。

      byte[] productImage;
      productImage = (byte[])ds.Tables[0].Rows[0]["Image"];
      

      【讨论】:

        【解决方案7】:

        这是一个老问题,我一直在使用上面的 Anton Bakulev 的答案,直到遇到我的数据实际上大于 int curPos 可以处理的 2GB 的情况。当我尝试将 bufferIndex 参数更改为 0 时,超出 bufferSize 的任何内容都已损坏。 (此外,如此小的缓冲区大小使得加载 2MB 以上的任何内容都成为一种痛苦)。

        不,您可能不应该在数据库的单个列中包含超过 2GB 的数据。尽量避免这种情况。但以防万一,这里有一个更健壮、更精简的代码版本,作为 SqlDataReader 扩展方法:

        public static byte[] ParseStrictByteArray(this SqlDataReader reader, string columnName)
        {
            int colIdx = reader.GetOrdinal(columnName);
            long size = reader.GetBytes(colIdx, 0, null, 0, 0);
            byte[] imageValue = new byte[size];
            // essentially, we are loading all this data in memory, either way... Might as well do it in one swoop if we can
            int bufferSize = (int)Math.Min(int.MaxValue, size); 
            //int.MaxValue = 2,147,483,647 = roughly 2 GB of data, so if the data > 2GB we have to read in chunks
                    
            if(size > bufferSize){
        
                long bytesRead = 0;
                int position = 0;
                //we need to copy the data over, which means we DON'T want a full copy of all the data in memory. 
                //We need to reduce the buffer size (but not too much, as multiple calls to the reader also affect performance a lot)
                bufferSize = 104857600; //this is roughly 100MB
                byte[] buffer = new byte[bufferSize];
                while (bytesRead < size)
                {
                    if (size - bytesRead < bufferSize)
                        bufferSize = Convert.ToInt32(size - bytesRead);
        
                    bytesRead += reader.GetBytes(colIdx, position, buffer, 0, bufferSize);
                    //shift the buffer into the final array
                    Array.Copy(buffer, 0, imageValue, position, bufferSize);
                    position += bufferSize;
                }
            }
            else 
            {
                //single read into the image buffer
                reader.GetBytes(colIdx, 0, imageValue, 0, bufferSize);
            }
        
            return imageValue;
        } 
        

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2020-10-31
          • 2014-05-08
          • 2021-10-05
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多