前置準備與環境說明
核心類與關鍵屬性
| 類/屬性 | 用途 | LONG 特殊處理 |
| OracleConnection | 連接對象 | 連接字符串需 `Persist Security Info=True` |
| OracleCommand | 命令對象 | `CommandType = CommandType.Text` |
| OracleParameter | 參數對象 | **關鍵**:`OracleType = OracleType.LongVarChar` (或 `LongRaw`),`Direction = ParameterDirection.Input/Output` |
| OracleDataAdapter | 數據適配器 | `Fill(DataSet)` 自動將 LONG 映射為 `System.String` (受 .NET 字符串 2GB 限制) |
| OracleDataReader | 只讀流 | `GetString(ordinal)` / `GetChars(ordinal, ...)` 流式讀取大文本 |
完整代碼實現:插入與讀取 LONG 數據
1. 連接字符串最佳實踐
// app.config / web.config
// C# 獲取
string connStr = ConfigurationManager.ConnectionStrings["OracleConn"].ConnectionString;
2. 插入 LONG 數據 (參數化防注入、流式寫入)
using System;
using System.Data;
using System.Data.OracleClient;
using System.Configuration;
public class LongDataAccess
{
private readonly string _connStr;
public LongDataAccess(string connStrName = "OracleConn")
{
_connStr = ConfigurationManager.ConnectionStrings[connStrName].ConnectionString;
}
///
/// 插入/更新 LONG 文本內容
///
public void SaveAgreement(string agreementId, string content)
{
const string sql = @"
MERGE INTO test_long t
USING (SELECT :id AS agreement_id FROM dual) s
ON (t.agreement_id = s.agreement_id)
WHEN MATCHED THEN UPDATE SET t.agreement_content = :content
WHEN NOT MATCHED THEN INSERT (agreement_id, agreement_content) VALUES (:id, :content)";
using (var conn = new OracleConnection(_connStr))
using (var cmd = new OracleCommand(sql, conn))
{
// 關鍵:參數類型必須為 OracleType.LongVarChar
cmd.Parameters.Add(":id", OracleType.Char, 3).Value = agreementId;
cmd.Parameters.Add(":content", OracleType.LongVarChar).Value = content ?? (object)DBNull.Value;
// 長文本建議顯式設置 Size (可選,LongVarChar 默認無大小限制)
// cmd.Parameters[":content"].Size = -1; // -1 表示無限制
conn.Open();
int rows = cmd.ExecuteNonQuery();
Console.WriteLine($"Upsert rows: {rows}");
}
}
}
3. 讀取 LONG 數據 (DataAdapter 填充 DataSet / DataReader 流式讀取)
///
/// 方式一:DataAdapter 填充 DataSet (適合中小文本,全量載入內存)
///
public string GetAgreementByDataSet(string agreementId)
{
const string sql = "SELECT agreement_content FROM test_long WHERE agreement_id = :id";
using (var conn = new OracleConnection(_connStr))
using (var da = new OracleDataAdapter(sql, conn))
{
da.SelectCommand.Parameters.Add(":id", OracleType.Char, 3).Value = agreementId;
var ds = new DataSet();
da.Fill(ds); // 自動將 LONG 列映射為 DataColumn (Type=System.String)
if (ds.Tables[0].Rows.Count > 0)
return ds.Tables[0].Rows[0]["agreement_content"] as string;
return null;
}
}
///
/// 方式二:OracleDataReader 流式讀取 (適合超大文本,避免內存壓力)
///
public string GetAgreementByReader(string agreementId)
{
const string sql = "SELECT agreement_content FROM test_long WHERE agreement_id = :id";
using (var conn = new OracleConnection(_connStr))
using (var cmd = new OracleCommand(sql, conn))
{
cmd.Parameters.Add(":id", OracleType.Char, 3).Value = agreementId;
conn.Open();
using (var reader = cmd.ExecuteReader(CommandBehavior.SequentialAccess)) // 關鍵:SequentialAccess 允許流式讀取 LOB/LONG
{
if (reader.Read())
{
// GetString 直接返回完整字符串 (受 2GB 限制)
return reader.GetString(reader.GetOrdinal("agreement_content"));
// 超大文本分塊讀取示例 (GetChars)
/*
const int bufferSize = 8192;
char[] buffer = new char[bufferSize];
long fieldOffset = 0;
int charsRead;
using (var writer = new System.IO.StringWriter())
{
while ((charsRead = reader.GetChars(reader.GetOrdinal("agreement_content"), fieldOffset, buffer, 0, bufferSize)) > 0)
{
writer.Write(buffer, 0, charsRead);
fieldOffset += charsRead;
}
return writer.ToString();
}
*/
}
return null;
}
}
}
4. 完整測試程序
class Program
{
static void Main(string[] args)
{
var dao = new LongDataAccess();
string testId = "A01";
string longText = new string('X', 50000); // 50KB 測試文本
Console.WriteLine("=== Insert/Update ===");
dao.SaveAgreement(testId, longText);
Console.WriteLine("\n=== Read via DataSet ===");
string result1 = dao.GetAgreementByDataSet(testId);
Console.WriteLine($"Length: {result1?.Length ?? 0}, Match: {result1 == longText}");
Console.WriteLine("\n=== Read via DataReader ===");
string result2 = dao.GetAgreementByReader(testId);
Console.WriteLine($"Length: {result2?.Length ?? 0}, Match: {result2 == longText}");
Console.WriteLine("\nPress any key to exit...");
Console.ReadKey();
}
}
常見錯誤與排查
- ORA-01461: can bind a LONG value only for insert into a LONG column → 參數 `OracleType` 未設置為 `LongVarChar` (或 `LongRaw`),檢查 `cmd.Parameters.Add` 類型。
- ORA-01704: string literal too long → 直接在 SQL 文本中拼接超長字面量 (超過 4000 字符),**必須使用參數化綁定**。
- System.OutOfMemoryException → DataSet/DataReader `GetString` 將超大 LONG (>100MB) 全量載入托管堆。改用 `CommandBehavior.SequentialAccess` + `GetChars` 分塊流式處理,或遷移至 CLOB 使用 `OracleLob` 流。
- 連接池耗盡 / ORA-12519 → 未正確 `using` 釋放連接,或連接字符串 `Pooling=false` 導致頻繁建連。檢查 `Min/Max Pool Size` 配置。
- 編譯警告: 'OracleConnection' is obsolete → `System.Data.OracleClient` 已棄用。新項目**立即遷移**至 `Oracle.ManagedDataAccess.Core` (NuGet: `Oracle.ManagedDataAccess.Core`),命名空間改為 `Oracle.ManagedDataAccess.Client`,API 基本兼容,`OracleType.LongVarChar` 對應 `OracleDbType.LongRaw` / `Clob`。
遷移指南:從 System.Data.OracleClient 到 ODP.NET Managed (核心差異)
| 項目 | System.Data.OracleClient (舊) | Oracle.ManagedDataAccess.Client (新) |
| 命名空間 | `System.Data.OracleClient` | `Oracle.ManagedDataAccess.Client` |
| NuGet 包 | 內置 .NET Framework (無包) | `Oracle.ManagedDataAccess.Core` (跨平台) |
| 參數類型枚舉 | `OracleType.LongVarChar` | `OracleDbType.Clob` / `OracleDbType.LongRaw` |
| LONG 讀取 | `GetString` / `GetChars` | `GetString` / `GetChars` / `OracleClob` 對象 (推薦) |
| CLOB 寫入 | 不支持 (需先遷移表) | `OracleClob` 流式寫入、臨時 LOB、SecureFile 支持 |
| .NET Core 支持 | ❌ | ✅ (Windows/Linux/macOS) |
| 配置文件 | `` | `` section |
總結
- LONG 類型在 `System.Data.OracleClient` 中通過 `OracleType.LongVarChar` 參數實現參數化讀寫,`DataAdapter.Fill` 簡單易用,`DataReader.SequentialAccess` 適合大對象流式處理。
- **嚴禁** SQL 拼接長文本,**必須** 參數化綁定防注入且繞過 4000 字符限制。
- `System.Data.OracleClient` **已棄用**,**新項目禁止使用**,存量系統應規劃遷移至 `Oracle.ManagedDataAccess.Core`,同時**將數據庫 LONG 列遷移至 CLOB**,獲得完整功能與性能。
- 連接池配置 (`Pooling=true, Min/Max Pool Size`) 對高併發應用至關重要,務必 `using` 正確釋放資源。