Sqlserver
 sql >> Teknologi Basis Data >  >> RDS >> Sqlserver

Bagaimana cara saya membuat serial grafik besar objek .NET menjadi SQL Server BLOB tanpa membuat buffer besar?

Tidak ada fungsionalitas ADO.Net bawaan untuk menangani ini dengan sangat baik untuk data besar. Masalahnya ada dua:

  • tidak ada API untuk 'menulis' ke dalam perintah SQL atau parameter seperti ke dalam aliran. Jenis parameter yang menerima aliran (seperti FileStream ) menerima aliran untuk BACA dari itu, yang tidak setuju dengan semantik serialisasi tulis ke dalam aliran. Tidak peduli ke mana Anda mengubah ini, Anda berakhir dengan salinan dalam memori dari seluruh objek serial, buruk.
  • bahkan jika poin di atas dapat diselesaikan (dan tidak dapat diselesaikan), protokol TDS dan cara SQL Server menerima parameter tidak berfungsi dengan baik dengan parameter besar karena seluruh permintaan harus diterima terlebih dahulu sebelum diluncurkan ke eksekusi dan ini akan membuat salinan tambahan dari objek di dalam SQL Server.

Jadi, Anda benar-benar harus mendekati ini dari sudut yang berbeda. Untungnya, ada solusi yang cukup mudah. Triknya adalah dengan menggunakan UPDATE .WRITE yang sangat efisien sintaks dan meneruskan potongan data satu per satu, dalam serangkaian pernyataan T-SQL. Ini adalah cara yang disarankan MSDN, lihat Memodifikasi Data Nilai Besar (maks) di ADO.NET. Ini terlihat rumit, tetapi sebenarnya sepele untuk dilakukan dan dihubungkan ke kelas Stream.

Kelas BlobStream

Ini adalah roti dan mentega dari solusinya. Kelas turunan Stream yang mengimplementasikan metode Write sebagai panggilan ke sintaks T-SQL BLOB WRITE. Terus terang, satu-satunya hal yang menarik tentang itu adalah ia harus melacak pembaruan pertama karena UPDATE ... SET blob.WRITE(...) sintaks akan gagal pada bidang 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
 }

Menggunakan BlobStream

Untuk menggunakan kelas aliran blob yang baru dibuat ini, Anda mencolokkan ke BufferedStream . Kelas memiliki desain sepele yang hanya menangani penulisan aliran ke dalam kolom tabel. Saya akan menggunakan kembali tabel dari contoh lain:

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

Saya akan menambahkan objek dummy untuk diserialkan:

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

Akhirnya, serialisasi yang sebenarnya. Pertama-tama kita akan memasukkan record baru ke dalam Uploads tabel, lalu buat BlobStream pada Id yang baru dimasukkan dan panggil serialisasi langsung ke aliran ini:

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();
    }
}

Jika Anda memantau eksekusi sampel sederhana ini, Anda akan melihat bahwa tidak ada aliran serialisasi besar yang dibuat. Sampel akan mengalokasikan larik [1024*1024] tetapi itu untuk tujuan demo agar memiliki sesuatu untuk diserialisasi. Kode ini bersambung dengan cara buffer, potongan demi potongan, menggunakan SQL Server BLOB yang direkomendasikan ukuran pembaruan 8040 byte sekaligus.



  1. Database
  2.   
  3. Mysql
  4.   
  5. Oracle
  6.   
  7. Sqlserver
  8.   
  9. PostgreSQL
  10.   
  11. Access
  12.   
  13. SQLite
  14.   
  15. MariaDB
  1. Cara Menghentikan/Memulai Antrian Surat Database di SQL Server (T-SQL)

  2. SQL Query - Menggabungkan Hasil menjadi Satu String

  3. Indeks Hilang SQL Server

  4. Menulis file csv ke database SQL Server menggunakan python

  5. Kapan dan bagaimana menggunakan klausa SQL PARTITION BY