sql >> Database teknologi >  >> RDS >> Sqlserver

Hvordan serialiserer jeg en stor graf af .NET objekt til en SQL Server BLOB uden at oprette en stor buffer?

Der er ingen indbygget ADO.Net funktionalitet til at håndtere dette virkelig yndefuldt for store data. Problemet er todelt:

  • der er ingen API til at 'skrive' ind i en SQL-kommando(r) eller parametre som i en strøm. De parametertyper, der accepterer en stream (såsom FileStream ) accepter strømmen for at LÆS fra det, hvilket ikke stemmer overens med serialiseringssemantikken for write ind i et vandløb. Lige meget hvilken vej du vender dette, ender du med en i hukommelsen kopi af hele det serialiserede objekt, dårligt.
  • selvom punktet ovenfor ville blive løst (og det kan det ikke), fungerer TDS-protokollen og den måde, SQL Server accepterer parametre på, ikke godt med store parametre, da hele anmodningen først skal modtages, før den lanceres til udførelse og dette ville skabe yderligere kopier af objektet inde i SQL Server.

Så du skal virkelig gribe det an fra en anden vinkel. Heldigvis er der en ret nem løsning. Tricket er at bruge den meget effektive UPDATE .WRITE syntaks og videregive bidder af data én efter én, i en række T-SQL-sætninger. Dette er den MSDN anbefalede måde, se Ændring af store værdier (max) data i ADO.NET. Dette ser kompliceret ud, men er faktisk trivielt at gøre og tilslutte til en Stream-klasse.

BlobStream-klassen

Dette er løsningens brød og smør. En Stream-afledt klasse, der implementerer Write-metoden som et kald til T-SQL BLOB WRITE-syntaksen. Lige frem, det eneste interessante ved det er, at det skal holde styr på den første opdatering, fordi UPDATE ... SET blob.WRITE(...) syntaks ville mislykkes i et NULL-felt:

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
 }

Brug af BlobStream

For at bruge denne nyoprettede blobstream-klasse tilslutter du en BufferedStream . Klassen har et trivielt design, der kun håndterer at skrive strømmen ind i en kolonne i en tabel. Jeg genbruger en tabel fra et andet eksempel:

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

Jeg tilføjer et dummy-objekt, der skal serialiseres:

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

Endelig den faktiske serialisering. Vi indsætter først en ny post i Uploads tabel, og opret derefter en BlobStream på det nyligt indsatte id og kalder serialiseringen direkte ind i denne strøm:

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

Hvis du overvåger udførelsen af ​​denne simple prøve, vil du se, at der ingen steder er skabt en stor serialiseringsstrøm. Eksemplet vil allokere arrayet [1024*1024], men det er for demoformål at have noget at serialisere. Denne kode serialiseres på en bufret måde, stykke for stykke, ved hjælp af SQL Server BLOB anbefalede opdateringsstørrelse på 8040 bytes ad gangen.



  1. Bedste praksis:.NET:Hvordan returnerer jeg PK mod en oracle-database?

  2. Udvikling af PostgreSQL til Windows, del 2

  3. Hvad er forskellen mellem Office 365 og Office 2016?

  4. Oracle ydeevne og tuning Quiz