在 .NET 中使用大对象
作者:Jason Price
了解如何使用 .NET 读写大对象 (LOB) 以及如何通过 BFILE 读取数据。
本文相关下载: · 示例代码 · Oracle 数据库 10g · ODP.NET(版本 10.1.0.2.0 或更高版本,如果需要的话) · Microsoft .NET Framework 和 SDK |
本文是上一篇介绍在 .NET 中使用数据库对象文章的后续文章。在本文中,您将了解如何在 Visual Basic .NET (VB.NET) 和 Visual C# .NET (C#) 中使用大对象。尤其是,您将了解如何使用 .NET 读写大对象 (LOB)。您还将了解如何通过 BFILE(可以将其看作是文件指针)读取数据。本文中引用的所有脚本和文件都可在这里找到。本文假定您大体上了解 C# 和 VB.NET 编程。
本文适用于开发人员,您应大体上了解 C# 和 VB.NET 编程并具有 LOB 基础知识。如果您需要 LOB 的介绍,可以阅读 Oracle LOB 文档,也可以阅读我编写的 Oracle 数据库 10g SQL(McGraw-Hill/Osborne,2004)一书。
所需软件如果您要跟随我们逐步完成本文中给出的示例,那么您需要安装以下软件:
注意:如果您使用的是 Oracle 数据库 10g(Oracle8i 版本 3 8.1.7 或更高版本)之前的版本,则需要在数据库之外单独下载和安装 Oracle Data Provider for .NET (ODP.NET)。
ODP.NET 驱动程序针对 Oracle 数据库访问进行了优化,因此可以获得最佳性能,并且它们还支持 Oracle 数据库的丰富特性,如 BFILE、BLOB、CLOB、XMLType 等。如果您正在开发基于 Oracle 数据库的 .NET 应用程序,那么就特性和性能来讲,ODP.NET 无疑是最佳的选择。
注意:ODP.NET 驱动程序针对 Oracle 数据库访问进行了优化,因此可以获得最佳性能,并且它们还支持 Oracle 数据库的丰富特性,如 BFILE、BLOB、CLOB、XMLType 等。如果您正在开发基于 Oracle 数据库的 .NET 应用程序,那么就特性和性能来讲,ODP.NET 无疑是最佳的选择。
数据库模式设置
首先,您需要设置将包含本文所用表的数据库模式。您首先必须创建一个名为 lob_user 的用户,并按如下所示授予该用户所需权限。(您必须先以具有数据库管理员权限的用户身份登录数据库,才能创建用户和授予权限):
CREATE USER lob_user IDENTIFIED BY lob_password; GRANT CONNECT, RESOURCE, CREATE ANY DIRECTORY TO lob_user;
您会在示例代码文件 lob_db.sql 中找到前两个语句和该部分中出现的设置 store 模式的其他语句。
以下语句创建一个名为 SAMPLE_FILES_DIR 的目录,该目录指向服务器硬盘驱动器上的 C:\sample_files 目录;您必须在硬盘驱动器的 C: 分区中创建 sample_files 目录,并将 textContent.txt 和 binaryContent.doc 文件复制到 C:\sample_files 中。
CREATE OR REPLACE DIRECTORY SAMPLE_FILES_DIR AS 'C:\sample_files';
注意:textContent.txt 和 binaryContent.doc 文件包含莎士比亚戏剧 Macbeth 中的引文。您不久将看到把这两个文件的内容复制到数据库中。
下一语句授予公众读取权限,以便所有用户均可读取 SAMPLE_FILES_DIR 的内容:GRANT READ ON DIRECTORY SAMPLE_FILES_DIR TO PUBLIC;
下一语句以 lob_user 的身份连接:
CONNECT lob_user/lob_password;
您将看到本文使用了三个表:
CREATE TABLE clob_content ( id INTEGER PRIMARY KEY, clob_column CLOB NOT NULL ); CREATE TABLE blob_content ( id INTEGER PRIMARY KEY, blob_column BLOB NOT NULL ); CREATE TABLE bfile_content ( id INTEGER PRIMARY KEY, bfile_column BFILE NOT NULL );
如果您创建了这些表所用的模式不是 lob_user 的模式,那么您将需要修改稍后的示例程序中的模式名称。
下两条语句向 clob_content 和 blob_content 表中添加一个空的 CLOB 和 BLOB:
INSERT INTO clob_content ( id, clob_column ) VALUES ( 1, EMPTY_CLOB() ); INSERT INTO blob_content ( id, blob_column ) VALUES ( 1, EMPTY_BLOB() );
下列 PL/SQL 语句将文件 textContent.txt 中的文本加载到 clob_content 表中,将文件 binaryContent.doc 中的二进制数据加载到 blob_content 表中:
DECLARE my_clob CLOB; my_blob BLOB; my_bfile BFILE; BEGIN -- load the CLOB my_bfile := BFILENAME('SAMPLE_FILES_DIR', 'textContent.txt'); SELECT clob_column INTO my_clob FROM clob_content WHERE id = 1 FOR UPDATE; DBMS_LOB.FILEOPEN(my_bfile, dbms_lob.file_readonly); DBMS_LOB.LOADFROMFILE(my_clob, my_bfile, DBMS_LOB.GETLENGTH(my_bfile), 1, 1); -- load the BLOB my_bfile := BFILENAME('SAMPLE_FILES_DIR', 'binaryContent.doc'); SELECT blob_column INTO my_blob FROM blob_content WHERE id = 1 FOR UPDATE; DBMS_LOB.FILEOPEN(my_bfile, dbms_lob.file_readonly); DBMS_LOB.LOADFROMFILE(my_blob, my_bfile, DBMS_LOB.GETLENGTH(my_bfile), 1, 1); DBMS_LOB.FILECLOSEALL(); COMMIT; END; /
下一条语句使 bfile_content 表中的 BFILE 指向位于 SAMPLE_FILES_DIR 目录中的 textContent.txt 文件:
INSERT INTO bfile_content ( id, bfile_column ) VALUES ( 1, BFILENAME('SAMPLE_FILES_DIR', 'textContent.txt') );
使用 C# 和 VB.NET 从 LOB 中读取数据
检索 LOB 有两种方法:
此参数影响 SELECT 语句执行中的所有 LOB。例如,如果将 InitialLOBFetchSize 设置为 5K,且在 SELECT 语句执行中将检索 10 个 LOB,则在一个数据库往返中将检索这 10 个 LOB 中每个 LOB 的前 5K。当前,10.1.0.2.0 版的 ODP.NET 中 InitialLOBFetchSize 的最大设置为 32KB。Oracle 将在未来版本中将此最大大小增大为 2GB。
注意:如果更改 InitialLOBFetchSize(默认值 0),则您只能使用上面提到的方法 2 中的访问器从 CLOB 中读取数据 — 不过 Oracle 计划移除该限制。在未来的 ODP.NET 版本中,您将能够使用这两个方法来检索 LOB。
如果您选择的所有 LOB 的数据量不大,且选择了很多 LOB,则通过更改 InitialLOBFetchSize(默认值 0),您可能获得更出色的立即检索 LOB 数据性能。如果使用 InitialLOBFetchSize,则应将其设置为一个略大于所选 LOB 大小的 80% 的值。例如,如果行中 LOB 大小的 80% 小于或等于 1KB,则应将 InitialLOBFetchSize 设置为 1KB。由于结果将取决于网络性能、延迟、数据大小等因素,因此应试验您的设置以发现 InitialLOBFetchSize 的最优设置。
更改 InitialLOBFetchSize 的默认值 0 时,将进行立即 LOB 检索。当保留 InitialLOBFetchSize 的默认值零 0 时,将进行延迟 LOB 检索。下表提供了决定使用延迟 LOB 检索还是立即 LOB 检索时要考虑的原则。
在以下情况下使用延迟 LOB 检索 | 在以下情况下使用立即 LOB 检索 |
客户端和数据库服务器之间的网络带宽不足 | 网络带宽充足 |
您不需要立即对大部分 LOB 数据进行立即访问,而是可以随着时间的推移来检索它 | 您需要在选中 LOB 数据时立即读取几乎所有这些数据 |
您正在执行更新、插入或删除,不打算读取 LOB | N/A |
ClobExample1.cs 中的第 1 步从 clob_content 表中读取行:
myOracleCommand.CommandText = "SELECT id, clob_column " + "FROM clob_content " + "WHERE id = 1"; OracleDataReader myOracleDataReader = myOracleCommand.ExecuteReader(); myOracleDataReader.Read();
在 ClobExample1.vb 中,VB.NET 代码为
myOracleCommand.CommandText = _ "SELECT id, clob_column " & _ "FROM clob_content " & _ "WHERE id = 1" Dim myOracleDataReader As _ OracleDataReader = myOracleCommand.ExecuteReader() myOracleDataReader.Read()
BlobExample1.cs 中的第 1 步从 blob_content 表中读取行:
myOracleCommand.CommandText = "SELECT id, blob_column " + "FROM blob_content " + "WHERE id = 1"; OracleDataReader myOracleDataReader = myOracleCommand.ExecuteReader(); myOracleDataReader.Read();
在 BlobExample1.vb 中,VB.NET 代码为
myOracleCommand.CommandText = _ "SELECT id, blob_column " & _ "FROM blob_content " & _ "WHERE id = 1" Dim myOracleDataReader As _ OracleDataReader = myOracleCommand.ExecuteReader() myOracleDataReader.Read()
第 2 步
ClobExample1.cs 中的第 2 步将 LOB 定位器复制给 OracleClob 对象。使用 myOracleDataReader.GetOracleClob() 方法取得定位器:
OracleClob myOracleClob = myOracleDataReader.GetOracleClob(1);
在 ClobExample1.vb 中,VB.NET 代码为
Dim myOracleClob As _ OracleClob = myOracleDataReader.GetOracleClob(1)
BlobExample1.cs 中的第 2 步将 LOB 定位器复制给 OracleBlob 对象。使用 myOracleDataReader.GetOracleBlob() 方法取得定位器:
OracleBlob myOracleBlob = myOracleDataReader.GetOracleBlob(1);
在 BlobExample1.vb 中,VB.NET 代码为
Dim myOracleBlob As _ OracleBlob = myOracleDataReader.GetOracleBlob(1)
第 3 步
ClobExample1.cs 中的第 3 步使用 OracleClob 对象的 Read() 方法取得 CLOB 数据。Read() 方法有两个版本:
int Read(byte [] byteArray, int offset, int count) int Read(char [] charArray, int offset, int count)其中:
char [] charArray = new char[50]; int numCharsRead; while ((numCharsRead = myOracleClob.Read(charArray, 0, 50)) > 0) { Console.WriteLine("numCharsRead = " + numCharsRead); string clobData = new string(charArray, 0, numCharsRead); Console.WriteLine("clobData = " + clobData); }
在 ClobExample1.vb (VB.NET) 中:
Dim charArray(50) As char Dim numCharsRead As Integer numCharsRead = myOracleClob.Read(charArray, 0, 50) Do While (numCharsRead > 0) Console.WriteLine("numCharsRead = " & numCharsRead) Dim clobData As New string(charArray, 0, numCharsRead) Console.WriteLine("clobData = " & clobData) numCharsRead = myOracleClob.Read(charArray, 0, 50) Loop
BlobExample1.cs 中的第 3 步使用 OracleBlob 对象的 Read() 方法。Read() 方法只有一个版本:
int Read(byte [] byteArray, int offset, int count)
在 BlobExample1.cs 的以下 C# 代码中您将看到,从 BLOB 读取字符并将其写入名为 byteArray 的字符数组中。此示例代码将在每个数据库往返中一次读取 BLOB 50 个字符,直到读取了整个 BLOB。
byte [] byteArray = new byte[50]; Console.WriteLine("byteArray.Length = " + byteArray.Length); int numBytesRead; while ((numBytesRead = myOracleBlob.Read(byteArray, 0, 50)) > 0) { Console.WriteLine("numBytesRead = " + numBytesRead); }
在 BlobExample1.vb 中,VB.NET 代码为
Dim byteArray(50) As byte Dim numBytesRead As Integer numBytesRead = _ myOracleBlob.Read(byteArray, 0, 50) Do While (numBytesRead > 0) Console.WriteLine("numBytesRead = " & numBytesRead) numBytesRead = _ myOracleBlob.Read(byteArray, 0, 50) Loop
使用立即 LOB 检索从 LOB 中读取数据
现在,我将逐步完成四个示例程序(使用立即 LOB 检索读取先前存储在 clob_content.clob_column 中的文本和存储在 blob_content.blob_column 中的二进制数据)中的主要步骤。这四个程序如下所示:第 1 步
第 1 步将 InitialLOBFetchSize 设置为 1,000 个字节。在 ClobExample2.cs 和 BlobExample2.cs 中,C# 代码为
myOracleCommand.InitialLOBFetchSize = 1000;
注意,是在 OracleCommand 对象上设置 InitialLOBFetchSize。在 ClobExample2.vb 和 BlobExample2.vb 中,VB.NET 代码为
myOracleCommand.InitialLOBFetchSize = 1000
第 2 步
第 2 步与前面的延迟 LOB 检索示例显示的第 1 步相同。
第 3 步
ClobExample2.cs 中的第 3 步使用 GetString() 方法从 CLOB 中取得数据并显示该数据:
String clobData = myOracleDataReader.GetString(1); Console.WriteLine("clobData = " + clobData);
在 ClobExample2.vb 中,VB.NET 代码为
Dim clobData As string = myOracleDataReader.GetString(1) Console.WriteLine("clobData = " & clobData)
BlobExample2.cs 中的第 3 步使用 GetBytes() 方法从 BLOB 中取得数据并显示读取的字节数:
byte [] byteArray = new byte[1000]; long numBytesRead = myOracleDataReader.GetBytes(1, (long) 0, byteArray, 0, 1000); Console.WriteLine("numBytesRead = " + numBytesRead);
在 BlobExample2.vb 中,VB.NET 代码为
Dim byteArray(1000) As byte Dim numBytesRead As long = _ myOracleDataReader.GetBytes(1, 0, byteArray, 0, 1000) Console.WriteLine("numBytesRead = " & numBytesRead)
使用 C# 和 VB.NET 写入 LOB
使用 OracleDataReader 对象时有两种方法可以写入 LOB:
使用方法 1 写入 CLOB
我将逐步完成两个示例程序(使用方法 1 写入 clob_content.clob_column 中的 CLOB)中的主要步骤。这两个程序如下所示:
第 1 步
必须在 OracleTransaction 对象的上下文中执行所有 LOB 更新。在 ClobExample3.cs 中,C# 代码为
OracleTransaction myOracleTransaction = myOracleConnection.BeginTransaction();
在 ClobExample3.vb 中,VB.NET 代码为
Dim myOracleTransaction As OracleTransaction = _ myOracleConnection.BeginTransaction()
第 2 步
第 2 步是读取行,它与前面的“使用延迟 LOB 检索从 LOB 中读取数据”部分中的第 1 步相同。
第 3 步
第 3 步是使用 OracleDataReader 的 GetOracleClobForUpdate() 方法取得 LOB 定位器。在 ClobExample3.cs 中,C# 代码为
OracleClob myOracleClob = myOracleDataReader.GetOracleClobForUpdate(1);
在 ClobExample3.vb 中,VB.NET 代码为
Dim myOracleClob As _ OracleClob = myOracleDataReader.GetOracleClobForUpdate(1)
第 4 步
第 4 步是使用 OracleClob 对象的 Write() 方法写入 CLOB。Write() 方法有两个版本:
Write(byte [] byteArray, int offset, int count) Write(char [] charArray, int offset, int count)
其中:
myOracleClob.Erase(); string text = "It is the east, and Juliet is the Sun"; char [] charArray = text.ToCharArray(); myOracleClob.Write(charArray, 0, charArray.Length); Console.WriteLine("myOracleClob.Value = " + myOracleClob.Value);
在 ClobExample3.vb 中,VB.NET 代码为
myOracleClob.Erase() Dim text As string = "It is the east, and Juliet is the Sun" Dim charArray() As char = text.ToCharArray() myOracleClob.Write(charArray, 0, charArray.Length) Console.WriteLine("myOracleClob.Value = " & myOracleClob.Value)
第 5 步
第 5 步是提交事务,以便将新文本永久存储在数据库中。在 ClobExample3.cs 中,C# 代码为
myOracleTransaction.Commit();
在 ClobExample3.vb 中,VB.NET 代码为
myOracleTransaction.Commit()
使用方法 2 写入 CLOB
现在,我们将逐步完成两个示例程序(使用方法 2 写入 clob_content.clob_column 中的 CLOB)中的主要步骤。这两个程序如下所示:
myOracleCommand.CommandText = "SELECT id, clob_column " + "FROM clob_content " + "WHERE id = 1 FOR UPDATE"; OracleDataReader myOracleDataReader = myOracleCommand.ExecuteReader(); myOracleDataReader.Read();
在 ClobExample4.vb 中,VB.NET 代码为
myOracleCommand.CommandText = _ "SELECT id, clob_column " & _ "FROM clob_content " & _ "WHERE id = 1 FOR UPDATE" Dim myOracleDataReader As _ OracleDataReader = myOracleCommand.ExecuteReader() myOracleDataReader.Read()
第 3 步
第 3 步取得 CLOB 定位器。在 ClobExample4.cs 中,C# 代码为OracleClob myOracleClob = myOracleDataReader.GetOracleClob(1);
在 ClobExample4.vb 中,VB.NET 代码为
Dim myOracleClob As OracleClob = _ myOracleDataReader.GetOracleClob(1)
以下是该步骤与前一方法中所示步骤的差别:此处我调用 GetOracleClob(),而前面我调用了 GetOracleClobForUpdate()。由于第 2 步中使用了 FOR UPDATE 子句,因此我不必使用 GetOracleClobForUpdate() — 我只需调用 GetOracleClob() 并开始写入 CLOB。一旦拥有 CLOB 定位器,写入 CLOB 的步骤则与上面的方法 1 完全相同。
使用 C# 和 VB.NET 从 BFILE 中读取数据
BFILE 存储文件系统中文件的指针,您可以通过该指针访问该文件。我将逐步完成两个示例程序(读取由 bfile_content.bfile_column 中的 BFILE 指向的 textContent.txt 文件中的文本)中的主要步骤。这两个程序如下所示:
myOracleCommand.CommandText = "SELECT id, bfile_column " + "FROM bfile_content " + "WHERE id = 1"; OracleDataReader myOracleDataReader = myOracleCommand.ExecuteReader(); myOracleDataReader.Read();
在 BfileExample1.vb 中,VB.NET 代码为
myOracleCommand.CommandText = _ "SELECT id, bfile_column " & _ "FROM bfile_content " & _ "WHERE id = 1" Dim myOracleDataReader As OracleDataReader = _ myOracleCommand.ExecuteReader() myOracleDataReader.Read()
第 2 步
第 2 步通过调用 OracleDataReader 对象的 GetOracleBFile() 方法取得 BFILE。在 BfileExample1.cs 的以下 C# 代码中您将看到,我还将显示目录名、文件名以及文件是否存在:OracleBFile myOracleBFile = myOracleDataReader.GetOracleBFile(1); Console.WriteLine("myOracleBFile.DirectoryName = " + myOracleBFile.DirectoryName); Console.WriteLine("myOracleBFile.FileName = " + myOracleBFile.FileName); Console.WriteLine("myOracleBFile.FileExists = " + myOracleBFile.FileExists);
在 BfileExample1.vb 中,VB.NET 代码为
Dim myOracleBFile As OracleBFile = _ myOracleDataReader.GetOracleBFile(1) Console.WriteLine("myOracleBFile.DirectoryName = " & _ myOracleBFile.DirectoryName) Console.WriteLine("myOracleBFile.FileName = " & _ myOracleBFile.FileName) Console.WriteLine("myOracleBFile.FileExists = " & _ myOracleBFile.FileExists)
第 3 步
BfileExample1.cs 中的第 3 步打开 BFILE:
myOracleBFile.OpenFile();
在 BfileExample1.vb 中,VB.NET 与上一程序相同。
第 4 步
BfileExample1.cs 中的第 4 步使用 Read() 方法从 BFILE 中读取数据。在以下代码中您将看到,BFILE 中的数据被读入名为 byteArrray 的字节数组中;同时,还使用 System.Text.Encoding.ASCII.GetString() 方法将该数组中的数据转换为字符串,以便在屏幕上显示数据:byte [] byteArray = new byte[1000]; int offset = 0; int numBytesRead; while ((numBytesRead = myOracleBFile.Read(byteArray, offset, 1000-offset)) > 0) { Console.WriteLine("numBytesRead = " + numBytesRead); String text = System.Text.Encoding.ASCII.GetString(byteArray, 0, numBytesRead); Console.WriteLine("text = " + text); offset += numBytesRead; }
在 BfileExample1.vb 中,VB.NET 代码为
Dim byteArray(1000) As byte Dim offset As Integer = 0 Dim numBytesRead As Integer numBytesRead = myOracleBFile.Read(byteArray, offset, 1000-offset) Do While (numBytesRead > 0) Console.WriteLine("numBytesRead = " & numBytesRead) Dim text As string = _ System.Text.Encoding.ASCII.GetString(byteArray, 0, numBytesRead) Console.WriteLine("text = " & text) offset += numBytesRead numBytesRead = myOracleBFile.Read(byteArray, offset, 1000-offset) Loop
第 5 步
BfileExample1.cs 中的第 5 步使用 Close() 方法关闭 BFILE:
myOracleBFile.Close();
在 BfileExample1.vb 中,VB.NET 与上一程序相同。
结论本文介绍了如何从 C# 和 VB.NET 访问大对象。您还了解了如何通过 BFILE 读取数据。