【问题标题】:How to call a stored procedure (with user parameter) via code?如何通过代码调用存储过程(带有用户参数)?
【发布时间】:2019-11-11 21:38:47
【问题描述】:

从我的 C# 程序中,我需要调用 Oracle 数据库中的存储过程,该数据库具有以下约定:

PKG_ENTITY.ALTER_ENTITY (VARCHAR2 NAME, VARCHAR2 FULLNAME, ATTRS_TYPE ATTRS, VARCHAR2 STATUS, INTEGER OUT RESULT, VARCHAR2 OUT ERRORMSG). 

RESULTERRORMSG 参数是 OUT 参数。

我知道它指定的 ATTRS_TYPE 类型:

TYPE ATTRS_TYPE IS TABLE OF VARCHAR2(2000) INDEX BY VARCHAR2(30);

我以前是这样调用这个存储过程的:

 private void ExecuteNonQuery(string query, params OracleParameter[] parameters)
 {
      using (var connection = new OracleConnection(_connectionString))
      {
            var command = new OracleCommand(query, connection) { CommandType = CommandType.Text };
            connection.Open();
            command.Parameters.AddRange(parameters);
            command.ExecuteNonQuery();
      }
 }

查询 =

DECLARE
    tNAME varchar2(100);
    tATTRS PKG_ENTITY.ATTRS_TYPE;

    tRESULT INTEGER;
    tERRORMSG varchar2(100);


BEGIN

    tNAME := :pEntityId;

     tATTRS(:pPropId) := :pPropValue;

    PKG_ENTITY.ALTER_ENTITY(tUSERNAME,NULL,tATTRS,NULL,tRESULT,tERRORMSG);
END;

参数值:pEntityId、pPropId和pPropValue在代码中定义。

一切都很好,但后来我收到了必须注销 tRESULT 和 tERRORMSG 参数值的要求,因此我遇到了很大的困难。我想在调用存储过程后通过添加 SELECT 来修改查询。像这样:

DECLARE
    tNAME varchar2(100);
    tATTRS PKG_ENTITY.ATTRS_TYPE;

    tRESULT INTEGER;
    tERRORMSG varchar2(100);


BEGIN

    tNAME := :pEntityId;

     tATTRS(:pPropId) := :pPropValue;

    PKG_USER.ALTER_USER(tUSERNAME,NULL,tATTRS,NULL,tRESULT,tERRORMSG);
    SELECT tRESULT, tERRORMSG FROM DUAL;
END;

但是从pl/sql 语言的角度来看,这样的查询是不正确的。因此,我得出的结论是,我需要直接使用存储过程调用,代码应该是这样的:

private ProcedureResult ExecuteStoredProcedure(string procedureName)
{
    using (var connection = new OracleConnection(_connectionString))
    {
        var command = new OracleCommand(procedureName, connection) { CommandType = CommandType.StoredProcedure };
        connection.Open();
        command.Parameters.Add("NAME", OracleDbType.Varchar2, "New name", ParameterDirection.Input);
        command.Parameters.Add("FULLNAME", OracleDbType.Varchar2, "New fullname", ParameterDirection.Input);
        var attr = new EntityAttribute() { attribute1 = "id", attribute2 = "value"};
        command.Parameters.Add("ATTRS", EntityAttribute, "New fullname", ParameterDirection.Input);
        command.Parameters.Add("STATUS", OracleDbType.Varchar2, "Status", ParameterDirection.Input);
        command.Parameters.Add("RESULT", OracleDbType.Int32).Direction = ParameterDirection.Output;
        command.Parameters.Add("ERRORMSG", OracleDbType.Varchar2).Direction = ParameterDirection.Output;

        command.ExecuteNonQuery();

        return new ProcedureResult()
        {
            StatusCode = int.Parse(command.Parameters["RESULT"].Value.ToString()),
            Message = command.Parameters["ERRORMSG"].Value.ToString()
        };
    }
}

在这里我遇到了PKG_ENTITY.ATTRS_TYPE 类型定义的困难。

TYPE ATTRS_TYPE IS TABLE OF VARCHAR2 (2000) INDEX BY VARCHAR2 (30);

我知道有一个IOracleCustomType接口,但是我不明白如何正确实现它。

例如

[OracleCustomTypeMapping("PKG_ENTITY.ATTRS_TYPE")]
public class EntityAttribute : INullable, IOracleCustomType
{
    [OracleObjectMapping("ATTRIBUTE1")]
    public string attribute1 { get; set; }
    [OracleObjectMapping("ATTRIBUTE2")]
    public string attribute2 { get; set; }

    public bool IsNull => throw new System.NotImplementedException();
    public void FromCustomObject(OracleConnection con, IntPtr pUdt)
    {
        throw new NotImplementedException(); 
    }

    public void ToCustomObject(OracleConnection con, IntPtr pUdt)
    {
        throw new NotImplementedException();
    }
}

这个类的字段名称应该是什么?我了解“ATTRIBUTE1”和“ATTRIBUTE2”不是有效名称。

【问题讨论】:

  • 您永远不会在执行之前将RESULTERRORMSG 参数添加到命令中。检查how to add output parameters
  • @bradbury9 我在这行代码'command.Parameters.AddRange(parameters);'中添加参数。无论如何,问题不在于这个。
  • 该行没有说明该数组包含什么。编辑您的问题并添加数组声明。输出参数示例:.Parameters.Add("return_code", OracleDbType.Int32).Direction = ParameterDirection.Output;。顺便说一句,在您的 PL/SQL 中,参数未声明为 OUTPUT
  • This answer 声明您不能在 C# 中传递 INDEX BY VARCHAR2 关联数组。相反,您可以在匿名 PL/SQL 块中构建关联数组并从那里调用过程(就像您最初所做的那样)。

标签: c# oracle stored-procedures plsql


【解决方案1】:

This answer 声明不能在 C# 中传递 INDEX BY VARCHAR2 关联数组。相反,您可以在匿名 PL/SQL 块中构建关联数组并从那里调用过程(就像您最初所做的那样)。

所以你可以使用:

DECLARE
  tATTRS PKG_ENTITY.ATTRS_TYPE;
BEGIN
  tATTRS(:pPropId) := :pPropValue;

  PKG_USER.ALTER_USER(
    NAME     => :pEntityId,
    USERNAME => NULL,
    ATTRS    => tATTRS,
    STATUS   => NULL,
    RESULT   => :pResult,
    ERRORMSG => :pErrorMsg
  );
END;

然后像刚才一样传入pPropIdpPropValuepEntityId的参数,方向为ParameterDirection.Input,并传入pResultpErrorMsg,方向为ParameterDirection.Output

【讨论】:

  • 我不明白在这种情况下如何传递和取出参数。你能帮帮我吗?
【解决方案2】:

调用 PL/SQL 过程要简单得多(未经测试,但我想你会明白的):

var cmd = new OracleCommand("BEGIN PKG_ENTITY.ALTER_ENTITY(:NAME, :FULLNAME, :ATTRS, :STATUS, :RESULT, :ERRORMSG); END;"), connection);
cmd.CommandType = CommandType.Text;

// should work as well
// var cmd = new SqlCommand("PKG_ENTITY.ALTER_ENTITY(:NAME, :FULLNAME, :ATTRS, :STATUS, :RESULT, :ERRORMSG)"), connection);
// cmd.CommandType = CommandType.StoredProcedure;


cmd.Parameters.Add("NAME", OracleDbType.Varchar2, ParameterDirection.Input).Value = "New name"
cmd.Parameters.Add("FULLNAME", OracleDbType.Varchar2, ParameterDirection.Input).Value = "New fullname"

par = cmd.Parameters.Add("ATTRS", OracleDbType.Varchar2, ParameterDirection.Input);
par.CollectionType = OracleCollectionType.PLSQLAssociativeArray;
var arr string[] = new string[] {"id" , "value"};
par.Value = arr;
par.Size = arr .Count;

cmd.Parameters.Add("STATUS", OracleDbType.Varchar2, ParameterDirection.Input).Value = "Status";
cmd.Parameters.Add("RESULT", OracleDbType.Int32, ParameterDirection.Output);
cmd.Parameters("RESULT").DbType = DbType.Int32;
cmd.Parameters.Add("ERRORMSG", OracleDbType.Varchar2, 100, null, ParameterDirection.Output);
cmd.Parameters("ERRORMSG").DbType = DbType.String;

cmd.ExecuteNonQuery();
var result = System.Convert.ToInt32(cmd.Parameters("RESULT").Value);
var errmsg = cmd.Parameters("ERRORMSG").Value.ToString();

也许这个有帮助:PL/SQL Associative Array Binding

实际上,我已经不记得为什么要cmd.Parameters("RESULT").DbType = DbType.Int32;了。也许需要使我的代码与 ODP.NET Provider 1.x 和 2.0 兼容,请参阅Mandatory Migration of .NET 1.x Stored Procedures to .NET 2.0 or Later

【讨论】:

  • OP 的数据类型是INDEX BY VARCHAR2(30)。这适用于该数据类型吗? This answer says that it won't. 如果是,它会将输入 new string[] {"id" , "value"} 映射到键值对还是将其映射到 "0"->"id","1"->"value"
  • @MT0 你是对的,我错过了。显然它不适用于INDEX BY VARCHAR2(30)。在我的回答中,我更关注短语“必须注销 tRESULT 和 tERRORMSG 参数的值,因此我遇到了很大的困难”
【解决方案3】:

PL存储过程的签名应该是

PKG_ENTITY.ALTER_ENTITY (VARCHAR2 NAME, VARCHAR2 FULLNAME, ATTRS_TYPE ATTRS, VARCHAR2 STATUS, INTEGER OUT RESULT, VARCHAR2 OUT ERRORMSG)

请注意OUT 已添加到参数声明中。

c#code 中,您需要确保将参数标记为输出

.Parameters.Add("RESULT", OracleDbType.Int32).Direction = ParameterDirection.Output;

完成后,您只需为 PL/SQL 存储过程中的参数分配所需的值。

RESULT:= 0;

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-09-18
    • 1970-01-01
    • 2023-03-05
    • 2018-05-29
    • 2011-10-08
    • 1970-01-01
    相关资源
    最近更新 更多