Sqlserver
 sql >> база данни >  >> RDS >> Sqlserver

Как да сериализирам голяма графика на .NET обект в SQL Server BLOB, без да създавам голям буфер?

Няма вградена ADO.Net функционалност, която да се справя наистина грациозно с големи данни. Проблемът е два пъти:

  • няма API за „записване“ в SQL команда(и) или параметри като в поток. Типовете параметри, които приемат поток (като FileStream ) приемете потока за ЧЕТЕНЕ от него, което не е в съгласие със семантиката на сериализация на write в поток. Независимо по какъв начин го включите, в крайна сметка ще получите копие в паметта на целия сериализиран обект, лошо.
  • дори и точката по-горе да бъде решена (а не може да бъде), протоколът TDS и начинът, по който SQL Server приема параметри, не работят добре с големи параметри, тъй като цялата заявка трябва първо да бъде получена, преди да бъде стартирана в изпълнение и това ще създаде допълнителни копия на обекта в SQL Server.

Така че наистина трябва да подходите към това от различен ъгъл. За щастие има доста лесно решение. Номерът е да използвате високоефективния UPDATE .WRITE синтаксис и предават парчетата данни една по една в серия от T-SQL оператори. Това е препоръчания от MSDN начин, вижте Промяна на данни с големи стойности (макс.) в ADO.NET. Това изглежда сложно, но всъщност е тривиално да се направи и да се включи в клас Stream.

Класът BlobStream

Това е хлябът и маслото на разтвора. Клас, извлечен от поток, който реализира метода Write като извикване на синтаксиса на T-SQL BLOB WRITE. Направо, единственото интересно нещо в него е, че трябва да следи първата актуализация, защото UPDATE ... SET blob.WRITE(...) синтаксисът ще се провали в поле NULL:

class BlobStream: Stream
{
    private SqlCommand cmdAppendChunk;
    private SqlCommand cmdFirstChunk;
    private SqlConnection connection;
    private SqlTransaction transaction;

    private SqlParameter paramChunk;
    private SqlParameter paramLength;

    private long offset;

    public BlobStream(
        SqlConnection connection,
        SqlTransaction transaction,
        string schemaName,
        string tableName,
        string blobColumn,
        string keyColumn,
        object keyValue)
    {
        this.transaction = transaction;
        this.connection = connection;
        cmdFirstChunk = new SqlCommand(String.Format(@"
UPDATE [{0}].[{1}]
    SET [{2}] = @firstChunk
    WHERE [{3}] = @key"
            ,schemaName, tableName, blobColumn, keyColumn)
            , connection, transaction);
        cmdFirstChunk.Parameters.AddWithValue("@key", keyValue);
        cmdAppendChunk = new SqlCommand(String.Format(@"
UPDATE [{0}].[{1}]
    SET [{2}].WRITE(@chunk, NULL, NULL)
    WHERE [{3}] = @key"
            , schemaName, tableName, blobColumn, keyColumn)
            , connection, transaction);
        cmdAppendChunk.Parameters.AddWithValue("@key", keyValue);
        paramChunk = new SqlParameter("@chunk", SqlDbType.VarBinary, -1);
        cmdAppendChunk.Parameters.Add(paramChunk);
    }

    public override void Write(byte[] buffer, int index, int count)
    {
        byte[] bytesToWrite = buffer;
        if (index != 0 || count != buffer.Length)
        {
            bytesToWrite = new MemoryStream(buffer, index, count).ToArray();
        }
        if (offset == 0)
        {
            cmdFirstChunk.Parameters.AddWithValue("@firstChunk", bytesToWrite);
            cmdFirstChunk.ExecuteNonQuery();
            offset = count;
        }
        else
        {
            paramChunk.Value = bytesToWrite;
            cmdAppendChunk.ExecuteNonQuery();
            offset += count;
        }
    }

    // Rest of the abstract Stream implementation
 }

Използване на BlobStream

За да използвате този новосъздадени blob stream клас, вие включвате в BufferedStream . Класът има тривиален дизайн, който обработва само записването на потока в колона на таблица. Ще използвам повторно таблица от друг пример:

CREATE TABLE [dbo].[Uploads](
    [Id] [int] IDENTITY(1,1) NOT NULL,
    [FileName] [varchar](256) NULL,
    [ContentType] [varchar](256) NULL,
    [FileData] [varbinary](max) NULL)

Ще добавя фиктивен обект, който да бъде сериализиран:

[Serializable]
class HugeSerialized
{
    public byte[] theBigArray { get; set; }
}

И накрая, действителната сериализация. Първо ще вмъкнем нов запис в Uploads таблица, след което създайте BlobStream върху нововмъкнатия идентификатор и извикайте сериализацията направо в този поток:

using (SqlConnection conn = new SqlConnection(Settings.Default.connString))
{
    conn.Open();
    using (SqlTransaction trn = conn.BeginTransaction())
    {
        SqlCommand cmdInsert = new SqlCommand(
@"INSERT INTO dbo.Uploads (FileName, ContentType)
VALUES (@fileName, @contentType);
SET @id = SCOPE_IDENTITY();", conn, trn);
        cmdInsert.Parameters.AddWithValue("@fileName", "Demo");
        cmdInsert.Parameters.AddWithValue("@contentType", "application/octet-stream");
        SqlParameter paramId = new SqlParameter("@id", SqlDbType.Int);
        paramId.Direction = ParameterDirection.Output;
        cmdInsert.Parameters.Add(paramId);
        cmdInsert.ExecuteNonQuery();

        BlobStream blob = new BlobStream(
            conn, trn, "dbo", "Uploads", "FileData", "Id", paramId.Value);
        BufferedStream bufferedBlob = new BufferedStream(blob, 8040);

        HugeSerialized big = new HugeSerialized { theBigArray = new byte[1024 * 1024] };
        BinaryFormatter bf = new BinaryFormatter();
        bf.Serialize(bufferedBlob, big);

        trn.Commit();
    }
}

Ако наблюдавате изпълнението на тази проста извадка, ще видите, че никъде не е създаден голям поток за сериализация. Пробата ще разпредели масива от [1024*1024], но това е за демонстрационни цели, за да има нещо за сериализиране. Този код се сериализира по буфериран начин, парче по парче, използвайки препоръчания размер за актуализация на SQL Server BLOB от 8040 байта наведнъж.



  1. Database
  2.   
  3. Mysql
  4.   
  5. Oracle
  6.   
  7. Sqlserver
  8.   
  9. PostgreSQL
  10.   
  11. Access
  12.   
  13. SQLite
  14.   
  15. MariaDB
  1. DateTime2 срещу DateTime в SQL Server

  2. Как да определим броя на дните в месеца в SQL Server?

  3. Как да разберете дали изчислената колона е детерминистична в SQL Server

  4. SQL Server - Връщане на стойност след INSERT

  5. Разбиране на SQL Server ALTER TABLE ADD COLUMN Инструкция