Team Intellekt
El sitio de interacción con los arquitectos de Intellekt
Enterprise Library 4 con Oracle y ODP NET 11

Que tal un sludo a todos, después de algunos meses de trabajar en Intellekt al fin voy a estrenar mi blog en el team esperando que les sea de utilidad.

Trabajar el acceso a datos de nuestras aplicaciones con EntLib, sin duda que facilita mucho el trabajo de plomería, permitiendo que nos enfoquemos mas en el tratamiento de los datos que en el acceso a ellos mediante la aplicación.

EntLib trabaja de forma excelente con conexiones en SQL Server pero no así con otros gestores. Recientemente en la empresa trabajamos sobre un proyecto que requiere de una conexión a Oracle por lo que decidimos hacer uso de mejores practicas mediante la herramienta EntLib, una vez que comenzamos a trabajar nos encontramos con algunos problemas en las operaciones, sobre todo sobre los tipos de dato, en colaboración con mi compañero Roberto, decidimos utilizar el proveedor de datos odp.net que Oracle distribuye para conectarse con .NET y además hacer los cambios adecuados para que trabajara de forma óptima con EntLib, pues bien basta de charla y aquí va el aporte.

Primero necesitamos ODAC que incluye el proveedor y herramientas de integración con Visual Studio 2008 que son de gran utilidad, así que a descargar, la version que utilizo es la ODTwithODAC1110621. Una vez instalado ubicaremos la carpeta donde se encuentra el Oracle.DataAccess.Client que generalmente se encuentra en C:\app\Usuario\product\11.1.0\client_1\odp.net\bin\2.x\Oracle.DataAccess.Client.dll.

Necesitamos instalar la  Enterprise Library 4, una vez instalada haremos un respaldo de la carpeta bin y de la solución Data (solo por si acaso, jeje). Abrimos la solución de la carpeta Data (..\EntLib4Src\Blocks\Src\Data) y empezaremos los cambios.

1. Agregar el en proyecto Data, la referencia al cliente de oracle, que ya habíamos ubicado.

2. Sustituiremos el using System.Data.OracleClient; por el de odp net que es using Oracle.DataAccess.Client;  y using Oracle.DataAccess.Types; en los siguientes archivos.

..\EntLib4Src\Blocks\Src\Data\Oracle\OracleDatabase.cs 
..\EntLib4Src\Blocks\Src\Data\DatabaseConfigurationView.cs 
..\EntLib4Src\Blocks\Src\Data\Oracle\OracleDataReaderWrapper.cs

3.  Después en el archivo ..\EntLib4Src\Blocks\Src\Data\Configuration\DbProviderMapping.cs en la clase  DbProviderMapping 
buscamos el texto System.Data.OracleClient y lo reemplazamos con Oracle.DataAccess.Client

4. Ahora buscaremos dentro del archivo ..\EntLib4Src\Blocks\Src\Data\Oracle\OracleDatabase.cs, buscamos el metodo AddParameter con la siguiente declaración

public void AddParameter(OracleCommand command, string name, OracleType oracleType, int size,
ParameterDirection direction, bool nullable, byte precision, byte scale, string sourceColumn, DataRowVersion sourceVersion, object value)

y la cambiaremos por la siguiente

public void AddParameter(OracleCommand command, string name, OracleDbType oracleType, int size,
ParameterDirection direction, bool nullable, byte precision, byte scale, string sourceColumn, DataRowVersion sourceVersion, object value), realmente solo cambia el parametro OracleType por OracleDbType.

5. Del archivo ..\EntLib4Src\Blocks\Src\Data\Oracle\OracleDatabase.cs también debemos buscar y remover la siguiente declaración

[OraclePermission(SecurityAction.Demand)]

6. En el mismo archivo ..\EntLib4Src\Blocks\Src\Data\Oracle\OracleDatabase.cs debemos buscar

param.OracleType

y reemplazar por

param.OracleDbType

también debemos buscar

OracleType.Cursor

y reeemplazar

OracleDbType.RefCursor

por último buscar

parameter.OracleType

y reemplazar por

parameter.OracleDbTypes

7. Finalmente compilamos la aplicación, VS nos mandara un error de un XML comment, debido a los cambios, procedemos a cambiar los comentarios en la parte de parámetros de las funciones que fueron modificadas.

Hasta aquí solo hemos configurado a EntLib para  utilizar el ODP.NET, pero aún tenemos que agregar la función que nos permita obtener datos de manera transparente para Oracle.

La función es nombrada AddGenericParameter, pues la intención es extender su uso para cualquier manejador de base de datos.

Bien, en el archivo ..\EntLib4Src\Blocks\Src\Data\Database.cs debemos agregar la función:

/// <summary>
/// Metodo genérico para agregar parámetros en un command para oracle con tipo de dato nativo
/// </summary>
/// <param name="command">Comando que será ejecutado</param>
/// <param name="parameterName">Nombre del parámetro</param>
/// <param name="parameterType">Cadena que contiene el nombre del tipo de dato nativo en oracle</param>
/// <param name="value">Objeto con el valor del parametro</param>
/// <param name="direction">Dirección del parémtro</param>

public virtual void AddGenericParameter(DbCommand command, string parameterName, string parameterType, object value, ParameterDirection direction)
{
}

Bien podemos implementarla o no, en este caso no veo la necesidad de implementarla aquí.

Sin embargo en el archivo ..\EntLib4Src\Blocks\Src\Data\Oracle\OracleDatabase.cs si es necesario implementarla, y lo haremos de la siguiente forma

/// <summary>
/// Metodo genérico para agregar parámetros en un command para oracle con tipo de dato nativo
/// </summary>
/// <param name="command">Comando que será ejecutado</param>
/// <param name="parameterName">Nombre del parámetro</param>
/// <param name="parameterType">Cadena que contiene el nombre del tipo de dato nativo en oracle</param>
/// <param name="value">Objeto con el valor del parametro</param>
/// <param name="direction">Dirección del parémtro</param>
public override void AddGenericParameter(DbCommand command, string parameterName, string parameterType, object value, ParameterDirection direction)
{
     OracleDbType typeData = OracleDbType.Object;
     Array typeEnum = Enum.GetValues(typeof(OracleDbType));
     string nameType = string.Empty;
     foreach (object item in typeEnum)
     {
         nameType = ((OracleDbType)item).ToString();
         if (nameType.Equals(parameterType))
         {
              typeData = (OracleDbType)item;
              AddParameter(command as OracleCommand, parameterName, typeData, 0, direction, false, 0, 0, string.Empty, DataRowVersion.Default, value);
              break;
          }
     }      
}

Lo que hace esta función es buscar dentro de la colección de tipos de dato que corresponda, en este caso OracleDbType, el tipo de dato que coincida con el nombre del tipo que le fue pasado como parámetro, es decir, busca en su coleccíón los tipos que coincidan con parameterType, una vez que el tipo de dato es encontrado, se instancia, y se agrega un parametro al comando del tipo OracleCommand y listo, hemos agregado un parámetro con un tipo de dato nativo para Oracle.

Ahora podemos compilar nuestra solución data y ejecutar el script BuildLibraryAndCopyAssemblies.bat que se encuentra en ..\EntLib4Src\Scripts, y tienen lista la EntLib 4 para utilizarla con Oracle, a continuación pondré un ejemplo completo de como crear los procedimientos almacenados y como hacer la llamada a EntLib desde un poyecto de Acceso a Datos.

Como estamos acostumbrados a que un SP en SQL Server para una sentencia select nos regrese un conjunto de datos, es necesario que para Oracle le demos la instrucción adecuada para obtener lo mismo, que en este caso seria algo así:

CREATE PROCEDURE "SP_SELECTCOMPANIESBYID" (companyId in number,
  companies OUT SYS_REFCURSOR) IS

BEGIN
  open companies for
    SELECT COMPANY_ID, CM_DESCRIPTION, CM_COUNTRY_IDENTIFICATION, CM_ENABLED
    FROM DRILLING.TBL_COMPANIES
    WHERE COMPANY_ID = companyId;
END;

 Este procedimiento nos devuelve el registro que cumple con el criterio indicado.

Otro procedimiento de consulta común es el que obtiene varios registros como lo siguiente:

CREATE PROCEDURE "SP_SELECTCOMPANIES" (description in varchar2, companies OUT SYS_REFCURSOR)
IS
BEGIN
    IF description IS NOT NULL THEN
        OPEN companies FOR
        SELECT COMPANY_ID, CM_DESCRIPTION, CM_COUNTRY_IDENTIFICATION, CM_ENABLED
        FROM TBL_COMPANIES
        WHERE TBL_COMPANIES.CM_DESCRIPTION LIKE '%' || description || '%';
    ELSE
        OPEN companies FOR
        SELECT COMPANY_ID, CM_DESCRIPTION, CM_COUNTRY_IDENTIFICATION, CM_ENABLED
        FROM TBL_COMPANIES;
    END IF;       
END;

La diferencia mas evidente es el parámetro de salida que es un parámetro de tipo REFCURSOR en Oracle, a través del cual nos regresa el conjunto de datos que se obtienen a través de una sentencia SELECT.

Para el caso de una inserción tendríamos el siguiente ejemplo

CREATE PROCEDURE sp_InsertCompanies(description in varchar2, country in varchar2, enabled in varchar2, rowcount out number)
AS
count_companies number;
id number;
BEGIN
    ROWCOUNT := 0; 
    SELECT COUNT(TBL_COMPANIES.CM_DESCRIPTION) INTO count_companies FROM TBL_COMPANIES WHERE TBL_COMPANIES.CM_DESCRIPTION = description;
    IF  count_companies > 0 THEN
        RAISE_APPLICATION_ERROR(-20001,'La compañía ya existe');
    ELSE       
        INSERT INTO TBL_COMPANIES (COMPANY_ID, CM_DESCRIPTION, CM_COUNTRY_IDENTIFICATION, CM_ENABLED)
        VALUES ( SQ_COMPANIES.nextval, description, country, enabled );         
        ROWCOUNT := SQL%ROWCOUNT;
        COMMIT;       
    END IF;   
END;

Esta vez solo le pedimos que nos devuelva el número de filas afectadas. Este punto es importante ya que para simular el autoincremento como en SQL Server, debemos crear una "variable global" (debe tener un nombre este objeto en Oracle) del tipo Sequence, en este caso para el campo Company_ID, una variable sequence se crea de la siguiente forma:

CREATE SEQUENCE nombre_secuencia
INCREMENT BY numero_incremento
START WITH numero_por_el_que_empezara
MAXVALUE valor_maximo | NOMAXVALUE
MINVALUE valor_minimo | NOMINVALUE
CYCLE | NOCYCLE
ORDER | NOORDER

Así que podríamos modificar nuestro procedimiento para que nos devuelva el ultimo valor insertado para una determinada secuencia, por ejemplo:

CREATE PROCEDURE sp_InsertCompanies(description in varchar2, country in varchar2, enabled in varchar2, identity out number)
AS
count_companies number;
id number;
BEGIN 
        SELECT COUNT(TBL_COMPANIES.CM_DESCRIPTION) INTO count_companies FROM TBL_COMPANIES WHERE TBL_COMPANIES.CM_DESCRIPTION = description;
    IF  count_companies > 0 THEN
        RAISE_APPLICATION_ERROR(-20001,'La compañía ya existe');
    ELSE       
        INSERT INTO TBL_COMPANIES (COMPANY_ID, CM_DESCRIPTION, CM_COUNTRY_IDENTIFICATION, CM_ENABLED)
        VALUES ( SQ_COMPANIES.nextval, description, country, enabled );          
        identity := SQ_COMPANIES.currval

        COMMIT;       
    END IF;   
END;

Bien para el caso de un Update y Delete se sigue la misma lógica.

Finalmente les muestro una llamada a la EntLib para utilizar la nueva característica.

En este caso se llena una entidad del tipo Company con los datos necesarios.

public static SOP_CompaniesBI SelectCompanyByID(int companyId)
       {
           SOP_CompaniesBI entity = null;

           try
           {
               dbAccesoDatos = DatabaseFactory.CreateDatabase();
               DbCommand dbComando = dbAccesoDatos.GetStoredProcCommand(spSelectById);
               dbAccesoDatos.AddGenericParameter(dbComando, "companyId", OracleDbType.Int32.ToString(), companyId, ParameterDirection.Input);
               dbAccesoDatos.AddGenericParameter(dbComando, "companies", OracleDbType.RefCursor.ToString(), DBNull.Value, ParameterDirection.Output);

               using (IDataReader dr = dbAccesoDatos.ExecuteReader(dbComando))
               {
                   while (dr.Read())
                   {
                       entity = new SOP_CompaniesBI();
                       entity.companyId = Convert.ToInt32(dr["COMPANY_ID"]);
                       entity.cmDescription = dr["CM_DESCRIPTION"].ToString();
                       entity.cmCountryIdentification = dr["CM_COUNTRY_IDENTIFICATION"].ToString();
                       entity.cmEnabled = dr["CM_ENABLED"].ToString();
                   }
               }
               return entity;
           }
           catch (Exception ex)
           {
               throw new Exception(ex.Message);
           }
       }

Y para insertar una nueva compañía:

public static byte InsertCompanies(SOP_CompaniesBI entity, ref DatabaseErrorCode errorCode, ref string errorMessage)
        {
            byte result = 0;
            errorCode = DatabaseErrorCode.Ninguno;
            errorMessage = string.Empty;

            try
            {
                dbAccesoDatos = DatabaseFactory.CreateDatabase();

                DbCommand cmd = dbAccesoDatos.GetStoredProcCommand(spInsert);
                dbAccesoDatos.AddGenericParameter(cmd, "description", OracleDbType.Varchar2.ToString(), entity.cmDescription, ParameterDirection.Input);
                dbAccesoDatos.AddGenericParameter(cmd, "country", OracleDbType.Varchar2.ToString(), entity.cmCountryIdentification, ParameterDirection.Input);
                dbAccesoDatos.AddGenericParameter(cmd, "enabled", OracleDbType.Varchar2.ToString(), entity.cmEnabled, ParameterDirection.Input);
                dbAccesoDatos.AddGenericParameter(cmd, "identity", OracleDbType.Int32.ToString(), DBNull.Value, ParameterDirection.Output);
                dbAccesoDatos.ExecuteNonQuery(cmd);
                result = Convert.ToByte(cmd.Parameters["identity"].Value.ToString());
            }

            catch (Oracle.DataAccess.Client.OracleException oraEx)
            {
                switch (oraEx.Number)
                {
                    case 20001:
                        errorCode = DatabaseErrorCode.ObjetoExistenteEnBD;
                        errorMessage = "La compañía ya se encuentra registrada.";
                        break;
                    default:
                        throw;
                }
            }
            catch (Exception ex)
            {
                throw new Exception("Ocurrió una excepción al agregar la compañía.", ex.InnerException);
            }
            return result;
        } 

Y con eso es suficiente para trabajar con datos desde Oracle con Enterprise Library. Seguramente cuando trabajen con estas características surgiran algunos problemas, por eso les recomiendo que se lean esta entrada donde se explican algunas cosas acerca de trabajar con versiones personalizadas de enterprise library.

Espero que les sea de utilidad.


Posted 12-01-2008 2:50 PM by david

Comments

Emmanuel Valle wrote re: Enterprise Library 4 con Oracle y ODP NET 11
on 12-02-2008 7:25 PM

Muy buen aporte... muy funcional...

y además tiene potencial para extenderlo a cualquier otro gestor de DB's...

Insisto en k lo envies a los desarrolladores de la Enterprise Library pa k lo agreguen a la distribucion oficial...

Como dices: "Eso es POO y no m...das"...

Felicidades...

Josue wrote re: Enterprise Library 4 con Oracle y ODP NET 11
on 12-27-2008 8:49 AM

Exelente te ganastes un 10...!!! casi me despiden por no seber como hacerlo mil Gracias......

GRACIAS!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!

Jorge M wrote re: Enterprise Library 4 con Oracle y ODP NET 11
on 02-16-2009 8:48 AM

Muy buena explicación y estupenda la capacidad que tienes.

Tengo una pregunta:

¿Puedo usar otro tipo de variable en lugar de SYS_REFCURSOR?

Fran wrote re: Enterprise Library 4 con Oracle y ODP NET 11
on 06-09-2009 4:16 AM

Buen post.

Una vergüenza que EnterpriseLibrary no soporte directamente Oracle.

¿ Por qué le llaman "Enterprise" ???

Saludos.

david wrote re: Enterprise Library 4 con Oracle y ODP NET 11
on 06-15-2009 11:37 AM

Si lo soporta por default, lo que hicimos fue cambiar el proveedor que trae al que ofrece Oracle, ademas de hacerlo transparente para los usuarios SQL, de otro modo hay que declarar un sys_refcursor en el sp de Oracle

Luis Pinedo - Peru wrote re: Enterprise Library 4 con Oracle y ODP NET 11
on 10-05-2009 8:52 AM

Amigo David, yo estoy utlizando Visual Studio 2005 y Enterprise la version 3.1, y al hacer todos los cambios como indicas , me salen los siguientes errores:

-El nombre 'OracleClientFactory' no existe en el contexto actual D:\EntLib3Src\App  Src\Data\Oracle\OracleDatabase.cs

-No se puede convertir el 'System.Data.Common.DbCommand' en 'Oracle.DataAccess.Client.OracleCommand' D:\EntLib3Src\App Blocks\Src\Data\Oracle\OracleDatabase.cs

Tiene algo que ver estos problemas conl a version que tu indicas en el post Enterprise Library 4 con Oracle y ODP NET 11 ?

David desde ya agradesco tu valiosa respuesta, porque se que lo haces dejando de hacer tus quehaceres laborale.Gracias

ae wrote re: Enterprise Library 4 con Oracle y ODP NET 11
on 10-20-2009 7:17 AM

qué pasa con la Entt. Library 4.1 , si soporta odp.net ???

saludos

ae wrote re: Enterprise Library 4 con Oracle y ODP NET 11
on 10-20-2009 7:32 AM

Y con la 4.1 ??

david wrote re: Enterprise Library 4 con Oracle y ODP NET 11
on 11-09-2009 3:54 PM

La ent lib 4.1 no soporta por default odp net, soporta el acceso a datos con oracle pero a traves del cliente de .Net lo cual ya lo hace en las anteriores versiones de ent lib, por lo que siguiendo correctamente las instrucciones del post podras obtener el resultado que buscas

Darío Griffo wrote re: Enterprise Library 4 con Oracle y ODP NET 11
on 02-04-2010 11:55 AM

Nosotros nos encontramos con este problema y nos atraía la idea de extender EnterpriseLibrary pero encontramos un workaround un poco mas simple.

Database db = DatabaseFactory.CreateDatabase();

DbCommand cmd =  db.GetStoredProcCommand("ProcName");

System.Data.OracleClient.OracleParameter p = new System.Data.OracleClient.OracleParameter("p_cursor_out", OracleType.Cursor);

p.Direction = ParameterDirection.Output;

cmd.Parameters.Add(p);

using (IDataReader reader = db.ExecuteReader(cmd))

{

   //Mapear datos al obj o lo que quieras hacer con la data

}

Add a Comment

(required)  
(optional)
(required)  
Remember Me?

Protected by FormShield
Refresh
Listen
Please enter the characters shown on the image


Code:


Copyright (c) Intellekt All Rights Reserved
Powered by Community Server (Non-Commercial Edition), by Telligent Systems