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