从 C# 使用大型 CLOB 调用存储过程时出现问题

发布于 2024-09-15 23:27:58 字数 2907 浏览 5 评论 0原文

我不是第一个遇到这些问题的人,并将在下面列出一些参考帖子,但我仍在寻找适当的解决方案。

我需要从 C# Web 服务调用存储过程(Oracle 10g 数据库)。 Web 服务器安装了 Oracle 9i 客户端,我使用的是 Microsoft 的 System.Data.OracleClient。

该过程将 XML 作为 CLOB。当 XML 超过 4000 字节(这可能是在正常用例中)时,我偶然发现了以下错误:

ORA-01460 - 请求的转换未实现或不合理

我发现此 和 这篇文章。

此外,我发现了一种很有前途的解决方法,它不直接从 C# 调用存储过程,而是定义一段匿名 PL/SQL 代码。此代码作为 OracleCommand 运行。 XML 作为字符串文字嵌入,过程调用是从该代码段内完成的:

private const string LoadXml =
    "DECLARE " +
    "  MyXML CLOB; " +
    "  iStatus INTEGER; " +
    "  sErrMessage VARCHAR2(2000); " +
    "BEGIN " +
    "  MyXML := '{0}'; " +
    "  iStatus := LoadXML(MyXML, sErrMessage); " +
    "  DBMS_OUTPUT.ENABLE(buffer_size => NULL); " +
    "  DBMS_OUTPUT.PUT_LINE(iStatus || ',' || sErrMessage); " +
    "END;";
OracleCommand oraCommand = new OracleCommand(
    string.Format(LoadXml, xml), oraConnection);
oraCommand.ExecuteNonQuery();

不幸的是,一旦 XML 超过 32 KB这种方法就会失败左右,这在我的申请中仍然很有可能。这次错误源于 PL/SQL 编译器,它表示:

ORA-06550: 第 1 行,第 87 列:PLS-00172: 字符串文字太长

经过一番研究,我得出结论,用第二种方法解决问题根本不可行。

根据上述帖子,我有以下两个选择。

(第一篇文章说某些客户端有错误,但我的(9i )不属于上述的 10g/11g 版本范围。)

您能否确认这是仅剩的两个选项?或者还有其他方法可以帮助我吗?

只是澄清一下:XML不会最终保存在任何表中,但它是由存储过程处理的,该存储过程会在其中插入一些记录一些基于 XML 内容的表。

我对这两个选项的考虑:

  • 切换到 ODP.NET 很困难,因为我必须将其安装在到目前为止我没有系统访问权限的 Web 服务器上,并且因为我们可能还想在客户端上部署该代码段,因此每个客户端都必须安装 ODP.NET 作为部署的一部分。
  • 绕过表使客户端代码变得相当复杂,并且在数据库调整/扩展 PL/SQL 例程方面也花费了相当多的精力。

I'm not the first to have these issues, and will list some reference posts below, but am still looking for a proper solution.

I need to call a stored procedure (Oracle 10g database) from a C# web service. The web server has an Oracle 9i client installed and I am using Microsofts System.Data.OracleClient.

The procedure takes an XML as a CLOB. When the XML was over 4000 Bytes (which is likely in a normal use case), I stumbled over the following error:

ORA-01460 - unimplemented or unreasonable conversion requested

I've found this, this and this post.

Further I found a promising workaround which doesn't call the stored procedure directly from C# but defines a piece of anonymous PL/SQL code instead. This code is run as an OracleCommand. The XML is embedded as a string literal and the procedure call is done from within that piece of code:

private const string LoadXml =
    "DECLARE " +
    "  MyXML CLOB; " +
    "  iStatus INTEGER; " +
    "  sErrMessage VARCHAR2(2000); " +
    "BEGIN " +
    "  MyXML := '{0}'; " +
    "  iStatus := LoadXML(MyXML, sErrMessage); " +
    "  DBMS_OUTPUT.ENABLE(buffer_size => NULL); " +
    "  DBMS_OUTPUT.PUT_LINE(iStatus || ',' || sErrMessage); " +
    "END;";
OracleCommand oraCommand = new OracleCommand(
    string.Format(LoadXml, xml), oraConnection);
oraCommand.ExecuteNonQuery();

Unfortunately, this approach now fails as soon as the XML is over 32 KBytes or so, which still is very likely in my application. This time the error stems from the PL/SQL compiler which says:

ORA-06550: line1, column 87: PLS-00172: string literal too long

After some research I conclude that it's simply not feasible to solve the problem with my second approach.

Following the above-mentioned posts I have the following two options.

(The first post said some clients are buggy, but mine (9i) does not fall in the mentioned range of 10g/11g versions.)

Can you confirm that these are the only two options left? Or is there another way to help me out?

Just to clarify: the XML won't eventually be saved in any table, but it is processed by the stored procedure which inserts some records in some table based on the XML contents.

My considerations about the two options:

  • Switching to ODP.NET is difficult because I have to install it on a web server on which I don't have system access so far, and because we might also want to deploy the piece of code on clients, so each client would have to install ODP.NET as part of the deployment.
  • The detour over a table makes the client code quite a bit more complicated and also takes quite some effort on the database adapting/extending the PL/SQL routines.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(4

小霸王臭丫头 2024-09-22 23:27:58

我发现还有另一种方法可以解决该问题!我的同事帮我指点

设置参数值时
开始交易已经
调用 DbConnection。

能更简单一点吗?该博客与 Oracle.DataAccess 相关,但它也适用于 System.Data.OracleClient

实际上这意味着:

varcmd = new OracleCommand("LoadXML", _oracleConnection);
cmd.CommandType = CommandType.StoredProcedure;

var xmlParam = new OracleParameter("XMLFile", OracleType.Clob);
cmd.Parameters.Add(xmlParam);

// DO NOT assign the parameter value yet in this place

cmd.Transaction = _oracleConnection.BeginTransaction();
try
{
    // Assign value here, AFTER starting the TX
    xmlParam.Value = xmlWithWayMoreThan4000Characters;

    cmd.ExecuteNonQuery();
    cmd.Transaction.Commit();
}
catch (OracleException)
{
    cmd.Transaction.Rollback();
}

I found that there is another way to work around the problem! My fellow employee saved my day pointing me to this blog, which says:

Set the parameter value when
BeginTransaction has already been
called on the DbConnection.

Could it be simpler? The blog relates to Oracle.DataAccess, but it works just as well for System.Data.OracleClient.

In practice this means:

varcmd = new OracleCommand("LoadXML", _oracleConnection);
cmd.CommandType = CommandType.StoredProcedure;

var xmlParam = new OracleParameter("XMLFile", OracleType.Clob);
cmd.Parameters.Add(xmlParam);

// DO NOT assign the parameter value yet in this place

cmd.Transaction = _oracleConnection.BeginTransaction();
try
{
    // Assign value here, AFTER starting the TX
    xmlParam.Value = xmlWithWayMoreThan4000Characters;

    cmd.ExecuteNonQuery();
    cmd.Transaction.Commit();
}
catch (OracleException)
{
    cmd.Transaction.Rollback();
}
§普罗旺斯的薰衣草 2024-09-22 23:27:58

就我而言,chiccodoro 的解决方案不起作用。我正在使用 ODP.NET (Oracle.DataAccess)。

对我来说,解决方案是使用 OracleClob 对象。

OracleCommand cmd = new OracleCommand("LoadXML", _oracleConnection);
cmd.CommandType = CommandType.StoredProcedure;

OracleParameter xmlParam = new OracleParameter("XMLFile", OracleType.Clob);
cmd.Parameters.Add(xmlParam);

//connection should be open!
OracleClob clob = new OracleClob(_oracleConnection);
// xmlData: a string with way more than 4000 chars
clob.Write(xmlData.ToArray(),0,xmlData.Length);
xmlParam.Value = clob; 

try
{
    cmd.ExecuteNonQuery();
}
catch (OracleException e)
{
}

In my case, chiccodoro's solution did not work. I'm using ODP.NET ( Oracle.DataAccess ).

For me the solution is using OracleClob object.

OracleCommand cmd = new OracleCommand("LoadXML", _oracleConnection);
cmd.CommandType = CommandType.StoredProcedure;

OracleParameter xmlParam = new OracleParameter("XMLFile", OracleType.Clob);
cmd.Parameters.Add(xmlParam);

//connection should be open!
OracleClob clob = new OracleClob(_oracleConnection);
// xmlData: a string with way more than 4000 chars
clob.Write(xmlData.ToArray(),0,xmlData.Length);
xmlParam.Value = clob; 

try
{
    cmd.ExecuteNonQuery();
}
catch (OracleException e)
{
}
变身佩奇 2024-09-22 23:27:58

我想我只是用谷歌搜索这个来让你获得便宜的积分,但是这里有一个很好的解释:

http://www.orafaq.com/forum/t/48485/0/

基本上你不能在字符串文字中使用超过4000个字符,如果你需要做更多,你必须使用存储过程。然后,您的最大大小限制为 32KB,因此您必须对插入内容进行“分块”。布莱赫。

-奥辛

I guess I just googled this for you to get cheap points, but there's a great explanation here:

http://www.orafaq.com/forum/t/48485/0/

Basically you cannot use more than 4000 chars in a string literal, and if you need to do more, you must use a stored procedure. Then, you are limited to 32KB at max so you have to "chunk" the inserts. Blech.

-Oisin

不再让梦枯萎 2024-09-22 23:27:58

奇科多罗是对的。

public static int RunProcedure(string storedProcName, IDataParameter[] parameters)
    {
        using (OracleConnection connection = new OracleConnection(connectionString))
        {
            int rowsAffected;

            OracleCommand command = new OracleCommand(storedProcName, connection);
            command.CommandText = storedProcName;
            command.CommandType = CommandType.StoredProcedure;
            foreach (OracleParameter parameter in parameters)
            {
                command.Parameters.Add(parameter);
            }
            connection.Open();

            try
            {
                // start transaction
                command.Transaction = connection.BeginTransaction();
                rowsAffected = command.ExecuteNonQuery();
                command.Transaction.Commit();
            }
            catch (System.Exception ex)
            {
                command.Transaction.Rollback();
                throw ex;
            }

            connection.Close();
            return rowsAffected;
        }
    }

chiccodoro is right.

public static int RunProcedure(string storedProcName, IDataParameter[] parameters)
    {
        using (OracleConnection connection = new OracleConnection(connectionString))
        {
            int rowsAffected;

            OracleCommand command = new OracleCommand(storedProcName, connection);
            command.CommandText = storedProcName;
            command.CommandType = CommandType.StoredProcedure;
            foreach (OracleParameter parameter in parameters)
            {
                command.Parameters.Add(parameter);
            }
            connection.Open();

            try
            {
                // start transaction
                command.Transaction = connection.BeginTransaction();
                rowsAffected = command.ExecuteNonQuery();
                command.Transaction.Commit();
            }
            catch (System.Exception ex)
            {
                command.Transaction.Rollback();
                throw ex;
            }

            connection.Close();
            return rowsAffected;
        }
    }
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文