sql >> Database >  >> RDS >> Sqlserver

Hoe kan ik een grote grafiek van een .NET-object serialiseren in een SQL Server BLOB zonder een grote buffer te maken?

Er is geen ingebouwde ADO.Net-functionaliteit om dit echt gracieus af te handelen voor grote gegevens. Het probleem is tweeledig:

  • er is geen API om in een SQL-opdracht(en) of parameters te 'schrijven' zoals in een stream. De parametertypen die een stream accepteren (zoals FileStream ) accepteer de stream om LEES ervan, wat niet overeenkomt met de serialisatie-semantiek van write in een stroom. Het maakt niet uit hoe je dit draait, je krijgt uiteindelijk een kopie in het geheugen van het hele geserialiseerde object, slecht.
  • zelfs als het bovenstaande punt zou worden opgelost (en dat kan het niet zijn), werken het TDS-protocol en de manier waarop SQL Server parameters accepteert niet goed met grote parameters, aangezien het volledige verzoek eerst moet worden ontvangen voordat het in uitvoering wordt gestart en dit zou extra kopieën van het object in SQL Server maken.

Je moet dit dus echt vanuit een andere hoek benaderen. Gelukkig is er een vrij eenvoudige oplossing. De truc is om de zeer efficiënte UPDATE .WRITE . te gebruiken syntaxis en geef de stukjes gegevens één voor één door in een reeks T-SQL-instructies. Dit is de door MSDN aanbevolen manier, zie Grote waarde (max.) gegevens wijzigen in ADO.NET. Dit ziet er ingewikkeld uit, maar is eigenlijk triviaal om te doen en aan te sluiten op een Stream-klasse.

De BlobStream-klasse

Dit is het brood en de boter van de oplossing. Een van Stream afgeleide klasse die de Write-methode implementeert als een aanroep van de T-SQL BLOB WRITE-syntaxis. Rechttoe rechtaan, het enige interessante eraan is dat het de eerste update moet bijhouden omdat de UPDATE ... SET blob.WRITE(...) syntaxis zou mislukken op een NULL-veld:

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
 }

De BlobStream gebruiken

Om deze nieuw gemaakte blobstream-klasse te gebruiken, sluit je aan op een BufferedStream . De klasse heeft een triviaal ontwerp dat alleen het schrijven van de stream in een kolom van een tabel afhandelt. Ik zal een tabel uit een ander voorbeeld hergebruiken:

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

Ik zal een dummy-object toevoegen dat moet worden geserialiseerd:

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

Eindelijk, de eigenlijke serialisatie. We zullen eerst een nieuw record invoegen in de Uploads tabel en maak vervolgens een BlobStream op de nieuw ingevoegde ID en roep de serialisatie rechtstreeks in deze stream:

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

Als je de uitvoering van dit eenvoudige voorbeeld volgt, zul je zien dat nergens een grote serialisatiestroom wordt gemaakt. Het voorbeeld zal de array van [1024*1024] toewijzen, maar dat is voor demo-doeleinden om iets te serialiseren. Deze code serialiseert op een gebufferde manier, stuk voor stuk, met behulp van de door SQL Server BLOB aanbevolen updategrootte van 8040 bytes per keer.



  1. Beste equivalent voor IsInteger in SQL Server

  2. Hoe EXISTS Logical Operator te gebruiken in SQL Server - SQL Server / TSQL Tutorial Part 125

  3. Top 9 nuttige Oracle Apps Printer-query's

  4. Genereer willekeurige int-waarde van 3 tot 6