sql >> Databáze >  >> RDS >> Sqlserver

Jak mohu serializovat velký graf objektu .NET do objektu SQL Server BLOB bez vytvoření velké vyrovnávací paměti?

Neexistuje žádná vestavěná funkce ADO.Net, která by to u velkých dat zvládla opravdu elegantně. Problém je dvojí:

  • neexistuje žádné rozhraní API, které by bylo možné „zapsat“ do příkazů SQL nebo parametrů jako do streamu. Typy parametrů, které přijímají stream (například FileStream ) přijměte stream ke ČTENÍ z něj, což nesouhlasí se serializační sémantikou write do potoka. Bez ohledu na to, jakým směrem to otočíte, skončíte s kopií celého serializovaného objektu v paměti, špatné.
  • i když by se výše uvedený bod vyřešil (a nelze), protokol TDS a způsob, jakým SQL Server přijímá parametry, nefungují dobře s velkými parametry, protože celý požadavek musí být nejprve přijat, než se spustí. a to by vytvořilo další kopie objektu uvnitř SQL Server.

Takže na to musíte opravdu přistupovat z jiného úhlu. Naštěstí existuje celkem snadné řešení. Trik je v použití vysoce efektivního UPDATE .WRITE syntaxi a předávat kusy dat jeden po druhém v řadě příkazů T-SQL. Toto je doporučený způsob MSDN, viz Úprava velkých (max.) dat v ADO.NET. Vypadá to složitě, ale ve skutečnosti je to triviální udělat a zapojit se do třídy Stream.

Třída BlobStream

Toto je chléb s máslem řešení. Třída odvozená ze streamu, která implementuje metodu Write jako volání syntaxe T-SQL BLOB WRITE. Přímo dopředu je na něm zajímavé pouze to, že musí sledovat první aktualizaci, protože UPDATE ... SET blob.WRITE(...) syntaxe by selhala v poli 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
 }

Použití BlobStreamu

Chcete-li použít tuto nově vytvořenou třídu streamu objektů blob, zapojte se do BufferedStream . Třída má triviální design, který zpracovává pouze zápis proudu do sloupce tabulky. Znovu použiji tabulku z jiného příkladu:

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

Přidám fiktivní objekt k serializaci:

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

Konečně skutečná serializace. Nejprve vložíme nový záznam do Uploads tabulku a poté vytvořte BlobStream na nově vložené ID a zavolejte serializaci přímo do tohoto streamu:

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

Pokud budete sledovat provádění tohoto jednoduchého vzorku, uvidíte, že nikde není vytvořen velký serializační proud. Vzorek alokuje pole [1024*1024], ale to je pro účely demo, aby bylo co serializovat. Tento kód se serializuje způsobem s vyrovnávací pamětí, kousek po bloku, pomocí doporučené velikosti aktualizace SQL Server BLOB 8040 bajtů najednou.



  1. FieldShield SDK

  2. Oracle Indexy a typy indexů v oracle s příkladem

  3. Příkaz ALTER TABLE byl v konfliktu s omezením FOREIGN KEY v SQL Server - SQL Sever / Výukový program TSQL, část 69

  4. Testování funkcí PostgreSQL, které spotřebovávají a vracejí rekurzor