Jaimir G. blog

Experiencias de mi trabajo diario construyendo soluciones sobre plataformas Microsoft

Error “Attempted to read or write protected memory. This is often an indication that other memory is corrupt” con Oracle.

Al ejecutar un procedimiento almacenado de Oracle desde una aplicación que utiliza ADO.NET, se genera el siguiente mensaje de error:


Attempted to read or write protected memory. This is often an indication that other memory is corrupt.

Description: An unhandled exception occurred during the execution of the current web request. Please review the stack trace for more information about the error and where it originated in the code.

Exception Details: System.AccessViolationException: Attempted to read or write protected memory. This is often an indication that other memory is corrupt.

Source Error:

An unhandled exception was generated during the execution of the current web request. Information regarding the origin and location of the exception can be identified using the exception stack trace below. 

Stack Trace:

[AccessViolationException: Attempted to read or write protected memory. This is often an indication that other memory is corrupt.]

   Oracle.DataAccess.Client.OpsSql.ExecuteReader(IntPtr opsConCtx, IntPtr& opsErrCtx, IntPtr& opsSqlCtx, IntPtr& opsDacCtx, IntPtr& opsReaderErrCtx, IntPtr opsSubscrCtx, Int32& isSubscrRegistered, Int32 bchgNTFNExcludeRowidInfo, Int32 bQueryBasedNTFNRegistration, Int64& query_id, OpoSqlValCtx*& pOpoSqlValCtx, String pCommandText, OpoDacValCtx*& pOpoDacValCtx, IntPtr[] pOpoPrmValCtx, String[] ppOpoPrmRefCtx, OpoMetValCtx*& pOpoMetValCtx, Int32 NoOfParams) +0
   Oracle.DataAccess.Client.OracleCommand.ExecuteReader(Boolean requery, Boolean fillRequest, CommandBehavior behavior) +2789
   Oracle.DataAccess.Client.OracleCommand.ExecuteDbDataReader(CommandBehavior behavior) +42
   System.Data.Common.DbCommand.ExecuteReader(CommandBehavior behavior) +10


En internet se encuentran muchas razones validas, conflicto entre las versiones del cliente Oracle y el servidor, algunos problemas de configuración del ODP.NET y el Oracle Cliente (home, tsnames, register, etc..), que no permite seleccionar la versión correcta de las librerías cliente al momento de comunicarse con el servidor. Básicamente cualquier cosa que afecte el búfer de ejecución o la transferencia de datos entre cliente y servidor de Oracle puede generar esta excepción. Sin embargo, todavía existen muchos reportes en los foros donde se corrigen todo los puntos referentes al ambiente y la configuración de la plataforma y aun sigue presentándose el error.

Debo decir que antes de llegar a estas conclusiones y solucionar el problema, pase mucho tiempo probando diferentes soluciones y siguiendo recomendaciones hechas en muchos foros. Cuando nada de esto funciono, empezamos a comparar que diferenciaba este procedimiento en especial a los demás y de allí llegamos al acercamiento que el problema estaba en la declaración de los parámetros. Para esta investigación conté con el apoyo de Beatriz Sanchez, una compañera que domina PL de Oracle.

En mi caso se presentaban las siguientes características:

  1. El procedimiento almacenado tiene un parámetro marcado como "IN OUT" de tipo "DATE".
  2. Dentro del cuerpo del procedimiento almacenado se asigna un nuevo valor al parámetro.
  3. En el comando de ADO.NET, en la colección de "Parameters", este parámetro se adiciono como "DbType.DateTime".

Colocando cometarios para inhabilitar parte del código del procedimiento almacenado, llegamos a la conclusión que el error se generaba cuando se cambiaba el valor o se asignaba un nuevo valor al parámetro, como esta misma acción se realiza con parámetros de tipo "VARCHAR" y "NUMERIC" sin ningún error, nos concentramos en la declaración del tipo de dato del parámetro.

Es importante aclarar que cuando los parámetros en Oracle son definidos utilizando los tipos genéricos ADO.NET ("DbType") el ODP.NET realiza el respectivo mapeo a los tipos "OracleDbType". En el caso del "DbType.DateTime" es mapeado a "OracleDbType.TimeStamp" con el fin de conservar las milésimas de segundo, pues el tipo de dato en Oracle "DATE" solo almacena hasta segundos.

La primera aproximación fue editar la definición del procedimiento en Oracle y cambiar la declaración del parámetro al tipo de dato "TIMESTAMP", sin embargo el error se volvió a generar, lo cual nos lleva a la primera conclusión:

"Los parámetros de un procedimiento almacenado de tipo "TIMESTAMP" declarados como "IN OUT" para poder asignarles un nuevo valor, generan error "System.AccessViolationException" al ejecutarse desde ADO.NET."

Después cambiamos en la declaración del comando de ADO.NET y utilizando "OracleParameter" se asignó el tipo de dato "OracleDbType.Date". En este caso la aplicación corrió sin errores y permitió cambiar el valor del parámetro dentro del procedimiento. Lo cual nos lleva a otras dos conclusiones:

"El mapeo realizado por ODP.NET entre "DbType" y "OracleDbType" no es acertado en todos los escenarios y en varios casos es necesario crear un función propia que realice la equivalencia", esta es nuestra función.

private static OracleDbType EquivalenciasOracle(DbType dbType)
{
    switch (dbType)
    {
       case DbType.AnsiString: return OracleDbType.Varchar2;
       case DbType.AnsiStringFixedLength: return OracleDbType.Char;
       case DbType.Binary: return OracleDbType.Raw;
       case DbType.Boolean: return OracleDbType.Byte;
       case DbType.Byte: return OracleDbType.Byte;
       case DbType.Currency: return OracleDbType.Decimal;
       case DbType.Date: return OracleDbType.Date;
       case DbType.DateTime: return OracleDbType.Date;
       case DbType.Decimal: return OracleDbType.Decimal;
       case DbType.Double: return OracleDbType.Double;
       case DbType.Guid: return OracleDbType.Raw;
       case DbType.Int16: return OracleDbType.Int16;
       case DbType.Int32: return OracleDbType.Int32;
       case DbType.Int64: return OracleDbType.Int64;
       case DbType.Object: return OracleDbType.Blob;
       case DbType.SByte: return OracleDbType.Int16;
       case DbType.Single: return OracleDbType.Single;
       case DbType.String: return OracleDbType.NVarchar2;
       case DbType.StringFixedLength: return OracleDbType.NChar;
       case DbType.Time: return OracleDbType.TimeStamp;
       case DbType.UInt16: return OracleDbType.Int16;
       case DbType.UInt32: return OracleDbType.Int32;
       case DbType.UInt64: return OracleDbType.Int64;
       case DbType.VarNumeric: return OracleDbType.Decimal;
       default: return OracleDbType.Varchar2;
    }
}

Finalmente, "Es muy importante seleccionar el tipo de dato correcto en los parámetros, sobre todo si estos se van a utilizar como espacios de memoria donde se van a intercambiar valores entre Oracle y ADO.NET (IN OUT), pues NO existe un validación real en tiempo de desarrollo y esto puede generar problemas en tiempo de ejecución de asignación de memoria que derivan en errores de tipo "System.AccessViolationException""

Ambiente
Sistema Operativo Windows Vista Ultimate + Sp1
Microsoft .NET Framework 3.5
Microsoft Visual Studio 2008 Versión 9.0.21022.8 RTM
ODP.NET 11.1.0.6.20
Oracle 10g

Comments

Juan Pelaez said:

Muy Interesante, sin embargo este error no es exclusivo de Oracle, usando SQL Server o cualquier base de datos también se puede presentar, esta mas relacionado con cambios en el contenido del DataReader, DataAdapter, DataSet, DataTable en tiempo de ejecución, y curiosamente se viene heredando desde ADO. En mi opinión el problema está en el hecho de que dentro del procedimiento almacenado se cambia el valor (punto 2 de la lista) y de alguna forma (me perdonan los puristas) por el tipo de parámetro este se paso “por referencia pero de solo lectura”.  Como esto se presenta mucho y como el error es de alguna forma es muy genérico se suele asociar a mil cosas, pero como usted bien anota, muchos de los que creen corregirlo en realidad cambiaron la reescritura del parámetro. Ahora bien, usted nos indica que efectivamente solucionó el error y que era un problema del mapeo de los tipos de datos entre Oracle y ADO.Net, esto no contradice para nada lo anterior, simplemente es posible que con esos tipos de datos no se produzca el bloqueo de memoria, pero como comente al principio con otros motores de bases de datos se puede producir errores similares, es entonces más un tema de la implementación de los diferentes proveedores de acceso que del motor usado.

# January 11, 2009 5:00 PM
Leave a Comment

(required) 

(required) 

(optional)

(required)