C# 调用替换后 Oracle 包无效

Oracle package invalid after C# call to replace

我假设我从 C# 运行 SQL 脚本中做错了什么,但在 Internet 上进行大量搜索后,我仍然不知道哪里出了问题.. .

我在通过 C# 加载 Oracle 包和包主体时遇到问题。当我通过脚本在 SQL*PLUS 中加载包时,对脚本中函数的后续调用工作正常。当我从 C# 调用它时,它也有效。但是,当我从 C# 加载相同的脚本时,脚本的 运行ning 似乎有效,但随后对包函数的调用(来自 C# 和 SQL*PLUS)失败并显示 PLS- 00905 错误 ("object ANON.MY_PKG is invalid").

SQL脚本("simple.sql")的内容是:

CREATE OR REPLACE PACKAGE my_pkg IS
    FUNCTION my_function (
        p_1 IN VARCHAR2
    )RETURN VARCHAR2;
END my_pkg;
/
CREATE OR REPLACE PACKAGE BODY my_pkg IS
    FUNCTION my_function (
        p_1 IN VARCHAR2
    ) RETURN VARCHAR2 AS p_result VARCHAR2(2000);
        BEGIN
        RETURN p_1;
    END my_function;
END my_pkg;
/

运行 它在 SQL*PLUS 中工作正常...

SQL> SET SERVEROUTPUT ON
SQL> @"D:\_temp\simple.sql"

Package created.


Package body created.

SQL> EXEC DBMS_OUTPUT.PUT_LINE(my_pkg.my_function('hello'));
hello

PL/SQL procedure successfully completed.

然后在 C# 程序中调用该函数(与 SQL*PLUS 中使用的相同的 Oracle 用户)工作正常,直到 SQL 脚本在 运行程序。

using Oracle.ManagedDataAccess.Client;
using Oracle.ManagedDataAccess.Types;
using System;
using System.Collections.Generic;
using System.Configuration;
using System.Data;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace TestApplication
{
    class Program
    {
        static void Main(string[] args)
        {

                OracleConnection conn = getConnection();

                Debug.WriteLine("first call to my_function:");
                callMyFunction(conn);
                loadMyPackage(conn);

                try
                {
                Debug.WriteLine("second call to my_function:");
                callMyFunction(conn);
            }
            catch (Exception ex)
            {
                Debug.WriteLine("ex = " + ex.ToString());
            }

        }

        private static OracleConnection getConnection()
        {
            string connStr = "redacted...";
            OracleConnection conn = new OracleConnection();
            conn.ConnectionString = connStr;
            conn.Open();
            return conn;
        }

        private static void loadMyPackage(OracleConnection conn)
        {
            OracleCommand command = new OracleCommand();
            command.CommandType = CommandType.Text;
            command.CommandText = File.ReadAllText(@"D:\_temp\simple.sql");
            command.Connection = conn;
            int response = command.ExecuteNonQuery();
            Debug.WriteLine("response = " + response);
        }

        private static void callMyFunction(OracleConnection conn)
        {
            int RETURN_BUFFER_SIZE = 32767;
            OracleCommand cmd = new OracleCommand();

            cmd.Connection = conn;
            cmd.CommandText = "my_pkg.my_function";
            cmd.CommandType = CommandType.StoredProcedure;

            cmd.Parameters.Add("returnVal", OracleDbType.Varchar2, RETURN_BUFFER_SIZE);
            cmd.Parameters["returnVal"].Direction = ParameterDirection.ReturnValue;

            cmd.Parameters.Add("p_1", OracleDbType.Varchar2);
            cmd.Parameters["p_1"].Value = "hello";

            cmd.ExecuteNonQuery();
            string result = cmd.Parameters[0].Value.ToString();
            Debug.WriteLine("function result = " + result);
        }
    }
}

重新加载后 ("loadMyPackage") 出错。具体来说:

first call to my_function:
function result = hello
response = -1
second call to my_function:
A first chance exception of type 'Oracle.ManagedDataAccess.Client.OracleException' occurred in Oracle.ManagedDataAccess.dll
ex = Oracle.ManagedDataAccess.Client.OracleException (0x00001996): ORA-06550: line 1, column 15:
PLS-00905: object ANON.MY_PKG is invalid
ORA-06550: line 1, column 7:
PL/SQL: Statement ignored
   at OracleInternal.ServiceObjects.OracleCommandImpl.VerifyExecution(OracleConnectionImpl connectionImpl, Int32& cursorId, Boolean bThrowArrayBindRelatedErrors, OracleException& exceptionForArrayBindDML, Boolean& hasMoreRowsInDB, Boolean bFirstIterationDone)
   at OracleInternal.ServiceObjects.OracleCommandImpl.VerifyExecution(OracleConnectionImpl connectionImpl, Int32& cursorId, Boolean bThrowArrayBindRelatedErrors, OracleException& exceptionForArrayBindDML, Boolean bFirstIterationDone)
   at OracleInternal.ServiceObjects.OracleCommandImpl.ExecuteNonQuery(String commandText, OracleParameterCollection paramColl, CommandType commandType, OracleConnectionImpl connectionImpl, Int32 longFetchSize, Int64 clientInitialLOBFS, OracleDependencyImpl orclDependencyImpl, Int64[]& scnFromExecution, OracleParameterCollection& bindByPositionParamColl, Boolean& bBindParamPresent, OracleException& exceptionForArrayBindDML, Boolean isFromEF)
   at Oracle.ManagedDataAccess.Client.OracleCommand.ExecuteNonQuery()
   at TestApplication.Program.callMyFunction(OracleConnection conn) in c:\Users\ANON\Documents\Visual Studio 2013\Projects\myProject\TestApplication\Program.cs:line 73
   at TestApplication.Program.Main(String[] args) in c:\Users\ANON\Documents\Visual Studio 2013\Projects\myProject\TestApplication\Program.cs:line 29
The thread 0x2838 has exited with code 259 (0x103).
The thread 0x2d9c has exited with code 259 (0x103).
The program '[11268] TestApplication.vshost.exe' has exited with code 0 (0x0).

如何让 C# 正确地 运行 脚本?

更新: 根据反馈,我将 SQL 脚本分成两部分。第一部分 ("simple_A.sql") 现在是:

CREATE OR REPLACE PACKAGE my_pkg IS
    FUNCTION my_function (
        p_1 IN VARCHAR2
    )RETURN VARCHAR2;
END my_pkg;
/

第二部分("simple_B.sql")是:

CREATE OR REPLACE PACKAGE BODY my_pkg IS  -- body
    FUNCTION my_function (
        p_1 IN VARCHAR2
    ) RETURN VARCHAR2 AS p_result VARCHAR2(2000);
        BEGIN
        RETURN p_1;
    END my_function;
END my_pkg;
/

在此之后,更改,我通过以下方式验证它在 SQL*PLUS 中仍然有效:

SQL> drop package my_pkg;

Package dropped.

SQL> @"D:\_temp\simple_A.sql"

Package created.

SQL> @"D:\_temp\simple_B.sql"

Package body created.

SQL> EXEC DBMS_OUTPUT.PUT_LINE(my_pkg.my_function('hello'));
hello

PL/SQL procedure successfully completed.

SQL>

然后我通过以下更改更新 C# 代码以使用这两个脚本:

private static void loadMyPackage(OracleConnection conn)
{
    OracleCommand command_A = new OracleCommand();
    command_A.CommandType = CommandType.Text;
    command_A.CommandText = File.ReadAllText(@"D:\_temp\simple_A.sql");
    command_A.Connection = conn;
    int response = command_A.ExecuteNonQuery();
    Debug.WriteLine("response A = " + response);

    OracleCommand command_B = new OracleCommand();
    command_B.CommandType = CommandType.Text;
    command_B.CommandText = File.ReadAllText(@"D:\_temp\simple_B.sql");
    command_B.Connection = conn;
    response = command_B.ExecuteNonQuery();
    Debug.WriteLine("response B = " + response); 
}

但是,我仍然遇到同样的错误。具体来说,现在的输出如下:

first call to my_function:
function result = hello
response A = -1
response B = -1
second call to my_function:
A first chance exception of type 'Oracle.ManagedDataAccess.Client.OracleException' occurred in Oracle.ManagedDataAccess.dll
ex = Oracle.ManagedDataAccess.Client.OracleException (0x00001996): ORA-06550: line 1, column 15:
PLS-00905: object ANON.MY_PKG is invalid
ORA-06550: line 1, column 7:
PL/SQL: Statement ignored
   at OracleInternal.ServiceObjects.OracleCommandImpl.VerifyExecution(OracleConnectionImpl connectionImpl, Int32& cursorId, Boolean bThrowArrayBindRelatedErrors, OracleException& exceptionForArrayBindDML, Boolean& hasMoreRowsInDB, Boolean bFirstIterationDone)
   at OracleInternal.ServiceObjects.OracleCommandImpl.VerifyExecution(OracleConnectionImpl connectionImpl, Int32& cursorId, Boolean bThrowArrayBindRelatedErrors, OracleException& exceptionForArrayBindDML, Boolean bFirstIterationDone)
   at OracleInternal.ServiceObjects.OracleCommandImpl.ExecuteNonQuery(String commandText, OracleParameterCollection paramColl, CommandType commandType, OracleConnectionImpl connectionImpl, Int32 longFetchSize, Int64 clientInitialLOBFS, OracleDependencyImpl orclDependencyImpl, Int64[]& scnFromExecution, OracleParameterCollection& bindByPositionParamColl, Boolean& bBindParamPresent, OracleException& exceptionForArrayBindDML, Boolean isFromEF)
   at Oracle.ManagedDataAccess.Client.OracleCommand.ExecuteNonQuery()
...
The thread 0x4f0 has exited with code 259 (0x103).
The thread 0x3310 has exited with code 259 (0x103).
The program '[10728] TestApplication.vshost.exe' has exited with code 0 (0x0).

更新#2: 根据 Justin 的评论,我将 sql 脚本拆分为两个单独的文件,并从中删除了“/”字符。他们现在是:

simple_A.sql:

CREATE OR REPLACE PACKAGE my_pkg IS
    FUNCTION my_function (
        p_1 IN VARCHAR2
    )RETURN VARCHAR2;
END my_pkg;

simple_B.sql

CREATE OR REPLACE PACKAGE BODY my_pkg IS  -- body
    FUNCTION my_function (
        p_1 IN VARCHAR2
    ) RETURN VARCHAR2 AS p_result VARCHAR2(2000);
        BEGIN
        RETURN p_1;
    END my_function;
END my_pkg;

通过这些更改以及第一次更新中的更改,代码可以正常工作。

在让代码正常工作后,我根据 Justin 的另一条评论添加了一个额外的位,这是一种识别错误原因的方法。具体来说,我添加了一个 printErrors 函数,并在我的 C# 代码中添加了对它的调用。对代码的增补和修改为:

private static void loadMyPackage(OracleConnection conn)
{
    OracleCommand command_A = new OracleCommand();
    command_A.CommandType = CommandType.Text;
    command_A.CommandText = File.ReadAllText(@"D:\_temp\simple_A.sql");
    command_A.Connection = conn;
    int response = command_A.ExecuteNonQuery();
    printErrors(conn);
    Debug.WriteLine("response A = " + response);

    OracleCommand command_B = new OracleCommand();
    command_B.CommandType = CommandType.Text;
    command_B.CommandText = File.ReadAllText(@"D:\_temp\simple_B.sql");
    command_B.Connection = conn;
    response = command_B.ExecuteNonQuery();
    printErrors(conn);
    Debug.WriteLine("response B = " + response); 
}

    private static void printErrors(OracleConnection conn)
    {
        OracleCommand cmd = new OracleCommand();
        cmd.Connection = conn;
        cmd.CommandText = "SELECT name, text FROM user_errors";
        cmd.CommandType = CommandType.Text;
        OracleDataReader dr = cmd.ExecuteReader();
        while (dr.Read())
        {
            Debug.WriteLine("user_error: " + dr.GetString(0) + ": " + dr.GetString(1) );
        }
    }

添加了 printErrors 代码后,错误的原因就很容易看出了。第一次更新的错误 SQL 版本产生以下输出:

...
first call to my_function:
function result = hello
user_error: MY_PKG: PLS-00103: Encountered the symbol "/" The symbol "/" was ignored.

response A = -1
...

[注意:以上错误消息来自给定有错误的 sql 脚本时的最终代码。 sql 脚本的最终版本没有错误。]

因此,此代码不仅开始工作,而且现在能够在失败的情况下提供描述性错误信息。

假设您的文件看起来像这样,包含两个 DDL 语句且没有 SQL*Plus 命令

CREATE OR REPLACE PACKAGE package_name
  ...
END;
/

CREATE OR REPLACE PACKAGE BODY package_name
  ...
END;
/

有两个问题。

首先,这是两个独立的 DDL 语句,因此它们必须通过两个独立的 ExecuteNonQuery 调用来执行。您可以将单个文件拆分为多个文件,每个 DDL 语句一个,或者您可以在 C# 代码中解析文件中的各个语句。

其次,每个 DDL 语句的末尾都有一个 / 字符。那是 SQL*Plus 的分隔符,因此它知道您的语句何时完成,它不是 DDL 语句的一部分。您可以在执行语句之前在 C# 中删除它,也可以从文件中删除语句并创建一个单独的 SQL*Plus 脚本来引用各个语句文件并包含适当的分隔符。