読み出し処理の改良
これまで示した例では、写真データの読み出しは次のように実行されていました。
p.PhotoData = (byte[])reader.GetValue(reader.GetOrdinal("Photo"));
GetValue()メソッドは、指定された列のすべてのデータをオブジェクトとして返します。写真のサイズが大きい場合はどうなるでしょうか? バイト配列のサイズが膨大である場合は、アプリケーションのパフォーマンスが低下します。幸いなことにSqlDataReaderクラスには、これよりもエレガントな方法が用意されています。SqlDataReaderのGetBytes()メソッドを使用すると、列値をチャンクで読み出すことができます。GetBytes()メソッドの動作を示すために、GetByID()メソッドをリスト14のように変更します。
public static Photo GetByID(int photoid)
{
SqlConnection cnn = new SqlConnection(strConn);
SqlCommand cmd = new SqlCommand();
byte[] data = new byte[1000];
cmd.CommandText = "select * from photos
where PhotoID=@photoid";
cmd.Connection = cnn;
SqlParameter pId = new SqlParameter("@photoid", photoid);
cmd.Parameters.Add(pId);
cnn.Open();
SqlDataReader reader = cmd.ExecuteReader();
Photo p = new Photo();
while (reader.Read())
{
p.PhotoID = reader.GetInt32(reader.GetOrdinal("PhotoID"));
p.Title = reader.GetString(reader.GetOrdinal("Title"));
p.Description =
reader.GetString(reader.GetOrdinal("Description"));
MemoryStream ms = new MemoryStream();
int index = 0;
while (true)
{
long count = reader.GetBytes(reader.GetOrdinal("Photo"),
index, data, 0, data.Length);
if (count == 0)
{
break;
}
else
{
index = index + (int)count;
ms.Write(data, 0, (int)count);
}
}
p.PhotoData = ms.ToArray();
}
cnn.Close();
return p;
}
太字で示されたコードに着目してください。まず読み出すデータチャンクを格納するバイト列を宣言します。次にデータ全体を格納するためのメモリストリームを宣言します。
whileループは、すべてのバイトが読み出されるまでSqlDataReaderのGetBytes()メソッドを呼び出します。GetBytes()メソッドの最初の引数は列インデックスを指定し、2番目の引数は列における読み出し開始位置を指定します。3番目の引数はバイト配列における書き込み位置を示し、最後の引数は読み出される最大バイト数を示します。GetBytes()メソッドは、正常に読み出されたバイト数を返します。戻り値が0である場合は、データがまったく読み出されなかったことを意味します。
データを読み出し、メモリストリームに格納したら、MemoryStreamクラスのToArray()メソッドを呼び出して、それをバイト配列に変換します。
更新処理の改良
更新する写真が大容量である場合、アプリケーションのパフォーマンスに影響が生じてしまいます。このような場合には、更新処理もチャンクで実行することができます。リスト15は、PhotoHelperクラスのUpdate()メソッドを変更したものです。
public static int Update(Photo p)
{
SqlConnection cnn = new SqlConnection(strConn);
SqlCommand cmd = new SqlCommand();
cmd.Connection = cnn;
cmd.CommandText =
"update photos set title=@title,description=
@description,photo.write(@photo,@offset,@length)
where photoid=@photoid";
SqlParameter title = new SqlParameter("@title", p.Title);
SqlParameter description = new SqlParameter("@description",
p.Description);
SqlParameter photo = new SqlParameter("@photo",
SqlDbType.VarBinary);
photo.Value = p.PhotoData;
SqlParameter offset =
new SqlParameter("@offset",SqlDbType.BigInt);
offset.Value = 0;
SqlParameter length = new SqlParameter("@length",
p.PhotoData.Length);
SqlParameter photoid = new SqlParameter("@photoid",
p.PhotoID);
cmd.Parameters.Add(title);
cmd.Parameters.Add(description);
cmd.Parameters.Add(photo);
cmd.Parameters.Add(offset);
cmd.Parameters.Add(length);
cmd.Parameters.Add(photoid);
cnn.Open();
int i = cmd.ExecuteNonQuery();
cnn.Close();
return i;
}
コードの中の太字で示された行に着目してください。UPDATEステートメントが.WRITE()句を使用するように変更されています。.WRITE()句は、varchar(MAX)、nvarchar(MAX)、varbinary(MAX)列に対して適用され、列の値全体を置き換えるのではなく、列の一部のみを変更することが可能です。これは、大量のデータを扱う際には非常に便利です。
.WRITE()句には、列に書き込むデータ、列内の書き込み開始位置、書き込むデータの長さという3つの引数があります。ループ内でUPDATEステートメントを実行することにより、複数のチャンクで列値を更新することもできます。
まとめ
BLOB(バイナリラージオブジェクト)を扱うアプリケーションでは、データをSQL Server内に格納するか、それとも物理的なファイルとして格納するかを決定しなければなりません。
SQL Serverでは、BLOBを格納するためのデータ型としてvarbinary(MAX)を提供していますが、旧バージョンのImageデータ型もまだ使用することができます。.NETアプリケーションでは、SqlDataReaderクラスを用いることによってBLOBデータを読み出すことができます。
SqlDataReaderのGetBytes()メソッドは、列からバイナリデータをチャンクで読み出すためのものです。バイナリデータをデータベースへと書き戻す際には、SQL Serverの.WRITE()句を使用すれば、データをチャンクで書き込むことができます。SQL ServerとADO.NETにより、ドキュメント、ファイル、写真などの大きなバイナリデータを容易に扱うことが可能です。
