我应该向SqlDataReader.GetBytes()方法传递什么“length”参数?

31
我有一个SqlDataReader并且需要使用SqlDataReader.GetBytes()方法从中读取varbinary(max)列。该方法填充了一个字节数组,因此需要知道要读取多少长度的数据。
这就是让我困惑的地方...显然,我想要读取从数据库返回的所有数据在这一行/列,所以我应该传递什么'长度'参数?
据我所见,SqlDataReader并没有提供任何方法来发现可用的数据长度,因此我觉得这个方法相当麻烦。
我倾向于只需在此处传递int.MaxValue,然后忘记这个问题,但是这种做法让我感到不舒服。
我知道我可以调用
byte[] value = (byte[])dataReader["columnName"];

...这似乎完全在内部解决了长度问题。 然而,我正在使用一组复杂的代码生成模板,这些模板是围绕SqlDataReader.GetXXXX()方法构建的。所以我必须使用GetBytes并且需要了解其正确用法。


谢谢,我确实考虑使用SQL中的DATALENGTH,但这并不是一个可接受的解决方案。你可能能猜到,这都是数据访问层的一部分,因此在手写存储过程中强制约定以支持基本功能并不好。 - Martyn
@Martyn 在这里的时候,有一些专门的库可以确保您不需要编写繁琐的 ADO.NET 代码... "dapper"(在我非常偏见的看法中)非常适合隐藏这些问题,同时保持非常轻量级和不具侵入性。 - Marc Gravell
@Marc Gravell 我在这里维护一个遗留产品,因此提到的代码模板我不能太偏离。我真希望能使用现成的DAL或ORM!但这是你需要与我的老板讨论的问题 :) - Martyn
@TomTom 是的.. 如果有人正在编写存储过程,那么他们必须以特定的方式编写它,并包括 DATALENGTH 才能使其正常工作。这就是为什么那个想法在一段时间前被排除的原因。 - Martyn
@Martyn 但是你基本上告诉人们不要正确使用SQL Server。看,这就是SQL Server的工作方式,无论你喜欢与否 - 你要么这样做,要么有限制,或者在其他地方维护自己的长度字段。在SQL Server中编写存储过程而不使用SQL Server API = 不聪明,抱歉。 - TomTom
显示剩余2条评论
2个回答

47

处理 varbinary(max) 时,有两种情况:

  • 数据的长度适中
  • 数据的长度很大

GetBytes()用于第二种情况,当您使用CommandBehaviour.SequentialAccess确保您正在流式传输数据而不是缓冲数据时。特别地,在这种情况下,通常会在循环中写入(例如)流。例如:

// moderately sized buffer; 8040 is a SQL Server page, note
byte[] buffer = new byte[8040]; 
long offset = 0;
int read;
while((read = reader.GetBytes(col, offset, buffer, 0, buffer.Length)) > 0) {
    offset += read;
    destination.Write(buffer, 0, read); // push downstream
}

然而!如果你正在使用中等规模的数据,那么你原来的代码:

byte[] data = (byte[])reader[col];

没有任何问题!! 这种方法是没问题的,实际上在某些情况下Get*API是有问题的,其中一个显著的例子是GetChar()(提示:它不起作用)。

你已经有了使用Get*的现有代码是无关紧要的-在这种情况下,强制转换的方法是完全适当的。


1
谢谢,这是一个很棒的答案!在我的特定情况下,整个数据将始终被读入一个字节数组中,而不是缓冲或推入另一个流中。在这种情况下,手动编写读取循环与只调用一次GetBytes并将int.MaxValue作为长度传递之间是否存在性能优势? - Martyn
2
@Martyn ReadBytes 是一个流式 API;你永远不能传递一个大值并假设它已经读取了所有内容 - 你总是需要循环直到获得一个非正值。我的主要猜测(未经检查)是,在内部它已经知道了长度(至少在缓冲使用中),并且将分配一个正确大小的数组并直接块复制数据。如果你通过 GetBytes() 来完成,你需要将其写入 MemoryStream,然后在最后调用 ms.ToArray()。问题在于:额外的(不必要的)byte[] 分配。由你决定是否有问题。 - Marc Gravell
没错,当然是你说的对。循环是必须的,因为数据可能超过 int.MaxValue。感谢你的帮助 - 已接受。 - Martyn
4
@Martyn 不,你误解了我的意思。循环是必要的,因为即使底层数据有100,000个字节且缓冲区有1000个字节的空间,仅返回10个字节也是完全合法的。你必须循环,因为判断何时达到结尾的唯一方法是继续读取 - Marc Gravell

3
你可能可以做到这一点。在 MSDN 上找到的。可能它能够满足你的目的。
    // 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.
  bw.Write(outbyte, 0, (int)retval - 1);
  bw.Flush();
这个链接或者这个链接可以帮助你了解关于it技术的信息。
int ndx = rdr.GetOrdinal("<ColumnName>");
            if(!rdr.IsDBNull(ndx))
           {
            long size = rdr.GetBytes(ndx, 0, null, 0, 0);  //get the length of data
            byte[] values = new byte[size];

            int bufferSize = 1024;
            long bytesRead = 0;
            int curPos = 0;

            while (bytesRead < size)
            {
                bytesRead += rdr.GetBytes(ndx, curPos, values, curPos, bufferSize);
                curPos += bufferSize;
            }
           }

1
让我猜猜 - 你是直接从方法的文档中复制的吗?RTFM问题 ;) - TomTom
相信 MSDN 的核心。!! - Anand
1
谢谢,我之前在MSDN上看到过这个,虽然它展示了使用这种方法检索数据的明显方式,但没有提供任何背景信息或任何关于特定场景下正确使用的提示。 - Martyn
感谢提供第二个示例。我用它从SQLite中提取GIF blob,因为reader.GetValue返回的是字符串。 - spedsal

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