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.