3

I've found lots of information on how to select a BLOB as a stream using CommandBehavior.SequentialAccess.

I'd like to stream the BLOB on an insert too (to avoid caching the BLOB as a byte array in memory), and I can't find any examples. I've found some documentation mentioning a .WRITE (expression,@Offset, @Length) syntax in the UPDATE T-SQL statement which is compatible with VARBINARY(MAX). So, I'm thinking of writing a class which can take a Stream and chunk it into the database using successive UPDATE (.WRITE) statements. Is this the right way to do this, or is there a better way?

Links to UPDATE.WRITE:

http://msdn.microsoft.com/en-us/library/ms178158(SQL.100).aspx

http://msdn.microsoft.com/en-us/library/ms177523(v=SQL.100).aspx

Links to selecting BLOBs using CommandBehavior.SequentialAccess:

http://msdn.microsoft.com/en-us/library/87z0hy49.aspx

Memory effective way to read BLOB data in C#/SQL 2005

Getting binary data using SqlDataReader

How to make streams from BLOBs available in plain old C# objects when using SqlDataReader?

Streaming VARBINARY data from SQL Server in C#

Here's a POC of using the .Write syntax:

DDL:

create database BlobTest
go
use blobtest
go

create table Blob
(
    Id bigint not null primary key identity(1,1),
    Data varbinary(max) not null default(0x)
)

C#:

using System.Data;
using System.Data.SqlClient;
using System.IO;
using System.Linq;

namespace ConsoleApplication1
{
    class Program
    {
        static void Main()
        {
            string pathToBigFile = "C:\\bigfile.big";
            int optimumBufferSizeForSql = 8040; //See https://stackoverflow.com/questions/5629991/how-can-i-generate-an-insert-script-for-a-table-with-a-varbinarymax-field

            long newBlobId = InitialiseNewBlobInSqlServer();

            using (Stream stream = new FileStream(  pathToBigFile, 
                                                    FileMode.Open, 
                                                    FileAccess.Read, 
                                                    FileShare.ReadWrite))
            {
                byte[] buffer = new byte[optimumBufferSizeForSql];

                while(true)
                {
                    int numberBytesRead = stream.Read(buffer, 0, optimumBufferSizeForSql);

                    if (numberBytesRead == 0)
                    {
                        //Done
                        break;
                    }

                    WriteBufferToSqlServer(
                        numberBytesRead == optimumBufferSizeForSql ? buffer : buffer.Take(numberBytesRead).ToArray(),
                        newBlobId);
                }
            }
        }

        static long InitialiseNewBlobInSqlServer()
        {
            using (SqlConnection conn = new SqlConnection("Data Source=localhost; Initial Catalog=BlobTest; Integrated Security=SSPI;"))
            using (SqlCommand command = new SqlCommand())
            {
                command.Connection = conn;
                command.CommandType = CommandType.Text;
                command.CommandText = "Insert into blob (Data) values (0x); select convert(bigint,Scope_identity());";

                conn.Open();
                return (long) command.ExecuteScalar();
            }
        }

        static void WriteBufferToSqlServer(byte[] data, long blobId)
        {
            using (SqlConnection conn = new SqlConnection("Data Source=localhost; Initial Catalog=BlobTest; Integrated Security=SSPI;"))
            using (SqlCommand command = new SqlCommand())
            {
                command.Connection = conn;
                command.CommandType = CommandType.Text;
                command.Parameters.AddWithValue("@id", blobId);
                command.Parameters.AddWithValue("@data", data);
                command.CommandText = "Update Blob set Data.Write(@data, null, null) where Id = @id;";

                conn.Open();
                command.ExecuteNonQuery();
            }
        }
    }
}
Community
  • 1
  • 1
Daniel James Bryars
  • 4,429
  • 3
  • 39
  • 57
  • SqlFileStream has better performance and much less load on the SQL DB, but require SQL2008 or higher. http://msdn.microsoft.com/en-us/library/system.data.sqltypes.sqlfilestream.aspx – Bengie Dec 05 '11 at 14:47
  • Thanks @Bengie, this is good, but I need to store the data inline in the database not in a FILESTREAM. – Daniel James Bryars Dec 05 '11 at 14:59

1 Answers1

1

You should be using RBS interface of SQL Server for working with blobs.

Muhammad Hasan Khan
  • 34,648
  • 16
  • 88
  • 131
  • Thanks @Hasan. This is quite interesting, but doesn't help me directly; for my requirements I want to keep the BLOB in the database. The RBS Team Blog does mention that they have written a FILESTREAM backed BlobStore in addition to the example FileStoreLibrary BlobStore. I'm downloading the Add On, and sample code so I can check out if they stream to a FileStream (which I doubt, because I'd expect them to use Win32 to access the FILESTREAM directly.) – Daniel James Bryars Dec 05 '11 at 14:16
  • So, I've had a look at the RBS code and the SQL for the FileStream BlobStore, and there is an option to store the data inline (ie in a VARBINARY(MAX) not a FILESTREAM) but when it's set for store_inline = true then RBS doesn't stream the data in (which is what I want.) Curiously they break up the BLOB into a maximum of 25 blocks, as seperate parameters to the sproc, and then .Write the data together in the sproc (rbs_fs_sp_store_blob_params) into a varbinary(max) and insert that in one go. – Daniel James Bryars Dec 05 '11 at 14:38
  • You want to store data in line and stream it. These two requirements don't go well together. Specially in your case of insert. – Muhammad Hasan Khan Dec 05 '11 at 15:06
  • Why "especially in your case of insert", ie what's the fundamental difference between inserting and selecting? – Daniel James Bryars Dec 05 '11 at 16:08
  • 1
    @DanielJamesBryars the fundamental difference is that while inserting you're creating a row in db while during reading the row is already there. You can take a year to stream it out on your will; doesn't bother the db much but while inserting it has to be as quick as possible. – Muhammad Hasan Khan Dec 05 '11 at 17:54
  • +1 @hasan. Your comment about "inserting" blobs has made me think much more about my scenario. It makes sense to me now why there isn't a streaming API for inserting a blob, and why there is only a "chunking" mechanism exposed; the DBMS only takes a lock out (even if only row level) during the upload of the chunk. It does mean I need another mechanism (rather like a FileSystem Write lock) "on top of" the database locking to signal that the "file" is being written to. I'll do this with a seperate column. Unless any forthcoming answers are posted today I'll mark yours as the answer. – Daniel James Bryars Dec 06 '11 at 10:07