これを大規模なデータに対して非常に適切に処理するための組み込みのADO.Net機能はありません。問題は2つあります:
- ストリームのようにSQLコマンドまたはパラメータに「書き込む」APIはありません。ストリームを受け入れるパラメータタイプ(
FileStream
など) )ストリームを受け入れて読む それから、 writeのシリアル化セマンティクスと一致しません ストリームに。これをどちらの方向に向けても、シリアル化されたオブジェクト全体のメモリ内コピーになってしまいます。悪いことです。 - 上記の点が解決されたとしても(そして解決できない場合でも)、TDSプロトコルとSQL Serverがパラメーターを受け入れる方法は、実行を開始する前に要求全体を最初に受信する必要があるため、大きなパラメーターではうまく機能しません。これにより、SQLServer内にオブジェクトの追加コピーが作成されます。
ですから、あなたは本当に別の角度からこれにアプローチしなければなりません。幸いなことに、かなり簡単な解決策があります。秘訣は、非常に効率的なUPDATE .WRITE
を使用することです。 構文を作成し、一連のT-SQLステートメントでデータのチャンクを1つずつ渡します。これはMSDNが推奨する方法です。ADO.NETでの大きな値(最大)データの変更を参照してください。これは複雑に見えますが、実際には、Streamクラスにプラグインするのは簡単です。
BlobStreamクラス
これがソリューションのパンとバターです。 T-SQLBLOBWRITE構文の呼び出しとしてWriteメソッドを実装するStream派生クラス。率直に言って、それについて興味深いのは、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
を作成します 新しく挿入されたIDで、シリアル化をこのストリームに直接呼び出します:
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]の配列を割り当てますが、これはデモの目的でシリアル化するものを用意するためのものです。このコードは、SQL Server BLOBが推奨する更新サイズ8040バイトを一度に使用して、バッファリングされた方法でチャンクごとにシリアル化します。