2010-08-24 87 views
17

No soy el primero en tener estos problemas, y voy a enumerar algunas publicaciones de referencia a continuación, pero sigo buscando una solución adecuada.Problemas al llamar al procedimiento almacenado desde C# con CLOB grande

Necesito llamar a un procedimiento almacenado (base de datos Oracle 10g) desde un servicio web C#. El servidor web tiene un cliente Oracle 9i instalado y estoy usando Microsofts System.Data.OracleClient.

El procedimiento toma un XML como un CLOB. Cuando el XML era más de 4000 Bytes (que es probable que en un caso de uso normal), tropecé con el siguiente error:

ORA-01460 - unimplemented or unreasonable conversion requested

que he encontrado this, this y this puesto.

Además, encontré una solución prometedora que no llama al procedimiento almacenado directamente desde C#, sino que define un fragmento de código PL/SQL anónimo. Este código se ejecuta como un OracleCommand. El XML está incrustado como una cadena literal y el llamado procedimiento se realiza desde dentro de ese trozo de código:

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(); 

Desafortunadamente, este enfoque ahora falla tan pronto como el XML es más de 32 KBytes más o menos, que todavía es muy probable en mi aplicación. Esta vez se deriva el error del compilador de PL/SQL que dice:

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

Después de algunas investigaciones que concluyen que no es simplemente viable para resolver el problema con mi segundo enfoque.

Siguiendo las publicaciones mencionadas, tengo las siguientes dos opciones.

(The first post dijo que algunos clientes son errores, pero el mío (9i) no entra en el rango mencionado de las versiones 10g/11g.)

Puede confirmar que estos son los solo quedan dos opciones? ¿O hay otra forma de ayudarme?

Solo para aclarar: el XML no se finalmente se guardarán en cualquier mesa, pero es procesado por el procedimiento almacenado que inserta algunos registros de alguna tabla basada en el contenido XML.

Mis consideraciones acerca de las dos opciones:

  • El cambio a ODP.NET es difícil porque tengo que instalarlo en un servidor web en la que no tengo acceso al sistema hasta ahora, y porque podríamos También quiero implementar la pieza de código en los clientes, por lo que cada cliente debería instalar ODP.NET como parte de la implementación.
  • El desvío sobre una tabla hace que el código del cliente sea un poco más complicado y también requiere bastante esfuerzo en la base de datos para adaptar/extender las rutinas PL/SQL.
+0

BLOB or CLOB, typo. – Benny

+0

@Benny: El problema existe para ans LOB, de ahí las publicaciones BLOB a las que se hace referencia, pero tuve problemas con CLOB. No puedo encontrar ningún error tipográfico en mi publicación ... – chiccodoro

+0

'MyXML: = '{0}';' the 'string.Format()' está reemplazando ''{0}'' con el xml - sin embargo, '' ... '' es un literal de cadena que es del tipo de datos 'VARCHAR2' (no un' CLOB'). [PL/SQL tiene un límite de 32k] (http://docs.oracle.com/cd/B28359_01/appdev.111/b28370/datatypes.htm#i43252) en 'VARCHAR2's y por eso falla. – MT0

Respuesta

12

he encontrado que hay es otra manera de solucionar el problema! Mi compañero de trabajo me salvó día señalarme a this blog, que dice:

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

Podría ser más simple? El blog se relaciona con Oracle.DataAccess, pero funciona igual de bien para System.Data.OracleClient.

En la práctica esto significa:

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(); 
} 
+0

¿Este enfoque también funciona alrededor de la limitación de 32k? ¿O solo el límite de 4k? – mikemanne

+0

Me temo que no puedo volver a decírtelo de mi mente. Probablemente podría hacerlo si me preguntas hace 1,5 años ... Creo que tengo que tener en cuenta que también funciona alrededor del límite de 32 k. ¿Me puede avisar una vez que lo haya verificado/falsificado? – chiccodoro

+0

definitivamente publicaremos, si tenemos la oportunidad de confirmar/falsificar. Tengo que querer resucitar viejas preguntas, ¿eh? :) – mikemanne

0

Creo que sólo en Google esto para que usted pueda obtener puntos fáciles, pero hay una gran explicación aquí:

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

Básicamente no se puede utilizar más de 4000 caracteres en una cadena literal, y si necesita hacer más, debe usar un procedimiento almacenado. Entonces, está limitado a 32 KB al máximo, por lo que debe "dividir" las inserciones. Blech.

-Oisin

+0

Hola xOn, repitiste más o menos lo que ya escribí en mi pregunta, ¿verdad? - ¿O eso significa que de hecho confirmarías que no tengo otras opciones? – chiccodoro

+0

@chiccodoro - sí, estoy confirmando que no tienes suerte, afaict. – x0n

+0

@ xOn- ¡Tengo algunas buenas noticias! Ver mi nueva respuesta arriba – chiccodoro

1

chiccodoro es correcto.

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; 
     } 
    } 
+1

gracias por confirmar :-). Has publicado bastante código. ¿Tal vez podría ampliar un poco sobre cuál es la diferencia entre su código y mi código o sobre lo que pretendía decir al publicarlo? – chiccodoro

2

En mi caso, la solución de chiccodoro no funcionó. Estoy usando ODP.NET (Oracle.DataAccess).

Para mí la solución es usar el objeto 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) 
{ 
} 
+0

Hola Kemalettin, gracias por compartir.La pregunta original era sobre 'System.Data.OracleClient', pero dado que existe el mismo problema para' Oracle.DataAccess' esto ciertamente será interesante para algunos lectores. – chiccodoro

+0

Incluso esta solución no fue suficiente en mi caso. Tuve que escribir los datos en el clob por trozos de 1 ko, usando un ciclo y el método 'clob.Append' –

Cuestiones relacionadas