Det finns ingen inbyggd ADO.Net-funktion för att hantera detta riktigt graciöst för stora data. Problemet är tvåfaldigt:
- det finns inget API att "skriva" till ett eller flera SQL-kommandon eller parametrar som i en ström. De parametertyper som accepterar en ström (som
FileStream
) acceptera strömmen för att LÄSA från det, vilket inte överensstämmer med serialiseringssemantiken för write in i en bäck. Oavsett vilket håll du vänder på det här, får du en kopia i minnet av hela det serialiserade objektet, dåligt. - även om punkten ovan skulle lösas (och det kan den inte vara), fungerar inte TDS-protokollet och hur SQL Server accepterar parametrar bra med stora parametrar eftersom hela begäran först måste tas emot innan den körs igång och detta skulle skapa ytterligare kopior av objektet inuti SQL Server.
Så du måste verkligen närma dig detta från en annan vinkel. Lyckligtvis finns det en ganska enkel lösning. Tricket är att använda den mycket effektiva UPDATE .WRITE
syntax och skicka in databitarna en efter en, i en serie T-SQL-satser. Detta är det sätt som rekommenderas av MSDN, se Ändra data med stort värde (max) i ADO.NET. Det här ser komplicerat ut, men är faktiskt trivialt att göra och koppla in i en Stream-klass.
Klassen BlobStream
Detta är lösningens bröd och smör. En Stream-deriverad klass som implementerar Write-metoden som ett anrop till T-SQL BLOB WRITE-syntaxen. Rätt fram, det enda intressanta med den är att den måste hålla reda på den första uppdateringen eftersom UPDATE ... SET blob.WRITE(...)
syntax skulle misslyckas i ett NULL-fält:
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
}
Använda BlobStream
För att använda denna nyskapade blobstream-klass ansluter du till en BufferedStream
. Klassen har en trivial design som bara hanterar att skriva strömmen till en kolumn i en tabell. Jag återanvänder en tabell från ett annat exempel:
CREATE TABLE [dbo].[Uploads](
[Id] [int] IDENTITY(1,1) NOT NULL,
[FileName] [varchar](256) NULL,
[ContentType] [varchar](256) NULL,
[FileData] [varbinary](max) NULL)
Jag lägger till ett dummyobjekt som ska serialiseras:
[Serializable]
class HugeSerialized
{
public byte[] theBigArray { get; set; }
}
Slutligen den faktiska serialiseringen. Vi kommer först att infoga en ny post i Uploads
tabell och skapa sedan en BlobStream
på det nyligen infogade ID:t och anropa serialiseringen direkt i denna 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();
}
}
Om du övervakar utförandet av detta enkla prov kommer du att se att ingenstans skapas en stor serialiseringsström. Provet kommer att allokera arrayen [1024*1024] men det är för demoändamål för att ha något att serialisera. Den här koden serialiseras på ett buffrat sätt, bit för bit, med den rekommenderade SQL Server BLOB-uppdateringsstorleken på 8040 byte åt gången.