Как вызвать хранимую процедуру (с пользовательским параметром) через код? - PullRequest
0 голосов
/ 01 июля 2019

Из моей программы на 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» не являются допустимыми именами.

Ответы [ 3 ]

1 голос
/ 01 июля 2019

В этом ответе говорится, что нельзя передать INDEX BY VARCHAR2 ассоциативный массив в C #.Вместо этого вы можете построить ассоциативный массив в анонимном 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 и pErrorMsg с направлением ParameterDirection.Output.

0 голосов
/ 01 июля 2019

Вызов процедуры 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

На самом деле, я уже не помню, почему я cmd.Parameters("RESULT").DbType = DbType.Int32;. Возможно, необходимо было сделать мой код совместимым с провайдером ODP.NET 1.x и 2.0, см. Обязательный перенос хранимых процедур .NET 1.x в .NET 2.0 или более позднюю версию

0 голосов
/ 01 июля 2019

Подпись хранимой процедуры 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;
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...