如何读取与写入数据库字段中的二进制影像数据是大家非常喜欢讨论的一项课题,为了顺应广大读者的要求,我们特别撰写了能够读取与写入二进制影像数据的CLR存储过程,以下我们将讨论相关的设计与使用技巧。
存取二进制影像的 CLR 存储过程位于 ReadWriteBLOB 类别库项目中,最主要的公用静态方法 ReadBlobFromDbColumn 与 WriteBlobIntoDbColumn 分别代表读取与写入二进制数据的CLR存储过程,其用途说明如下:
q CLR 存储过程 ReadBlobFromDbColumn 负责将字段中的二进制影像数据写出成一个 JPEG 图档。程序会根据第一个参数所指定的身份证字号搜寻「章立民研究室」数据表之数据记录,然后将该笔数据记录之「玉照」字段中的二进制影像数据写出成一个与身份证字号同名的图档(扩展名为 .jpg),并且储存至第二个参数所指定的目录中,程序代码如下所列:
[Microsoft.SqlServer.Server.SqlProcedure()]
public static void ReadBlobFromDbColumn(string Id, string currentDirectory)
{
using (SqlConnection connection =
new SqlConnection("context connection = true"))
{
SqlCommand command = connection.CreateCommand();
command.CommandText = "SELECT 玉照 FROM 章立民研究室 "
+ "WHERE 身份证字号 = @Id";
command.Parameters.Add("@Id", SqlDbType.NVarChar);
command.Parameters[0].Value = Id;
// BLOB 缓冲区的大小。
int bufferSize = 100;
// BLOB byte() 缓冲区会使用 GetBytes 来填入。
byte[] outByte = new byte[bufferSize];
// GetBytes 所传回的字节。
long retval;
// 位于 BLOB 输出中的起始位置。
long startIndex = 0;
// 开启数据库连接并将数据读入数据读取器中。
connection.Open();
using (SqlDataReader reader =
command.ExecuteReader(CommandBehavior.SequentialAccess))
{
if (reader.Read())
{
// 此 FileStream 用来将 BLOB 写至一个文件(.jpg)。
using (FileStream stream = new FileStream(
currentDirectory + Id + ".jpg", FileMode.OpenOrCreate, FileAccess.Write))
{
using (BinaryWriter writer = new BinaryWriter(stream))
{
// 替一个新的 BLOB 重新设定起始字节。
startIndex = 0;
// 将字节读入 outByte() 并保有传回的字节数目。
retval = reader.GetBytes(0, startIndex, outByte, 0, bufferSize);
// 只有在缓冲区的大小之后还有字节就持续循环。
while (retval == bufferSize)
{
writer.Write(outByte);
writer.Flush();
// 将启始索引的位置设定成最后一次缓冲区的尾端并填入缓冲区。
startIndex += bufferSize;
retval = reader.GetBytes(0, startIndex, outByte, 0, bufferSize);
}
// 写入剩余的字节。
writer.Write(outByte, 0, Convert.ToInt32(retval));
writer.Flush();
}
}
}
}
}
}
q CLR 存储过程 WriteBlobIntoDbColumn 负责将图文件中的二进制影像数据写回至数据库字段中。程序会根据第一个参数所指定的身份证字号搜寻「章立民研究室」数据表之数据记录,接着利用第二与第三个参数来构建出图文件的完整路径以便读取该图文件的二进制影像数据,并将其写回「玉照」字段中,兹将原始程序代码列示如下:
[Microsoft.SqlServer.Server.SqlProcedure()]
public static void WriteBlobIntoDbColumn(
string Id, string currentDirectory, string fileName)
{
string fullFileName = currentDirectory + fileName;
byte[] bytes = ReadFile(fullFileName);
try
{
SqlConnection conn = new SqlConnection("context connection = true");
conn.Open();
SqlCommand sprocCommand = conn.CreateCommand();
sprocCommand.CommandText = "dbo.usp_UpdateImage";
sprocCommand.CommandType = CommandType.StoredProcedure;
sprocCommand.Parameters.Add(new SqlParameter("@Id", SqlDbType.NVarChar));
sprocCommand.Parameters[0].Value = Id;
sprocCommand.Parameters.Add(
new SqlParameter("@ThumbNailPhoto", SqlDbType.VarBinary));
sprocCommand.Parameters[1].Value = bytes;
sprocCommand.ExecuteNonQuery();
}
catch (SqlException e)
{
…
}
}
q 显然我们用于读取与写入二进制影像数据的CLR存储过程涉及到文件的存取作业,但是请注意,要让组件的程序代码能够存取文件、网络、环境变量、以及登录(Registry)等特定的外部系统资源,必须将 PERMISSION_SET 设定成 EXTERNAL_ACCESS。然而大家必须了解,拥有 UNSAFE 或 EXTERNAL_ACCESS 权限的组件要能够在SQL Server中登录并正确运作,您必须已经设定了 TRUSTWORTHY 数据库属性或是组件已经使用非对称金钥加以签署,而且对应的登入必须具有 EXTERNAL ACCESS ASSEMBLY 权限。
在此我们是采用非对称金钥的作法,而其整体的安全性设定必须分别在类别库项目与SQL Server 2005中完成,您可以参考「SQL Server 2005完全实战」一书的说明,于此不再赘述。
q 至此,用于读取与写入二进制影像数据的 CLR 存储过程还不能使用,因为我们还必须在 SQL Server 中登录其组件并建立存储过程来参考该组件中的 CLR 存储过程。请您继续执行程序代码如下所列的SQL指令码来完成相关作业:
USE 北风贸易;
GO
IF EXISTS (SELECT * FROM sys.procedures WHERE [name] =
N'usp_UpdateImage')
DROP PROCEDURE [dbo].[usp_UpdateImage];
GO
IF EXISTS (SELECT * FROM sys.procedures WHERE [name] =
N'ReadBlobFromDbColumn')
DROP PROCEDURE [dbo].[ReadBlobFromDbColumn];
GO
IF EXISTS (SELECT * FROM sys.procedures WHERE [name] =
N'WriteBlobIntoDbColumn')
DROP PROCEDURE [dbo].[WriteBlobIntoDbColumn];
GO
IF EXISTS (SELECT [name] FROM sys.assemblies WHERE [name] =
N'ReadWriteBlob')
DROP ASSEMBLY ReadWriteBlob;
GO
-- 登录组件 ReadWriteBlob.dll 。
CREATE ASSEMBLY ReadWriteBlob
FROM 'C:\SQL2005Demo\CH13\ReadWriteBLOB\
ReadWriteBLOB\bin\ReadWriteBlob.dll'
WITH PERMISSION_SET = EXTERNAL_ACCESS;
GO
-- 建立一个会参考所登录之组件ReadWriteBlob.dll中
-- 之ReadBlobFromDbColumn的存储过程。
CREATE PROCEDURE [dbo].[ReadBlobFromDbColumn]
(
@Id nvarchar(10),
@CurrentDirectory nvarchar(1024)
)
AS EXTERNAL NAME
ReadWriteBLOB.[ReadWriteBLOB.ReadWriteBlobClass].
ReadBlobFromDbColumn;
GO
-- 建立一个会参考所登录之组件ReadWriteBlob.dll中
-- 之WriteBlobIntoDbColumn的存储过程。
CREATE PROCEDURE [dbo].[WriteBlobIntoDbColumn]
(
@Id nvarchar(10),
@CurrentDirectory nvarchar(1024),
@FileName nvarchar(1024)
)
AS EXTERNAL NAME
ReadWriteBLOB.[ReadWriteBLOB.ReadWriteBlobClass].
WriteBlobIntoDbColumn;
GO
-- 此Transact-SQL存储过程会负责实际的更新作业。
CREATE PROCEDURE [dbo].[usp_UpdateImage]
@Id nvarchar(10),
@ThumbNailPhoto AS VARBINARY(max)
AS
BEGIN
SET NOCOUNTON;
BEGIN TRY
UPDATE 章立民研究室
SET 玉照 = @ThumbNailPhoto
WHERE 身份证字号 = @Id;
IF(@@ROWCOUNT < 1)
RAISERROR ('更新失败。', 16, 1);
END TRY
BEGIN CATCH
SELECT
ERROR_NUMBER() AS ErrorNumber,
ERROR_SEVERITY() AS ErrorSeverity,
ERROR_STATE() as ErrorState,
ERROR_PROCEDURE() as ErrorProcedure,
ERROR_LINE() as ErrorLine,
ERROR_MESSAGE() as ErrorMessage;
RAISERROR ('更新失败。', 16, 1);
END CATCH;
END;
GO
q 我们现在终于完成了所有的前置准备工作,可以去执行 CLR 存储过程 ReadBlobFromDbColumn 与 WriteBlobIntoDbColumn 来读取与写入二进制影像数据。如下所列之 SQL 指令码示范如何使用这两个 CLR 存储过程,存储过程执行结果如图表1所示:
USE 北风贸易;
GO
-- 将身份证字号为 'A156401174' 之数据记录的「玉照」字段
-- 内容写出成 C:\Temp\A156401174.jpg
EXEC ReadBlobFromDbColumn 'A156401174', 'C:\Temp\';
GO
-- 将身份证字号为 'O156405929' 之数据记录的「玉照」字段
-- 内容写出成 C:\Temp\O156405929.jpg
EXEC ReadBlobFromDbColumn 'O156405929', 'C:\Temp\';
GO
-- 将 C:\Temp\A156401174.jpg 的图档内容写回至身份证字号
-- 为 'A156401174' 之数据记录的「玉照」字段
EXEC WriteBlobIntoDbColumn 'A156401174', 'C:\Temp\', 'A156401174.jpg';
GO
-- 将 C:\Temp\O156405929.jpg 的图档内容写回至身份证字号
-- 为 'O156405929' 之数据记录的「玉照」字段
EXEC WriteBlobIntoDbColumn 'O156405929', 'C:\Temp\', 'O156405929.jpg';
GO
图表1
本文节录并修改自《SQL Server 2005 完全实战》一书,特此声明之。
参考书籍:《Visual C# 2005 文件 IO 与资料存取秘诀》