Microsoft KB Archive/317034

= HOW TO: Read and Write a File to and from a BLOB Column by Using Chunking in ADO.NET and Visual Basic .NET =

Article ID: 317034

Article Last Modified on 12/26/2003

-

APPLIES TO


 * Microsoft ADO.NET 1.1
 * Microsoft ADO.NET 1.0
 * Microsoft Visual Basic .NET 2003 Standard Edition
 * Microsoft Visual Basic .NET 2002 Standard Edition
 * Microsoft SQL Server 2000 Standard Edition
 * Microsoft SQL Server 2000 64-bit Edition
 * Microsoft SQL Server 7.0 Standard Edition

-



This article was previously published under Q317034



For a Microsoft Visual C++ .NET version of this article, see 317044.

For a Microsoft Visual C# .NET version of this article, see 317043.



For a Microsoft Visual Basic 6.0 version of this article, see 194975.

This article refers to the following Microsoft .NET Framework Class Library namespaces:
 * System.Data
 * System.Data.SqlClient
 * System.Data.OleDb

IN THIS TASK

 * SUMMARY
 * Read Chunks from a BLOB Column
 * Write Chunks to a BLOB Column
 * Create the Sample Application
 * Troubleshooting
 * REFERENCES



SUMMARY
This step-by-step article describes how to use the Microsoft SQL Server READTEXT and UPDATETEXT statements to read and write data from BLOB (LongVarBinary) columns in a database table.

Unlike with ADO 2.6 and later, ADO.NET does not support reading and writing BLOB objects by using Stream objects. ADO.NET data providers do not have GetChunk and AppendChunk methods available to the Data Access Object (DAO) and ActiveX Data Objects (ADO) Recordset objects. To read a BLOB field for in-memory manipulation, you can use a DataReader object to select the row, or you can cache the data in a DataSet.

However, if you want to stream the data to a different medium, such as disk or Web response, then you can read the BLOB from the server in smaller chunks to minimize the amount of memory that the process consumes. This is especially important in Web Forms applications, where you may have multiple concurrent applications running at the same time and you want to conserve memory resources.

NOTES:
 * Examples are shown for both the SqlClient .NET Data Provider and the OLE DB .NET Data Provider. The only differences, apart from class names, are the connection strings and the declaration of SQL parameters. The fundamental technique to retrieve the READTEXT and UPDATETEXT statements is the same.
 * The Test record in the Categories table of the Northwind sample database does not exist. You must use the Server Explorer or another tool to add a record with the CategoryName &quot;Test&quot;.

back to the top

Read Chunks from a BLOB Column
The following functions use the SQL Server READTEXT statement and DataReader object to retrieve a portion of the BLOB value in a single-row, single-column rowset. Two commands perform this task: the first command retrieves the size of the BLOB field and a pointer to its location; the second command executes the READTEXT command, which retrieves the chunk of data into a Byte array, and then increments an Offset. The System.IO.Filesream object writes the Byte array to disk.

NOTE: In Visual Basic, you must subtract 1 from the BUFFER_LENGTH when declaring the Byte array because Visual Basic declares the upper bound of the array as opposed to the length. In other languages, such as C# or JScript, use the length without subtracting 1. Also, make sure that you modify your connection string for your environment. Private Sub SqlChunkBLOB2File(ByVal DestFilePath As String) Dim PictureCol As Integer = 0 ' position of Picture column in DataReader Dim BUFFER_LENGTH As Integer = 32768 ' chunk size Dim cn As New SqlConnection(&quot;Data Source=mySQLServer;Integrated Security=SSPI;Initial Catalog=Northwind;&quot;) ' ' Make sure that Photo is non-NULL and return TEXTPTR to it. ' Dim cmdGetPointer As New SqlCommand(&quot;SELECT @Pointer=TEXTPTR(Picture), @Length=DataLength(Picture) FROM Categories WHERE CategoryName='Test'&quot;, cn) Dim PointerOutParam As SqlParameter = cmdGetPointer.Parameters.Add(&quot;@Pointer&quot;, SqlDbType.VarBinary, 100) PointerOutParam.Direction = ParameterDirection.Output Dim LengthOutParam As SqlParameter = cmdGetPointer.Parameters.Add(&quot;@Length&quot;, SqlDbType.Int) LengthOutParam.Direction = ParameterDirection.Output cn.Open cmdGetPointer.ExecuteNonQuery If PointerOutParam.Value Is DBNull.Value Then cn.Close ' Add code to deal with NULL BLOB. Exit Sub End If ' ' Set up READTEXT command, parameters, and open BinaryReader. ' Dim cmdReadBinary As New SqlCommand(&quot;READTEXT Categories.Picture @Pointer @Offset @Size HOLDLOCK&quot;, cn) Dim PointerParam As SqlParameter = cmdReadBinary.Parameters.Add(&quot;@Pointer&quot;, SqlDbType.Binary, 16) Dim OffsetParam As SqlParameter = cmdReadBinary.Parameters.Add(&quot;@Offset&quot;, SqlDbType.Int) Dim SizeParam As SqlParameter = cmdReadBinary.Parameters.Add(&quot;@Size&quot;, SqlDbType.Int) Dim dr As SqlDataReader Dim fs As New System.IO.FileStream(DestFilePath, IO.FileMode.Create, IO.FileAccess.Write) Dim Offset As Integer = 0 OffsetParam.Value = Offset Dim Buffer(BUFFER_LENGTH - 1) As Byte

' ' Read buffer full of data and write to the file stream. ' Do    PointerParam.Value = PointerOutParam.Value '   ' Calculate the buffer size - may be less than BUFFER_LENGTH for the last block. '   If Offset + BUFFER_LENGTH >= LengthOutParam.Value Then SizeParam.Value = LengthOutParam.Value - Offset Else SizeParam.Value = BUFFER_LENGTH End If   dr = cmdReadBinary.ExecuteReader(CommandBehavior.SingleResult) dr.Read dr.GetBytes(PictureCol, 0, Buffer, 0, SizeParam.Value) dr.Close fs.Write(Buffer, 0, SizeParam.Value) Offset += SizeParam.Value OffsetParam.Value = Offset Loop Until Offset >= LengthOutParam.Value

fs.Close cn.Close End Sub

Private Sub OleDbChunkBLOB2File(ByVal DestFilePath As String) Dim PictureCol As Integer = 0 ' position of picture column in DataReader Dim BUFFER_LENGTH As Integer = 32768 ' chunk size Dim cn As New OleDbConnection(&quot;Provider=SQLOLEDB;Data Source=mySQLServer;Integrated Security=SSPI;Initial Catalog=Northwind;&quot;) ' ' Make sure that Photo is non-NULL and return TEXTPTR to it. ' Dim cmdGetPointer As New OleDbCommand(&quot;SELECT ?=TEXTPTR(Picture), ?=DataLength(Picture) FROM Categories WHERE CategoryName='Test'&quot;, cn) Dim PointerOutParam As OleDbParameter = cmdGetPointer.Parameters.Add(&quot;@Pointer&quot;, OleDbType.VarBinary, 100) PointerOutParam.Direction = ParameterDirection.Output Dim LengthOutParam As OleDbParameter = cmdGetPointer.Parameters.Add(&quot;@Length&quot;, OleDbType.Integer) LengthOutParam.Direction = ParameterDirection.Output cn.Open cmdGetPointer.ExecuteNonQuery If PointerOutParam.Value Is DBNull.Value Then cn.Close ' Add code to deal with NULL BLOB. Exit Sub End If ' ' Set up READTEXT command, parameters, and open BinaryReader. ' Dim cmdReadBinary As New OleDbCommand(&quot;READTEXT Categories.Picture ? ? ? HOLDLOCK&quot;, cn) Dim PointerParam As OleDbParameter = cmdReadBinary.Parameters.Add(&quot;@Pointer&quot;, OleDbType.Binary, 16) Dim OffsetParam As OleDbParameter = cmdReadBinary.Parameters.Add(&quot;@Offset&quot;, OleDbType.Integer) Dim SizeParam As OleDbParameter = cmdReadBinary.Parameters.Add(&quot;@Size&quot;, OleDbType.Integer) Dim dr As OleDbDataReader Dim fs As New System.IO.FileStream(DestFilePath, IO.FileMode.Create, IO.FileAccess.Write) Dim Offset As Integer = 0 OffsetParam.Value = Offset Dim Buffer(BUFFER_LENGTH - 1) As Byte ' ' Read buffer full of data and write to the file stream. ' Do    PointerParam.Value = PointerOutParam.Value '   ' Calculate buffer size - may be less than BUFFER_LENGTH for the last block. '   If Offset + BUFFER_LENGTH >= LengthOutParam.Value Then SizeParam.Value = LengthOutParam.Value - Offset Else SizeParam.Value = BUFFER_LENGTH End If   dr = cmdReadBinary.ExecuteReader(CommandBehavior.SingleResult) dr.Read dr.GetBytes(PictureCol, 0, Buffer, 0, SizeParam.Value) dr.Close fs.Write(Buffer, 0, SizeParam.Value) Offset += SizeParam.Value OffsetParam.Value = Offset Loop Until Offset >= LengthOutParam.Value

fs.Close cn.Close End Sub back to the top

Write Chunks to a BLOB Column
The following functions use the Command object, the Parameter object, and the SQL Server UPDATETEXT statement to write chunks of data from a Byte array to a BLOB column. The BLOB column cannot be null with this method, therefore a single byte is assigned to the column before the function retrieves the TEXTPTR.

On the first execution of the UPDATETEXT statement, the DeleteParam.Value is set to 1 to delete the existing byte from the column before the function inserts the chunk. This assignment prevents extraneous data from being appended to the BLOB. The UPDATETEXT statement is executed multiple times, and the offset increments with the size of the buffer after each call.

NOTE: In Visual Basic, you must subtract 1 from the length of the file when declaring the Byte array because Visual Basic declares the upper bound of the array as opposed to the length. In other languages, such as C# or JScript, use the length without subtracting 1. Also, make sure that you modify your connection string for your environment. Private Sub ChunkFile2SqlBLOB(ByVal SourceFilepath As String) Dim BUFFER_LENGTH As Integer = 32768 ' chunk size Dim cn As New SqlConnection(&quot;Data Source=mySQLServer;Integrated Security=SSPI;Initial Catalog=Northwind;&quot;) ' ' Make sure that Photo is non-NULL and return TEXTPTR to it. ' Dim cmdGetPointer As New SqlCommand(&quot;SET NOCOUNT ON;UPDATE Categories SET Picture = 0x0 WHERE CategoryName='Test';&quot; & _    &quot;SELECT @Pointer=TEXTPTR(Picture) FROM Categories WHERE CategoryName='Test'&quot;, cn) Dim PointerOutParam As SqlParameter = cmdGetPointer.Parameters.Add(&quot;@Pointer&quot;, SqlDbType.VarBinary, 100) PointerOutParam.Direction = ParameterDirection.Output cn.Open cmdGetPointer.ExecuteNonQuery ' ' Set up UPDATETEXT command, parameters, and open BinaryReader. ' Dim cmdUploadBinary As New SqlCommand(&quot;UPDATETEXT Categories.Picture @Pointer @Offset @Delete WITH LOG @Bytes&quot;, cn) Dim PointerParam As SqlParameter = cmdUploadBinary.Parameters.Add(&quot;@Pointer&quot;, SqlDbType.Binary, 16) Dim OffsetParam As SqlParameter = cmdUploadBinary.Parameters.Add(&quot;@Offset&quot;, SqlDbType.Int) Dim DeleteParam As SqlParameter = cmdUploadBinary.Parameters.Add(&quot;@Delete&quot;, SqlDbType.Int) DeleteParam.Value = 1 ' delete 0x0 character Dim BytesParam As SqlParameter = cmdUploadBinary.Parameters.Add(&quot;@Bytes&quot;, SqlDbType.Binary, BUFFER_LENGTH) Dim fs As New IO.FileStream(SourceFilePath, IO.FileMode.Open, IO.FileAccess.Read) Dim br As New IO.BinaryReader(fs) Dim Offset As Integer = 0 OffsetParam.Value = Offset

' ' Read buffer full of data and execute the UPDATETEXT statement. ' Dim Buffer As Byte = br.ReadBytes(BUFFER_LENGTH) Do While Buffer.Length > 0 PointerParam.Value = PointerOutParam.Value BytesParam.Value = Buffer cmdUploadBinary.ExecuteNonQuery DeleteParam.Value = 0 ' don't delete any other data Offset += Buffer.Length OffsetParam.Value = Offset Buffer = br.ReadBytes(BUFFER_LENGTH) Loop

br.Close fs.Close cn.Close End Sub

Private Sub ChunkFile2OleDbBLOB(ByVal SourceFilePath As String) Dim BUFFER_LENGTH As Integer = 32768 ' chunk size Dim cn As New OleDbConnection(&quot;Provider=SQLOLEDB;Data Source=mySQLServer;Integrated Security=SSPI;Initial Catalog=Northwind;&quot;) ' ' Make sure that Photo is non-NULL and return TEXTPTR to it. ' Dim cmdGetPointer As New OleDbCommand(&quot;SET NOCOUNT ON;UPDATE Categories SET Picture = 0x0 WHERE CategoryName='Test';&quot; & _     &quot;SELECT ?=TEXTPTR(Picture) FROM Categories WHERE CategoryName='Test'&quot;, cn) Dim PointerOutParam As OleDbParameter = cmdGetPointer.Parameters.Add(&quot;@Pointer&quot;, OleDbType.VarBinary, 100) PointerOutParam.Direction = ParameterDirection.Output cn.Open cmdGetPointer.ExecuteNonQuery ' ' Set up UPDATETEXT command, parameters, and open BinaryReader. ' Dim cmdUploadBinary As New OleDbCommand(&quot;UPDATETEXT Categories.Picture ? ? ? WITH LOG ?&quot;, cn) Dim PointerParam As OleDbParameter = cmdUploadBinary.Parameters.Add(&quot;@Pointer&quot;, OleDbType.Binary, 16) Dim OffsetParam As OleDbParameter = cmdUploadBinary.Parameters.Add(&quot;@Offset&quot;, OleDbType.Integer) Dim DeleteParam As OleDbParameter = cmdUploadBinary.Parameters.Add(&quot;@Delete&quot;, OleDbType.Integer) DeleteParam.Value = 1 ' delete 0x0 character Dim BytesParam As OleDbParameter = cmdUploadBinary.Parameters.Add(&quot;@Bytes&quot;, OleDbType.Binary, BUFFER_LENGTH) Dim fs As New IO.FileStream(SourceFilePath, IO.FileMode.Open, IO.FileAccess.Read) Dim br As New IO.BinaryReader(fs) Dim Offset As Integer = 0 OffsetParam.Value = Offset

' ' Read buffer full of data and execute the UPDATETEXT statement. ' Dim Buffer As Byte = br.ReadBytes(BUFFER_LENGTH) Do While Buffer.Length > 0 PointerParam.Value = PointerOutParam.Value BytesParam.Value = Buffer cmdUploadBinary.ExecuteNonQuery DeleteParam.Value = 0 ' don't delete any other data Offset += Buffer.Length OffsetParam.Value = Offset Buffer = br.ReadBytes(BUFFER_LENGTH) Loop

br.Close fs.Close cn.Close End Sub back to the top

Create the Sample Application
The following sample application demonstrates how to use the SqlClient .NET Data Provider versions of the functions presented previously in this article.  Start Visual Studio .NET, and use the Server Explorer or use another database tool of your choice. Connect to your Northwind database. Open the Categories table and then add a new record with a CategoryName of &quot;Test.&quot; Create a new Visual Basic .NET Windows Form application. Add two Button controls with the following properties to the form:

Name: btnLoadFromFile

Text: Load from file

Name: btnSaveToFile

Text: Save to file

  Switch to Code view, and then add the following Imports declaration at the top of the Code window: Imports System.Data.SqlClient   Add the following sample code to the Click event handler for the Load button: ChunkFile2SqlBLOB(&quot;c:\testfile.dat&quot;) 'ChunkFile2OleDbBLOB(&quot;C:\test.dat&quot;) ' uncomment to use OLEDB NOTE: Change the source file path to point to the data file that you want to copy into the SQL Server table.

  Change the source file path to point to the data file that you want to copy into the SQL Server table. Add the following sample code to the Click event handler for the Load button: SqlChunkBLOB2File(&quot;c:\copyoftestfile.dat&quot;) 'OleDbChunkBLOB2File(&quot;C:\copyoftestfileoledb.dat&quot;) ' uncomment to use OLEDB NOTE: You may want to change the destination file name to one that does not exist on your computer.

 Change the source file path to point to the data file that you want to copy into the SQL Server table. Paste the ChunkFile2SqlBLOB and SqlChunkBLOB2File procedures from the article text into your form code.</li> Change the source file path to point to the data file that you want to copy into the SQL Server table. Run the application. Click Load, and then click Save. You should see a new file on your hard disk that is identical to the source file.</li></ol>

back to the top

Troubleshooting
<ul> The sample code in this article may not be suitable for use against the LongVarChar or LongVarWChar columns without modification.</li> Remember to modify the connection string and SQL statements to suit your own server.</li> Add error checking in case your query returns no records.</li>  You must add an Imports statement at the top of the code file to expose the appropriate namespace: Imports System.Data.SqlClient  ' needed if using &quot;Sql&quot; functions Imports System.Data.OleDb      ' needed if using &quot;OleDb&quot; functions </li> Subtract 1 from the length of the data only when you declare the array in Visual Basic .NET. If you translate the code to a different language, you probably should not subtract 1.</li> READTEXT and UPDATETEXT are specific to Microsoft SQL Server. If you use a different database system, you may have similar commands available to you. If you do not have similar commands available, see the following Knowledge Base articles, which describe reading and writing BLOB data without chunking.

316887 HOW TO: Read and Write a File to and from a BLOB Column by Using ADO.NET and Visual Basic .NET

317017 HOW TO: Read and Write a File to and from a BLOB Column by Using ADO.NET and Visual C++ .NET

317016 HOWTO: Read and Write a File to/from a BLOB Column using ADO.NET and Visual C# .NET

</li></ul>

back to the top

<div class="references_section">