Der er ingen indbygget ADO.Net funktionalitet til at håndtere dette virkelig yndefuldt for store data. Problemet er todelt:
- der er ingen API til at 'skrive' ind i en SQL-kommando(r) eller parametre som i en strøm. De parametertyper, der accepterer en stream (såsom
FileStream
) accepter strømmen for at LÆS fra det, hvilket ikke stemmer overens med serialiseringssemantikken for write ind i et vandløb. Lige meget hvilken vej du vender dette, ender du med en i hukommelsen kopi af hele det serialiserede objekt, dårligt. - selvom punktet ovenfor ville blive løst (og det kan det ikke), fungerer TDS-protokollen og den måde, SQL Server accepterer parametre på, ikke godt med store parametre, da hele anmodningen først skal modtages, før den lanceres til udførelse og dette ville skabe yderligere kopier af objektet inde i SQL Server.
Så du skal virkelig gribe det an fra en anden vinkel. Heldigvis er der en ret nem løsning. Tricket er at bruge den meget effektive UPDATE .WRITE
syntaks og videregive bidder af data én efter én, i en række T-SQL-sætninger. Dette er den MSDN anbefalede måde, se Ændring af store værdier (max) data i ADO.NET. Dette ser kompliceret ud, men er faktisk trivielt at gøre og tilslutte til en Stream-klasse.
BlobStream-klassen
Dette er løsningens brød og smør. En Stream-afledt klasse, der implementerer Write-metoden som et kald til T-SQL BLOB WRITE-syntaksen. Lige frem, det eneste interessante ved det er, at det skal holde styr på den første opdatering, fordi UPDATE ... SET blob.WRITE(...)
syntaks ville mislykkes i et NULL-felt:
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
}
Brug af BlobStream
For at bruge denne nyoprettede blobstream-klasse tilslutter du en BufferedStream
. Klassen har et trivielt design, der kun håndterer at skrive strømmen ind i en kolonne i en tabel. Jeg genbruger en tabel fra et andet eksempel:
CREATE TABLE [dbo].[Uploads](
[Id] [int] IDENTITY(1,1) NOT NULL,
[FileName] [varchar](256) NULL,
[ContentType] [varchar](256) NULL,
[FileData] [varbinary](max) NULL)
Jeg tilføjer et dummy-objekt, der skal serialiseres:
[Serializable]
class HugeSerialized
{
public byte[] theBigArray { get; set; }
}
Endelig den faktiske serialisering. Vi indsætter først en ny post i Uploads
tabel, og opret derefter en BlobStream
på det nyligt indsatte id og kalder serialiseringen direkte ind i denne 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();
}
}
Hvis du overvåger udførelsen af denne simple prøve, vil du se, at der ingen steder er skabt en stor serialiseringsstrøm. Eksemplet vil allokere arrayet [1024*1024], men det er for demoformål at have noget at serialisere. Denne kode serialiseres på en bufret måde, stykke for stykke, ved hjælp af SQL Server BLOB anbefalede opdateringsstørrelse på 8040 bytes ad gangen.