You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在C#中调用带参数且返回记录的Oracle函数?报错求助

解决ORA-06550/PLS-00221:正确调用Oracle函数(返回4列记录)

你遇到的ORA-06550和PLS-00221错误,本质是把Oracle函数(Function)当成了存储过程(Stored Procedure)来调用。函数和存储过程的调用规则完全不同:函数必须有返回值,而存储过程不需要;在C#中调用时,参数处理和CommandType的设置也有明显区别。

下面分两种最常见的场景,给你提供可直接复用的代码方案:


场景1:函数返回单条4列记录(自定义OBJECT/RECORD类型)

假设你的Oracle函数定义是这样的(返回一个包含4列的自定义对象):

-- 先定义自定义对象类型
CREATE OR REPLACE TYPE receipt_record AS OBJECT (
    receipt_no VARCHAR2(20),
    amount NUMBER(10,2),
    create_date DATE,
    status VARCHAR2(10)
);

-- 定义函数
CREATE OR REPLACE FUNCTION get_receipts(
    p_store_id IN NUMBER,
    p_start_date IN DATE,
    p_end_date IN DATE
) RETURN receipt_record AS
    v_result receipt_record;
BEGIN
    -- 业务逻辑:查询单条记录赋值给v_result
    SELECT receipt_no, amount, create_date, status 
    INTO v_result 
    FROM receipt_table 
    WHERE store_id = p_store_id 
      AND create_date BETWEEN p_start_date AND p_end_date
      AND ROWNUM = 1;
    RETURN v_result;
END;
/

C#调用代码

using Oracle.ManagedDataAccess.Client; // 推荐使用托管驱动,无需Oracle客户端
using System;

class ReceiptHelper
{
    public static void GetSingleReceipt()
    {
        string connString = "Data Source=你的Oracle实例;User Id=用户名;Password=密码;";
        using (OracleConnection conn = new OracleConnection(connString))
        {
            conn.Open();
            // 用PL/SQL块封装函数调用,把返回的记录拆解成单独的输出参数
            string plsql = @"
                DECLARE
                    v_receipt receipt_record;
                BEGIN
                    v_receipt := get_receipts(:p_store_id, :p_start_date, :p_end_date);
                    :out_receipt_no := v_receipt.receipt_no;
                    :out_amount := v_receipt.amount;
                    :out_create_date := v_receipt.create_date;
                    :out_status := v_receipt.status;
                END;";
            
            using (OracleCommand cmd = new OracleCommand(plsql, conn))
            {
                cmd.CommandType = CommandType.Text;
                
                // 添加输入参数
                cmd.Parameters.Add("p_store_id", OracleDbType.Int32).Value = 101; // 替换成你的门店ID
                cmd.Parameters.Add("p_start_date", OracleDbType.Date).Value = DateTime.Now.AddMonths(-1);
                cmd.Parameters.Add("p_end_date", OracleDbType.Date).Value = DateTime.Now;
                
                // 添加对应4列的输出参数
                cmd.Parameters.Add("out_receipt_no", OracleDbType.Varchar2, 20).Direction = ParameterDirection.Output;
                cmd.Parameters.Add("out_amount", OracleDbType.Decimal).Direction = ParameterDirection.Output;
                cmd.Parameters.Add("out_create_date", OracleDbType.Date).Direction = ParameterDirection.Output;
                cmd.Parameters.Add("out_status", OracleDbType.Varchar2, 10).Direction = ParameterDirection.Output;
                
                // 执行命令
                cmd.ExecuteNonQuery();
                
                // 读取结果
                string receiptNo = cmd.Parameters["out_receipt_no"].Value.ToString();
                decimal amount = Convert.ToDecimal(cmd.Parameters["out_amount"].Value);
                DateTime createDate = Convert.ToDateTime(cmd.Parameters["out_create_date"].Value);
                string status = cmd.Parameters["out_status"].Value.ToString();
                
                Console.WriteLine($"小票号:{receiptNo},金额:{amount:C},日期:{createDate:yyyy-MM-dd},状态:{status}");
            }
        }
    }
}

场景2:函数返回多行4列结果集(REF CURSOR)

如果你的函数是返回多条记录(比如查询符合条件的所有小票),通常会用SYS_REFCURSOR作为返回类型,函数定义如下:

CREATE OR REPLACE FUNCTION get_receipts(
    p_store_id IN NUMBER,
    p_start_date IN DATE,
    p_end_date IN DATE
) RETURN SYS_REFCURSOR AS
    v_cursor SYS_REFCURSOR;
BEGIN
    OPEN v_cursor FOR
        SELECT receipt_no, amount, create_date, status 
        FROM receipt_table 
        WHERE store_id = p_store_id 
          AND create_date BETWEEN p_start_date AND p_end_date;
    RETURN v_cursor;
END;
/

C#调用代码

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

class ReceiptHelper
{
    public static void GetReceiptList()
    {
        string connString = "Data Source=你的Oracle实例;User Id=用户名;Password=密码;";
        using (OracleConnection conn = new OracleConnection(connString))
        {
            conn.Open();
            // 用PL/SQL块调用函数,获取游标输出
            string plsql = @"
                BEGIN
                    :out_cursor := get_receipts(:p_store_id, :p_start_date, :p_end_date);
                END;";
            
            using (OracleCommand cmd = new OracleCommand(plsql, conn))
            {
                cmd.CommandType = CommandType.Text;
                
                // 输入参数
                cmd.Parameters.Add("p_store_id", OracleDbType.Int32).Value = 101;
                cmd.Parameters.Add("p_start_date", OracleDbType.Date).Value = DateTime.Now.AddMonths(-1);
                cmd.Parameters.Add("p_end_date", OracleDbType.Date).Value = DateTime.Now;
                
                // 输出游标参数
                cmd.Parameters.Add("out_cursor", OracleDbType.RefCursor).Direction = ParameterDirection.Output;
                
                // 执行并读取结果集
                using (OracleDataReader reader = cmd.ExecuteReader())
                {
                    while (reader.Read())
                    {
                        string receiptNo = reader["receipt_no"].ToString();
                        decimal amount = Convert.ToDecimal(reader["amount"]);
                        DateTime createDate = Convert.ToDateTime(reader["create_date"]);
                        string status = reader["status"].ToString();
                        
                        Console.WriteLine($"小票号:{receiptNo},金额:{amount:C},日期:{createDate:yyyy-MM-dd},状态:{status}");
                    }
                }
            }
        }
    }
}

关键注意事项

  1. 不要混淆函数和存储过程:如果硬要设置CommandType = CommandType.StoredProcedure,必须显式添加名为RETURN_VALUE的输出参数,但用PL/SQL块的方式更灵活,尤其适合复杂返回类型。
  2. 参数类型要严格对应:比如Oracle的DATE对应C#的OracleDbType.Date,NUMBER(10,2)对应OracleDbType.Decimal,避免类型转换错误。
  3. 资源自动释放:一定要用using语句包裹连接、命令、阅读器,确保资源及时释放,避免内存泄漏。
  4. 参数名匹配:Oracle默认支持按位置绑定,但推荐按参数名绑定,避免因参数顺序变化导致的错误。

内容的提问来源于stack exchange,提问作者istudent

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.27 04:27:35