Sqlserver
 sql >> Baza danych >  >> RDS >> Sqlserver

Jak serializować duży wykres obiektu .NET do obiektu BLOB programu SQL Server bez tworzenia dużego bufora?

Nie ma wbudowanej funkcji ADO.Net, która mogłaby z wdziękiem obsłużyć to w przypadku dużych danych. Problem jest dwojaki:

  • nie ma interfejsu API, który można by „zapisać” w poleceniach SQL lub parametrach, tak jak w strumieniu. Typy parametrów, które akceptują strumień (np. FileStream ) zaakceptuj strumień, aby CZYTAĆ z niego, co nie zgadza się z semantyką serializacji write w strumień. Bez względu na to, w jaki sposób to zmienisz, skończysz z kopią w pamięci całego zserializowanego obiektu, źle.
  • nawet jeśli powyższy punkt zostałby rozwiązany (a nie może być), protokół TDS i sposób, w jaki SQL Server akceptuje parametry, nie działają dobrze z dużymi parametrami, ponieważ całe żądanie musi zostać najpierw odebrane, zanim zostanie uruchomione a to stworzyłoby dodatkowe kopie obiektu wewnątrz SQL Server.

Więc naprawdę musisz podejść do tego z innej perspektywy. Na szczęście istnieje dość proste rozwiązanie. Sztuką jest użycie wysoce wydajnego UPDATE .WRITE składni i przekazywać fragmenty danych jeden po drugim, w serii instrukcji T-SQL. Jest to zalecany przez MSDN sposób, zobacz Modyfikowanie danych o dużej wartości (maks.) w ADO.NET. Wygląda to na skomplikowane, ale w rzeczywistości jest trywialne do zrobienia i podłączenia do klasy Stream.

Klasa BlobStream

To jest chleb i masło rozwiązania. Klasa pochodna Stream, która implementuje metodę Write jako wywołanie składni T-SQL BLOB WRITE. Prosto, jedyną interesującą rzeczą jest to, że musi śledzić pierwszą aktualizację, ponieważ UPDATE ... SET blob.WRITE(...) składnia nie powiedzie się w polu 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
 }

Korzystanie z BlobStream

Aby użyć tej nowo utworzonej klasy strumienia obiektów blob, podłączasz do BufferedStream . Klasa ma banalny projekt, który obsługuje tylko zapisywanie strumienia w kolumnie tabeli. Ponownie użyję tabeli z innego przykładu:

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

Dodam fikcyjny obiekt do serializacji:

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

Wreszcie właściwa serializacja. Najpierw wstawimy nowy rekord do Uploads tabeli, a następnie utwórz BlobStream na nowo wstawionym identyfikatorze i wywołaj serializację bezpośrednio do tego strumienia:

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

Jeśli monitorujesz wykonanie tego prostego przykładu, zobaczysz, że nigdzie nie został utworzony duży strumień serializacji. Przykład przydzieli tablicę [1024*1024], ale jest to przeznaczone do celów demonstracyjnych, aby mieć coś do serializacji. Ten kod jest serializowany w sposób buforowany, porcja po porcji, przy użyciu zalecanego rozmiaru aktualizacji programu SQL Server BLOB wynoszącego 8040 bajtów na raz.



  1. Database
  2.   
  3. Mysql
  4.   
  5. Oracle
  6.   
  7. Sqlserver
  8.   
  9. PostgreSQL
  10.   
  11. Access
  12.   
  13. SQLite
  14.   
  15. MariaDB
  1. LIKE vs CONTAINS na SQL Server

  2. Jak uzyskać ciąg połączenia z bazy danych

  3. Wstawianie wierszy do tabeli z tylko jedną kolumną IDENTITY

  4. SQL Server konwertujący varbinary na string

  5. Jak wygenerować instrukcję dodawania kolumny dla wszystkich tabel w bazie danych w programie SQL Server — część samouczka SQL Server / T-SQL 49