C#调用替换后Oracle包无效

Posted

技术标签:

【中文标题】C#调用替换后Oracle包无效【英文标题】:Oracle package invalid after C# call to replace 【发布时间】:2016-10-10 20:13:16 【问题描述】:

我假设我在从 C# 运行 SQL 脚本的方式上做错了,但是在互联网上大量搜索后,我仍然不知道是什么问题......

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

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 的 cmets,我将我的 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 的另一个 cmets 的基础上添加了一个额外的位,这是一种识别错误原因的方法。具体来说,我添加了一个 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 脚本的最终版本没有错误。]

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

【问题讨论】:

文件里有什么?有没有SQL*Plus 命令?假设该文件包含两条 DDL 语句,一条用于创建包规范,一条用于创建包主体,则每个 DDL 语句都需要单独执行。您可以创建两个文件,也可以尝试解析应用程序中的单个文件以分隔两个语句。这可能就像在一行上单独寻找一个 / 字符来中断一样简单。 嗯,对第一个 loadmypackage 的 -1 响应说明了这一点。 D:_temp\simple.sql 文件中可能有一些错误的字符或命令。所以你想用另一个替换一个存储过程?它第一次工作,所以你的负载正在破坏存储过程。 @JustinCave 问题中的第一段代码是 SQL 脚本的内容。我已经更新了这个问题,以便在这一点上更清楚。由于它确实有多个 DDL 语句,我将尝试将其拆分。 @Marc '-1' 响应只是告诉我已处理的语句类型。在这种情况下,“-1”是“其他”。 link 您的 C# 代码不应在您的 DDL 语句中发送 / 字符。可以通过查询user_errors查看错误,在user_source可以看到包和包体的全文。我猜user_errors 中的错误是针对末尾的无效字符(/),并且包和包正文的文本在通过您的应用程序发送时末尾有一个额外的/ 【参考方案1】:

假设您的文件看起来像这样,带有两个 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 脚本,该脚本引用各个语句文件并包含适当的分隔符。

【讨论】:

以上是关于C#调用替换后Oracle包无效的主要内容,如果未能解决你的问题,请参考以下文章

oracle 自定义函数,调用时报“程序包或函数无效”

Oracle 包规范何时变为无效

当阅读器关闭时,C# 尝试调用 Read 无效

python调用oracle函数ORA-00904无效标识符

从 C# 调用 C++ dll。 “无法封送'返回值':托管/非托管类型组合无效。”

c#的webbrowser调用本地javascript脚本?