在C#程序中,我需要在Oracle数据库中调用存储过程,该数据库具有以下约定:
PKG_ENTITY.ALTER_ENTITY (VARCHAR2 NAME, VARCHAR2 FULLNAME, ATTRS_TYPE ATTRS, VARCHAR2 STATUS, INTEGER OUT RESULT, VARCHAR2 OUT ERRORMSG).
RESULT
和ERRORMSG
参数是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();
}
}
其中query =
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'是无效名称。
答案 0 :(得分: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;
然后在执行操作时以pPropId
的方向传递参数pPropValue
,pEntityId
和ParameterDirection.Input
,并传递pResult
和{{1} },方向为pErrorMsg
。
答案 1 :(得分:0)
PL存储过程的签名应为
PKG_ENTITY.ALTER_ENTITY (VARCHAR2 NAME, VARCHAR2 FULLNAME, ATTRS_TYPE ATTRS, VARCHAR2 STATUS, INTEGER OUT RESULT, VARCHAR2 OUT ERRORMSG)
请注意,OUT
已添加到参数声明中。
在c#
代码中,您需要确保将参数标记为输出
.Parameters.Add("RESULT", OracleDbType.Int32).Direction = ParameterDirection.Output;
完成后,您只需要为PL / SQL存储过程中的参数分配所需的值即可。
RESULT:= 0;
答案 2 :(得分:0)
调用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