首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 數據庫知識 >  >> Oracle 資料庫 >> Oracle 資料庫基礎

C# 呼叫 Oracle 預存程序完整實戰:ODP.NET 核心用法、Ref Cursor 結果集與 Function 返回值處理

     編輯:Oracle 資料庫基礎

.NET 生態下存取 Oracle,**ODP.NET (Oracle.ManagedDataAccess)** 是官方推薦、效能最優、功能最全的資料提供者。本文以完整可運行的 C# 程式碼,示範如何呼叫 Oracle Package 中的 **Function (標量返回)** 與 **Procedure (Ref Cursor 結果集)**,涵蓋參數綁定、連線管理、資料繫結與異常處理的企業級最佳實踐。

資料庫端:建立 Package Spec & Body


-- 1. Package 規範 (介面定義)
CREATE OR REPLACE PACKAGE scott.pkg_demo AS
  TYPE ref_cur IS REF CURSOR;           -- 定義弱類型游標
  
  FUNCTION f_get_prefix(p_str IN VARCHAR2) RETURN VARCHAR2;  -- 純標量函數
  PROCEDURE p_get_employees(p_deptno IN NUMBER, p_cursor OUT ref_cur); -- 回傳結果集
END pkg_demo;
/

-- 2. Package 實作
CREATE OR REPLACE PACKAGE BODY scott.pkg_demo AS
  FUNCTION f_get_prefix(p_str IN VARCHAR2) RETURN VARCHAR2 AS
    v_result VARCHAR2(100) := 'PREFIX_';
  BEGIN
    RETURN v_result || p_str;
  END;

  PROCEDURE p_get_employees(p_deptno IN NUMBER, p_cursor OUT ref_cur) AS
  BEGIN
    OPEN p_cursor FOR
      SELECT empno, ename, sal, hiredate
      FROM emp
      WHERE deptno = p_deptno
      ORDER BY sal DESC;
  END;
END pkg_demo;
/

C# 端完整實作

1. NuGet 套件安裝


Install-Package Oracle.ManagedDataAccess
// 或 .NET Core / .NET 5+:
dotnet add package Oracle.ManagedDataAccess.Core

2. 連線字串配置 (appsettings.json / Web.config)


{
  "ConnectionStrings": {
    "OracleDb": "User Id=scott;Password=tiger;Data Source=//host:1521/orclpdb;Pooling=true;Min Pool Size=5;Max Pool Size=100;"
  }
}

3. 呼叫 Function (標量返回值)


using System;
using System.Data;
using Oracle.ManagedDataAccess.Client;
using Oracle.ManagedDataAccess.Types;

public class OracleFunctionDemo
{
    private readonly string _connStr;

    public OracleFunctionDemo(string connStr) => _connStr = connStr;

    public string CallGetPrefix(string input)
    {
        const string sql = "pkg_demo.f_get_prefix"; // Package.Function 名稱
        
        using (var conn = new OracleConnection(_connStr))
        using (var cmd = new OracleCommand(sql, conn))
        {
            cmd.CommandType = CommandType.StoredProcedure;
            
            // 1. 輸入參數
            var pInput = new OracleParameter("p_str", OracleDbType.Varchar2, 100)
            {
                Direction = ParameterDirection.Input,
                Value = input
            };
            cmd.Parameters.Add(pInput);
            
            // 2. 返回值參數 (關鍵: ParameterDirection.ReturnValue)
            var pReturn = new OracleParameter("return_value", OracleDbType.Varchar2, 200)
            {
                Direction = ParameterDirection.ReturnValue
            };
            cmd.Parameters.Add(pReturn);
            
            conn.Open();
            cmd.ExecuteNonQuery(); // Function 使用 ExecuteNonQuery
            
            return pReturn.Value?.ToString() ?? string.Empty;
        }
    }
}

4. 呼叫 Procedure 取得 Ref Cursor 結果集


public class OracleProcedureDemo
{
    private readonly string _connStr;

    public OracleProcedureDemo(string connStr) => _connStr = connStr;

    public DataTable GetEmployeesByDept(int deptNo)
    {
        const string sql = "pkg_demo.p_get_employees";
        var dt = new DataTable();
        
        using (var conn = new OracleConnection(_connStr))
        using (var cmd = new OracleCommand(sql, conn))
        {
            cmd.CommandType = CommandType.StoredProcedure;
            
            // 輸入參數
            cmd.Parameters.Add(new OracleParameter("p_deptno", OracleDbType.Int32)
            {
                Direction = ParameterDirection.Input,
                Value = deptNo
            });
            
            // 輸出 Ref Cursor (關鍵: OracleDbType.RefCursor + ParameterDirection.Output)
            cmd.Parameters.Add(new OracleParameter("p_cursor", OracleDbType.RefCursor)
            {
                Direction = ParameterDirection.Output
            });
            
            // 使用 OracleDataAdapter 填充 DataTable (自動處理游標讀取)
            using (var da = new OracleDataAdapter(cmd))
            {
                conn.Open();
                da.Fill(dt); // 內部會讀取 Ref Cursor 並關閉游標
            }
        }
        return dt;
    }
    
    // 進階:使用 OracleDataReader 逐行處理 (記憶體友善)
    public IEnumerable StreamEmployees(int deptNo)
    {
        const string sql = "pkg_demo.p_get_employees";
        
        using (var conn = new OracleConnection(_connStr))
        using (var cmd = new OracleCommand(sql, conn))
        {
            cmd.CommandType = CommandType.StoredProcedure;
            cmd.Parameters.Add(new OracleParameter("p_deptno", OracleDbType.Int32) 
            { Direction = ParameterDirection.Input, Value = deptNo });
            cmd.Parameters.Add(new OracleParameter("p_cursor", OracleDbType.RefCursor) 
            { Direction = ParameterDirection.Output });
            
            conn.Open();
            using (var reader = cmd.ExecuteReader()) // 返回 OracleDataReader
            {
                while (reader.Read())
                {
                    yield return new Employee
                    {
                        EmpNo = reader.GetInt32(0),
                        EName = reader.GetString(1),
                        Sal = reader.GetDecimal(2),
                        HireDate = reader.GetDateTime(3)
                    };
                }
            }
        }
    }
}

public class Employee
{
    public int EmpNo { get; set; }
    public string EName { get; set; }
    public decimal Sal { get; set; }
    public DateTime HireDate { get; set; }
}

關鍵技術細節對照表

場景OracleDbTypeParameterDirection關鍵點
Function 返回值對應返回類型 (Varchar2, Int32, Decimal...)ReturnValue參數名慣用 return_value 或 RETURN,必須加在 Parameters 集合中
IN 參數對應類型InputValue 屬性賦值
OUT 參數 (標量)對應類型Output需指定 Size (Varchar2 等),執行後讀取 Value
IN OUT 參數對應類型InputOutput執行前給初值,執行後取新值
Ref CursorRefCursorOutput搭配 OracleDataAdapter.Fill() 或 ExecuteReader() 讀取

進階最佳實務

  1. 連線池管理:連線字串啟用 Pooling=true,設定合理 Min/Max Pool Size,避免連線風暴。
  2. 參數化防注入:**絕不**使用字串拼接 SQL,所有輸入皆走 OracleParameter。
  3. 非同步支援:.NET Core / .NET 5+ 使用 await cmd.ExecuteNonQueryAsync()、await reader.ReadAsync() 提升吞吐量。
  4. 資料類型映射:Oracle NUMBER → C# decimal (避免精度損失);DATE/TIMESTAMP → DateTime / DateTimeOffset。
  5. 例外處理策略:捕捉 OracleException,依 ErrorCode (如 12154, 12541, 01017) 分類處理、記錄完整堆疊與 SQL 文本。
  6. 大量資料流式處理:優先使用 OracleDataReader + yield return 避免一次性載入巨大 DataTable 造成 OOM。

常見錯誤排查

  • ORA-06550 / PLS-00306: 參數名稱/類型/順序不匹配 → 核對 Package 定義與 C# 參數集合 (名稱不區分大小寫,但建議一致)。
  • ORA-01008: 未綁定所有參數 → 檢查是否遺漏 ReturnValue 或 OUT 參數。
  • InvalidOperationException: Connection must be open: 執行命令前未 conn.Open()。
  • Ref Cursor 讀取失敗:確認 Procedure 中 OPEN cursor FOR ... 正確執行,且 C# 端 OracleDbType.RefCursor 拼寫正確。

掌握 ODP.NET 的 OracleParameter 機制與 RefCursor 處理模式,讓 C# 應用與 Oracle 預存程序無縫整合,是構建高效能、強類型、可維護企業級資料存取層的核心技能。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved