Нет встроенной функции ADO.Net, которая бы действительно изящно справлялась с большими данными. Проблема в два раза:
- отсутствует API для «записи» в команду (ы) SQL или параметры как в поток. Типы параметров, которые принимают поток (например,
FileStream
), принимают поток от него READ , что не согласуется с семантикой сериализации запись в поток. Независимо от того, каким образом вы это включите, вы получите в памяти копию всего сериализованного объекта, плохо.
- , даже если указанная выше точка будет решена (а это не может быть), протокол TDS и способ, которым SQL Server принимает параметры, плохо работают с большими параметрами, поскольку весь запрос должен быть сначала получен до того, как он будет запущен в исполнение и это создаст дополнительные копии объекта внутри SQL Server.
Так что вам действительно нужно подойти к этому под другим углом. К счастью, есть довольно простое решение. Хитрость заключается в том, чтобы использовать высокоэффективный синтаксис UPDATE .WRITE
и передавать порции данных один за другим в серии операторов T-SQL. Это рекомендуемый MSDN способ, см. Изменение больших (максимальных) данных в ADO.NET . Это выглядит сложно, но на самом деле тривиально сделать и подключить к классу Stream.
Класс BlobStream
Это хлеб с маслом решения. Производный класс Stream, который реализует метод Write как вызов синтаксиса T-SQL BLOB WRITE. Скорее всего, единственное, что интересно в этом, - это то, что он должен отслеживать первое обновление, потому что синтаксис UPDATE ... SET blob.WRITE(...)
в поле 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
}
Использование BlobStream
Чтобы использовать этот недавно созданный класс потока BLOB-объектов, вы подключаетесь к BufferedStream
. Класс имеет тривиальный дизайн, который обрабатывает только запись потока в столбец таблицы. Я буду использовать таблицу из другого примера:
CREATE TABLE [dbo].[Uploads](
[Id] [int] IDENTITY(1,1) NOT NULL,
[FileName] [varchar](256) NULL,
[ContentType] [varchar](256) NULL,
[FileData] [varbinary](max) NULL)
Я добавлю фиктивный объект для сериализации:
[Serializable]
class HugeSerialized
{
public byte[] theBigArray { get; set; }
}
Наконец, фактическая сериализация. Сначала мы вставим новую запись в таблицу Uploads
, затем создадим BlobStream
для вновь вставленного идентификатора и вызовем сериализацию прямо в этот поток:
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();
}
}
Если вы проследите за выполнением этого простого примера, вы увидите, что нигде не создается большой поток сериализации. В примере будет выделен массив [1024 * 1024], но для демонстрационных целей есть что-то для сериализации. Этот код сериализуется буферизованным способом, порция за порцией, с использованием рекомендованного BLOB-объекта SQL Server размера обновления 8040 байт за раз.