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.