Mes: mayo 2009

Abstracción de datos (IV): El patrón Abstract Factory (Factoría Abstracta)


Anteriormente vimos lo que era una interfaz y cómo implementarla en C#. Hoy aprovecharemos estos conocimientos para explicar otro patrón de diseño: la Factoría Abstracta o Abstract Factory.

El patrón Abstract Factory es un patrón “familiar”. ¿Por qué familiar? Porque está especializado en proporcionar instancias de familias de objetos. Imaginemos la siguiente situación:

Abstract Factory

(más…)

Tratamiento de cadenas en SQL Server


En cualquier lenguaje de programación es tarea obligada poseer una manipulación de cadenas fluida.

Transact SQL proporciona una variedad bastante amplia de funciones para llevar a cabo tal tarea. Algunas de las operaciones que podemos realizar son concatenación, obtención de subcadenas, pasar a mayúsculas o minúsculas, eliminar espacios en blanco… incluso detectar la similitud entre expresiones.

A continuación veremos algunas de ellos y aplicaremos algunos ejemplos:

(más…)

Introducción a Reflection (III): Ejecutando métodos de un objeto a partir de su tipo


Ayer veíamos cómo obtener información de un objeto mediante el espacio de nombres Reflection. Ahora veremos que no solo es posible obtener información de los metadatos, sino que también es posible acceder a sus miembros.

Vimos previamente que nuestra clase  ClasePrueba tenía una función llamada sumarUno(). Vamos a intentar, mediante Reflection, invocar a la función sin necesidad de “conocer” el objeto. En realidad, y para simplificar el ejemplo, utilizaremos el nombre del método directamente, pero sería posible, a partir de una estructura MethodInfo, obtener nombre y parámetros de cada uno de los métodos y ejecutarlos de forma dinámica.

Partiendo del código del artículo anterior, añadiríamos lo siguiente para invocar el método:

 // Ejecutamos un método mediante Reflection. En primer lugar obtenemos una referencia al método
 MethodInfo Metodo = tipoObjeto.GetMethod("sumarUno");

 // A continuación, llamamos al método Invoke del tipo, pasándole como primer parámetro
 //el objeto cuyo método queremos ejecutar, y como segundo parámetro, un array de objetos con
 //los parámetros que espera el método o función.
 int dato;
 object[] parametros = new object[1] { 2 };
 dato = (int)Metodo.Invoke(objeto, parametros);

 Console.WriteLine("El resultado de la invocación ha sido: " + dato.ToString());

Recordemos que el objeto ‘objeto‘ era una instancia de ClasePrueba.

Como puntilla mostraremos otra forma de invocar un método mediante Reflection: utilizando Activator para obtener una instancia de la clase ClasePrueba y ejecutando el método Type.InvokeMember().


 // Utilizamos la clase Activator para ejecutar el método de otro objeto
 object objeto2 = Activator.CreateInstance(tipoObjeto);
 dato = (int)tipoObjeto.InvokeMember("sumarUno", BindingFlags.InvokeMethod, null, objeto2, parametros);

 Console.WriteLine("El resultado de la invocación (mediante Activator) ha sido: " + dato.ToString());

El resultado sería el siguiente:

090502Resul

Introducción a Reflection (II): Obteniendo metadatos de una clase


En artículos anteriores hemos visto la potencia de las clases contenidas en el espacio de nombres Reflection. Hoy vamos a intentar profundizar un poco más en la potencia que podemos obtener utilizando estos recursos.
Como hemos visto, Reflection proporciona clases que permiten leer los metadatos de una clase. Bien… habrá quien se pregunte… ¿qué es un metadato? Un metadato no es más que un dato que describe a otro dato. Por ejemplo, en el ámbito que vamos a ver a continuación, el nombre de la clase es en sí un metadato. De hecho, la propia clase en sí es un metadato, ya que describe un “patrón” o estructura de los objetos que derivan de ella.
Reflection nos proporciona una serie de clases que se encargan de encapsular ensamblados, módulos y tipos. Esto nos permite realizar acciones como conocer el nombre de una clase o invocar un método de un objeto -a priori- desconocido en tiempo real.

(más…)

Eliminar la hora a una fecha en SQL Server 2005


Hay veces que pese a recibir un parámetro en formato fecha que almacene dia y hora, nos interese únicamente almacenar (o recuperar) la parte correspondiente a la fecha.
Aquellos que hayan realizado alguna vez comparaciones de fechas situadas en un intervalo probablemente sepan de qué hablo: ¿Comparar entre un día a las 00:00:00 y el siguiente a las 00:00:00? ¿Entre el mismo día entre las 00:00:00 y las 23:59:59?

Una forma simple de ahorrarnos dolores de cabeza sería almacenar la fecha sin hora. Para ello haremos uso de los distintos formatos de fecha que nos permite promocionar SQL Server y escribiríamos algo así:

 UPDATE NombreTabla SET CampoFecha = convert(datetime, convert(varchar(10), CampoFecha, 103))  

Con lo cual, almacenaremos nuestra fecha en formato DD:MM:AAAA 00:00:00.

Implementación de interfaces en C#


Una técnica imprescindible para lograr un adecuado nivel de abstracción es la utilización de interfaces.

Hablando en llano, una interfaz no es más que una estructura de datos que muestra únicamente las firmas de los métodos de una clase. A partir de ahí, una clase que herede de la interfaz estará obligada a “rellenar” la implementación de dichos métodos. De forma simple, una interfaz sería algo así:

Interfaz Motor
    Arrancar();
    Detener();

(más…)

Añadiendo un TemplateField a un GridView de forma dinámica


La mayor parte de los sitios web que se sirven de listados lo hacen utilizando un esquema como el que sigue:

090501Grid
Como vemos, lo normal suele ser utilizar campos enlazados a la consulta (BoundFields), así como campos-plantilla (TemplateFields) con los que se suele interactuar con el listado (ver detalles, edición, etc.).

Si sabemos de antemano qué datos vamos a obtener, es sencillo “maquetar” nuestro GridView de forma sencilla. El problema radica en el momento en el que el número de columnas que nuestro GridView mostrará sea variable. En este caso, el atributo AutoGenerateColumns deberá ser puesto a True, con lo que perderemos el control del orden de los TemplateFields.

Veamos el siguiente caso: queremos un campo plantilla que muestre un LinkButton que nos permita editar el registro, y otro que nos permita visualizar los detalles del mismo. Queremos uno de estos campos en la primera columna (el de edición) y otro en la última (visualización). Si utilizamos TemplateFields con el atributo AutoGenerateColumns=”True”, lo que lograremos será que ambos botones se sitúen juntos en las dos primeras columnas de nuestro GridView. ¿Cómo hacer que uno de ellos se coloque al final? Añadiéndolo de forma dinámica.

Para comenzar, añadiremos un GridView con un TemplateField conteniendo un LinkButton, colocando el atributo AutogenerateColumns=”True”.

090502Grid
El templatefield contendría un LinkButton:

090503Grid

El resultado sería este:

090504Grid

Seguidamente, acudiremos a nuestro código, listos para editar el evento RowDataBound. En él haremos lo siguiente:

  • Declararemos una celda y un LinkButton.
  • Daremos formato al LinkButton (nombre, clase, etc.)
  • Asignaremos al LinkButton un CommandArgument igual al Identificador (DataKey) de esa fila del GridView
  • Añadiremos el LinkButton a la celda, y la celda a la fila

Para ello utilizaremos el siguiente código:


Protected Sub GridView1_RowDataBound(ByVal sender As Object, ByVal e As System.Web.UI.WebControls.GridViewRowEventArgs) Handles GridView1.RowDataBound
Try

    ' Si se trata de una fila de datos...
    If e.Row.RowType = DataControlRowType.DataRow Then
        Dim celda As New TableCell()
        Dim boton As New LinkButton()

        boton.Text = "Detalles"
        If e.Row.Cells.Count > 1 Then
            boton.CommandArgument = GridView1.DataKeys(e.Row.RowIndex).Value.ToString()
        End If

        ' Añadimos el LinkButton a la celda, y la celda a la fila
        celda.Controls.Add(boton)
        e.Row.Controls.Add(celda)
    End If
Catch ex As Exception
    Throw ex
End Try
End Sub

Con esto lograremos el buscado esquema [Botón] [CAMPOS] [Botón].

Realizando una transacción en SQL Server


Por norma general, me gusta codificar la lógica de acceso a datos en programa, en lugar de en procedimientos almacenados en base de datos. Esta aproximación no es muy óptima (como se suele decir, al César lo que es del César y a Dios lo que es de Dios), ya que el entorno transaccional estará mejor gestionado por el SGBD que por el código .NET.
Si queremos trasladar la responsabilidad de gestionar la transacción al SGBD, deberemos saber en primer lugar que se declara del siguiente modo:

BEGIN TRAN
 -- Ejecución de sentencias
COMMIT TRAN

Como imaginaremos, la primera sentencia “arranca” la transacción, mientras que la última la compromete. Pero ¿y si ocurre un error en alguna de las sentencias? ¿Cómo lo detectamos?
En el siguiente ejemplo, utilizaremos variables enteras para trazar los fallos y realizar un ROLLBACK en el caso de que la transacción falle.

Se declarará una variable de tipo INT por cada sentencia a ejecutar, y se inician a 0. Se comprueba, después de cada sentencia ejecutada, la variable @@Error, y se almacena en las variables anteriormente creadas. Si es distinta de 0, hay un error, por lo que se añade información al mensaje de error.
Al final de la transacción, si todas las variables son 0, se hace el COMMIT. Si una de ellas es distinta de cero, significará que existe un error y se hace el Rollback, mostrando el mensaje de error.


DECLARE @CadenaError varchar(1000);
 DECLARE @CodigosError varchar(1000);
 DECLARE @ErrorSentencia1 int;
 DECLARE @ErrorSentencia2 int;
 DECLARE @ErrorSentencia3 int;

 SET @ErrorSentencia1 = 0;
 SET @ErrorSentencia2 = 0;
 SET @ErrorSentencia3 = 0;

 SET @CadenaError = 'Error al realizar actualización: ';
 SET @CodigosError = 'Códigos de error: ';

 -- Inicio de la transacción
 BEGIN TRAN

 -- Primera actualización
 UPDATE Usuario SET Nombre = 'Elena' WHERE Nombre = "Helena";

 -- Almacenamos el posible código de error
 SET @ErrorSentencia1 = @@Error;
 IF (@ErrorSentencia1  0)
 BEGIN
     SET @CadenaError = @CadenaError + 'Actualización 1; '
     SET @CodigosError = @CodigosError + CAST(@ErrorSentencia1 AS VARCHAR(10)) + '; ';
 END

 -- Segunda actualización
 UPDATE Perfil SET Nombre = 'Administrador' WHERE Nombre = "Admin";

 -- Almacenamos el posible código de error
 SET @ErrorSentencia2 = @@Error;
 IF (@ErrorSentencia2  0)
 BEGIN
     SET @CadenaError = @CadenaError + 'Actualización 2; '
     SET @CodigosError = @CodigosError + CAST(@ErrorSentencia2 AS VARCHAR(10)) + '; ';
 END

 -- Tercera actualización
 UPDATE UsuarioPerfil SET CodigoUsuario = 1 WHERE CodigoPerfil < 3;

 -- Almacenamos el posible código de error
 SET @ErrorSentencia3 = @@Error;
 IF (@ErrorSentencia3  0)
 BEGIN
     SET @CadenaError = @CadenaError + 'Actualización 3; '
     SET @CodigosError = @CodigosError + CAST(@ErrorSentencia3 AS VARCHAR(10)) + '; ';
 END

 -- En ausencia de errores, comprometemos la transacción
 IF (@ErrorSentencia1 = 0) AND (@ErrorSentencia2 = 0) AND (@ErrorSentencia3 = 0)

BEGIN
     COMMIT TRAN;
     PRINT('Transacción realizada con éxito');
 END
 ELSE
 BEGIN
     ROLLBACK TRAN;
     PRINT('Transacción abortada');
     PRINT(@CadenaError);
     PRINT(@CodigosError);
 END

Con todo esto, obtendremos suficiente información (esperemos) como para detectar donde está el fallo.

Concatenar campos nulos en una consulta SQL


Hay ocasiones en las que puede que se nos pidan consultas del siguiente tipo:

SELECT Nombre + ' ' + Apellido1 + ' ' + Apellido2 AS NombreCompleto FROM Usuario

Es decir: concatenar varios campos y devolverlo como uno solo. Ante esto existe un problema bastante común: ¿Qué ocurre cuando uno de estos campos es NULL?
Si uno de estos campos es NULL, por defecto el registro devolverá NULL. Esto es, si tenemos un usuario llamado Santiago, cuyo primer apellido es López y su segundo apellido no consta en la base de datos, el resultado de concatenar Santiago + López + NULL será NULL.

Una posible solución a este problema es utilizar condicionales dentro de la propia sentencia T-SQL, haciendo algo así:

SELECT U.IdUsuario, U.Nombre + ' ' + CASE 
     WHEN U.Apellido1 is NULL THEN '' 
     ELSE U.Apellido1 END + ' ' + CASE 
     WHEN U.Apellido2 is NULL THEN ' ' 
     ELSE U.Apellido2 END AS NombreCompleto
FROM Usuario U

Esto nos devolverá una columna “NombreCompleto” con los campos concatenados correctamente, sin temor a que éstos sean NULL.

Obteniendo el esquema de una Tabla de SQL Server


Hay lugares en los que la generación automática de código no está del todo bien vista. Sin embargo, utilizando las herramientas apropiadas, un generador de código simple puede ahorrar muchas horas de trabajo.
Esta introducción parece no tener demasiado que ver con el título del artículo, pero en realidad está bastante relacionada.

La razón es la siguiente: hemos visto previamente, cuando hablábamos del patrón DAO, que la necesidad de crear entidades DTO para cada tabla de la base de datos era una realidad.

Bien, hasta aquí todo claro. Pero… ¿y si fuese posible crear estos ficheros de forma automática? ¿Cómo? Preguntándole a la base de datos qué tablas y campos posee, y generando a partir de ellos el código necesario para implementar un DTO. Y todo ello sin despeinarnos.
Este artículo trata de hacer un breve inciso en el principio en el que se basa dicha generación de código. Únicamente veremos el mecanismo por el cual preguntar a la base de datos por estos parámetros. Hecho esto, podremos crear un fichero fuente con facilidad.

Para comenzar, declararemos un método que, a partir de una conexión de tipo Sql Server, devuelva un DataSet con una colección de tablas, cuyo nombre sea el propio nombre de la tabla y sus campos sean los datos de dichas tablas que queramos obtener.


      public DataSet ObtenerEsquemaBD(SqlConnection Conexion)
        {

Lo primero que haremos será abrir la conexión, con su correspondiente comprobación de nulos (es decir, si la conexión existe o no).

            try
            {
                if (!(Conexion == null))
                {
                    // Abrimos la conexión
                    if (!(Conexion.State == ConnectionState.Open))
                    {
                        Conexion.Open();
                    }

Seguidamente, crearemos un DataTable para almacenar los nombres de las tablas de la base de datos, así como un DataSet y un DataAdapter para almacenar los datos de las consultas. Utilizaremos una cadena de texto para almacenar las sentencias de las consultas.

                    // Creamos un DataTable para almacenar los nombres de las tablas de la base de datos
                    DataTable dtTablas = new DataTable();

                    // Creamos un DataSet y un DataAdapter para obtener los datos
                    DataSet ds = new DataSet();
                    SqlDataAdapter da = new SqlDataAdapter();

                    // Declaramos una cadena para la consulta
                    string sSQL;

Indicaremos que queremos obtener las tablas de la Base de Datos, y lo almacenamos en el DataTable destinado a ello mediante el DataAdapter

                    sSQL = "SELECT TABLE_NAME as Tabla FROM Information_Schema.Tables";
                    da.SelectCommand = new SqlCommand(sSQL, Conexion);
                    da.Fill(dtTablas);

Ahora recorreremos la tabla que contiene los nombres de cada tabla, realizando una consulta por cada una de ellas. En esta consulta obtendremos datos como el nombre de columna, si admite nulos o no, el tipo de dato…

                    foreach (DataRow dr in dtTablas.Rows)
                    {
                        if(!dr.IsNull("Tabla"))
                        {
                            DataTable dt = new DataTable();
                            sSQL = ("SELECT TABLE_CATALOG as Db, TABLE_NAME as Tabla, ORDINAL_POSITION as Posicion, COLUMN_NAME as Columna" +
                            ", IS_NULLABLE as AdmiteNulos, DATA_TYPE as TipoDatos, CHARACTER_MAXIMUM_LENGTH as MaxChar FROM Infor" +
                            "mation_Schema.Columns WHERE TABLE_NAME = \'"
                                        + (dr["Tabla"].ToString() + "\'"));
                            da.SelectCommand = new SqlCommand(sSQL, Conexion);
                            da.Fill(dt);

                            // Hecha la consulta, añadimos la tabla obtenida por este procedimiento a nuestro DataSet
                            dt.TableName = dr["Tabla"].ToString();
                            ds.Tables.Add(dt);
                        }
                    }

Finalmente, cerramos la conexión y devolvemos el DataSet

                    Conexion.Close();
                    return ds;
                }
                else
                {
                    Conexion.Close();
                    return null;
                }
            }
            catch (Exception ex)
            {
                throw(ex);
            }
        }

El resultado aparecerá en el DataSet, almacenando, por lo tanto, un esquema por cada tabla.

090501Tablas

Abstracción de datos (III): Utilizando Reflection para implementar un DAO en C#


El siguiente paso de nuestro objetivo de lograr un nivel de abstracción decente a la hora de implementar un modelo de datos consiste en la utilización del espacio de nombres Reflection. En una entrada anterior mostrábamos una función que nos devolvía un diccionario que contenía tanto el nombre como el valor de los filtros de la consulta.
Hoy vamos a ir más allá. Vamos a olvidarnos de la codificación de un DTO concreto: codificaremos un DAO que espere un objeto DTO genérico que cumpla los siguientes requisitos:

  • Cada Propiedad se corresponderá con un campo de la fuente de datos.
  • El nombre de la clase será DTO + Nombre de la tabla

Con estos dos requisitos, podremos codificar un DAO que comienza a abstraerse de los datos en sí, ligándose únicamente a una estructura arquitectónica y a una fuente de datos concreta (SQL Server). El siguiente paso será eliminar la segunda de estas restricciones, como veremos posteriormente.
Es importante partir de la entrada anterior para entender esta entrada, ya que no escribiré todo el código, sino que mostraré únicamente las modificaciones necesarias a los ficheros anteriores para poder entender el ejemplo.
DTOUsuario

La clase DTOUsuario no sufrirá modificación alguna. Esto es, será el DAO el que se adapte para conseguir la abstracción de un DTO en concreto.
DAOGenerico

En lugar de implementar las sentencias simples (SELECT, INSERT, UPDATE, DELETE) en cada uno de los DAO, crearemos una clase base llamada DAOGenerico en la que crearemos estas operaciones comunes. Posteriormente, las clases que hereden de ésta implementarán métodos específicos para cada entidad.
El primer cambio que tenemos en esta clase es la presencia de las funciones ObtenerElementos (visto en el primer post dedicado a Reflection) y ObtenerOrdenSql (vista en la anterior parte). Codificadas ambas funciones (manteniéndolas intactas), veremos cómo cambiamos la función select. El cuerpo de la misma será el siguiente:

public DataSet select(object datos)

Vemos que ya no esperamos un DTOUsuario, sino que nos basta un objeto cualquiera.

El siguiente paso será declarar los mismos elementos que en la versión anterior, así como comprobar que el objeto recibido no es nulo.

 // Instanciamos un DataSet, que albergará el contenido de la consulta
 DataSet ds = new DataSet();

// Declaramos dos StringBuilders: uno para la sentencia y otra para los campos
 // A su vez, declaramos un ArrayList para almacenar los parámetros.
 StringBuilder SQLString = new StringBuilder();
 StringBuilder Campos = new StringBuilder();
 ArrayList Parametros = new ArrayList();
 String NombreTabla = "";

 // Comprobamos que el DTO exista
 if (datos == null)
 throw new NullReferenceException("GenericDAO.select(datos)");

A continuación, recorremos los elementos del DTO, añadiendo los filtros en el caso de que éstos existan. Recordemos que Reflection nos proporcionaba la funcionalidad necesaria para almacenar en un diccionario el nombre y el valor de cada propiedad del objeto.

 Dictionary coleccionDatos = new Dictionary();
 // Obtenemos todas las propiedades del objeto
 coleccionDatos = ObtenerElementos(datos, MemberTypes.Property);

Ahora iteraremos cada elemento del diccionario, añadiendo, como hacíamos antes, elementos del tipo “Filtro = @Filtro”, solo que ahora el nombre del filtro viene dado por la clave del diccionario y su valor, por el contenido de el elemento que corresponde a esa clave.

 if (coleccionDatos != null)
 {
 foreach (string key in coleccionDatos.Keys)
 {
 if (coleccionDatos[key] != null)
 {
 Campos.Append(key).Append(" = @").Append(key).Append(" AND ");
 Parametros.Add(new SqlParameter("@" + key, coleccionDatos[key]));
 }
 }
 }

Como última novedad, obtendremos el nombre de la tabla a partir del nombre de la clase, eliminando del principio el prefijo “DTO”.

 // Creamos la cláusula SELECT
 SQLString.Append("SELECT * FROM ").Append((datos.GetType()).Name.TrimStart("DTO".ToCharArray()));

El resto del método es igual a la versión anterior.

 // Añadimos la cláusula WHERE, en caso de que sea necesaria
 if (Campos.Length > 0)
 {
 SQLString.Append(" WHERE ");

 // Como la cadena acabará en 'AND ', eliminamos los últimos 4 caracteres para cerrar la sentencia
 SQLString.Append(Campos.ToString().Substring(0, Campos.ToString().Length - 4));
 }

 // Obtenemos un SqlCommand configurado al efecto
 SqlCommand orden = ObtenerOrdenSql(SQLString.ToString(), Parametros);

 // Instanciamos un SqlDataAdapter y efectuamos la consulta
 SqlDataAdapter da = new SqlDataAdapter(orden);
 da.Fill(ds);

 // Por último, devolvemos el DataSet
 return ds;

Hecho esto, podemos crear un DTO por cada tabla de la base de datos. Siempre y cuando cumplamos las normas de respetar los nombres de tabla y columnas, esta función devolverá la consulta apropiada a cada entidad DTO llamando a la función select() y pasándole el objeto DTO como parámetro.

Introducción a Reflection: Recorriendo los elementos de un objeto


La reflexión es un proceso por el cual se posibilita observar y modificar la estructura y comportamiento de un objeto. Utilizar reflexión para programar implica un grado de abstracción enorme, y su complejidad es relativamente elevada.
La reflexión es soportada por multitud de lenguajes, y se aprovecha de la posibilidad de tratar a las instrucciones como datos. Generalmente se utiliza con el objetivo de observar o modificar el estado o el comportamiento de un programa en tiempo de ejecución. Es, por tanto, la base de la metaprogramación.
El Framework .NET posee un espacio de nombres dedicado a esta filosofía: System.Reflection. Veremos un pequeño ejemplo de cómo podemos, por ejemplo, obtener información sobre un objeto en concreto, como el nombre de sus elementos y su contenido.

La clase Dictionary

Las colecciones de datos, incluidas en los espacios de nombres System.Collections y System.Collections.Generic nos permiten agrupar datos de forma más eficiente de lo que nos permitiría, por ejemplo, un Array.
Las estructuras de este espacio de nombres más utilizadas suelen ser los Hashtable (pares clave-valor) y los ArrayList (Arrays de objetos heterogéneos). No obstante, y a nivel personal, una de las colecciones de datos que más potentes me parecen es la clase Dictionary.
Esta clase, al igual que el Hashtable, permite almacenar pares clave-valor. La diferencia está en que, a diferencia del Hashtable, la clave puede ser del tipo que nosotros escojamos (un entero, una estructura, o incluso una clase). Además, es posible acceder tanto a la lista de claves como a la de valores. Por tanto, al crear un diccionario le proporcionaremos la información del tipo de la clave y el tipo del valor.
Para nuestro ejemplo, crearemos un diccionario de tipo (string, object), es decir, un diccionario en el que la clave sea una cadena de texto y el valor sea un objeto.

Obteniendo información sobre un objeto.

Conociendo la clase Dictionary, es hora de declarar el cuerpo de la función que vamos a codificar. Se tratará de una función que recibirá dos parámetros: un objeto y un MemberType. El MemberType no es más que el tipo de miembro que queremos obtener del objeto (método, propiedad, etc.). Por lo tanto, el cuerpo de nuestra función será el siguiente:

private Dictionary<string , object> ObtenerElementos(object objeto, MemberTypes TipoElemento)
{
}

No debemos olvidarnos, eso sí, de incluir los espacios de nombres System.Reflection y System.Collections.Generic para poder hacer uso de la clase Dictionary y de Reflection, respectivamente.
Comenzaremos por declarar un diccionario de tipo (string, object).

Dictionary<string , object> Elementos = new Dictionary<string , object>();

A continuación, recorreremos la información de cada elemento del objeto con un bucle foreach. Para ello utilizaremos la clase MemberInfo.

foreach (MemberInfo infoMiembro in objeto.GetType().GetMembers())
{
}

En el interior del bucle, comprobaremos si el tipo del objeto es el que nosotros buscamos, y si es así, lo insertaremos en nuestro diccionario.

if (infoMiembro.MemberType == TipoElemento)
{
   if ((PropertyInfo)infoMiembro != null)
     Elementos.Add(((PropertyInfo)infoMiembro).Name, ((PropertyInfo)infoMiembro).GetValue(objeto, null));
}

Como podemos ver, el tipo de miembro puede ser de varios tipos.

090509MemberTypes
Finalmente, el diccionario devolverá los nombres de los elementos junto a sus respectivos valores. Por ejemplo, si buscamos las propiedades del objeto, le pasaríamos a la función como segundo parámetro MemberTypes.Property. Si el objeto tuviera una propiedad llamada “IdUsuario” cuyo valor fuese 1, el diccionario guardaría una entrada (“IdUsuario”, (object)1).

El método completo sería el siguiente:

 /// <summary>
 /// Función encargada de devolver un tipo de elemento en concreto de un objeto en un conjunto
 /// de pares clave - valor.
 /// </summary>
 /// <changelog>
 /// Daniel García    15/05/2009    Creación
 /// </changelog>
 private Dictionary<string , object> ObtenerElementos(object objeto, MemberTypes TipoElemento)
 {
   try
   {
     // Declaramos un Diccionario que contendra el nombre de los elementos del objeto y el
     //contenido de cada elemento.
     Dictionary<string , object> Elementos = new Dictionary<string , object>();

     // Se recorren los miembros del objeto
     foreach (MemberInfo infoMiembro in objeto.GetType().GetMembers())
     {
       // Si el tipo del objeto es del tipo que buscamos, se añade al diccionario
       if (infoMiembro.MemberType == TipoElemento)
       {
         if ((PropertyInfo)infoMiembro != null)
           Elementos.Add(((PropertyInfo)infoMiembro).Name, ((PropertyInfo)infoMiembro).GetValue(objeto, null));
       }
     }
     return Elementos;
   }
   catch (Exception ex)
   {
   throw (ex);
   }
 }

Posteriormente encontraremos una utilidad práctica a esta función, aplicándola a nuestro patrón DAO para no tener que depender de los nombres de los campos. Esta sencilla función ayudará a aumentar muchísimo el nivel de abstracción e independencia de nuestra aplicación.

 

ACTUALIZACIÓN: 12/11/2013

Podemos realizar esta última operación mediante LINQ de una forma aún más sencilla.


        private static Dictionary<string, object> ObtenerElementosLinq(object objeto, MemberTypes TipoElemento)
        {
            try
            {
                // Declaramos un Diccionario que contendra el nombre de los elementos del objeto y el
                //contenido de cada elemento.
                Dictionary<string, object> Elementos = new Dictionary<string, object>();

                // Usamos LINQ para realizar la consulta
                var consulta = from i in objeto.GetType().GetMembers()
                                where ((i.MemberType == TipoElemento) && (i != null))
                                select new KeyValuePair<string,object>(((PropertyInfo)i).Name,
                                                        ((PropertyInfo)i).GetValue(objeto, null));

                // Recorremos la lista de pares clave-valor forzando su ejecución
                foreach (KeyValuePair<string, object> kvp in consulta)
                    Elementos.Add(kvp.Key, kvp.Value);

                // Devolemos el diccionario
                return Elementos;
            }
            catch (Exception ex)
            {
                throw (ex);
            }
        }

Abstracción de datos (II): Construyendo un DAO simple en C#


Como introducción a la aplicación práctica de este patrón que ya vimos previamente, daremos las indicaciones necesarias para construir un DAO simple para una entidad en concreto.

El ejemplo que mostraremos ahora se tratará de un elemento macizo y poco funcional, que posteriormente iremos retocando y refinando para alcanzar un nivel de abstracción lo suficientemente elevado como para exportarlo a un componente plenamente reutilizable.

(más…)

Ejecutando programas externos a nuestra aplicación


En cierta ocasión almacené en DVD una serie de instaladores de los programas que utilizaba con más frecuencia. Me planteé que sería buena idea utilizar una aplicación de autoarranque con un menú navegable en el que se accediese directamente a la instalación del programa seleccionado. ¿Cómo ejecutar un programa ajeno a nuestra aplicación desde código? Creando un nuevo proceso y asignándole la información necesaria para “saber” qué tiene que ejecutar.
Como ejemplo simple, lanzaremos una consola en la que el directorio de trabajo sea C:\. En tres pasos tendremos nuestra aplicación funcionando:

  • Crearemos una instancia de la clase Process
  • Informaremos de los datos oportunos (ruta del ejecutable, parámetros, directorio de trabajo…)
  • Lanzaremos el proceso.

' Declaramos un objeto que identificará al nuevo proceso
 Dim proceso As New Process()

 ' Indicamos la ruta del ejecutable que queremos lanzar
 proceso.StartInfo.FileName = "cmd.exe"

 ' Indicamos el directorio de trabajo
 proceso.StartInfo.WorkingDirectory = "C:\"

 ' En caso de ser necesario, podemos indicarle argumentos.
 proceso.StartInfo.Arguments = "/T:0A"

 ' Indicamos el estilo de la ventana
 proceso.StartInfo.WindowStyle = ProcessWindowStyle.Normal

 ' Por último, lanzamos el proceso
 proceso.Start()

Como vemos, hemos obtenido lanzar la orden cmd.exe /T:0A (el argumento /T cambia los colores de la consola) en el directorio de trabajo C:\.

ejemploCmd

Como podremos imaginar, así podremos ejecutar cualquier programa a cuya ruta tengamos acceso.

Instalando PHP en Internet Information Server (IIS)


Para comenzar la instalación de PHP bajo Internet Information Server, necesitaremos en primer lugar descargar la versión para Windows de PHP. Para ello, nos dirigiremos a la página http://www.php.net/downloads.php y seleccionaremos la versión más actual de los binarios para Windows (Windows Binaries).
Descarga de PHPDescarga de PHP
Hecho esto, descomprimiremos el contenido del fichero que acabamos de descargar en una carpeta local, por ejemplo, C:\PHP.

PHPIIS_02 - Carpeta
En el interior de la carpeta buscaremos un fichero denominado php.ini-recommended, en el cual se encuentra la configuración por defecto recomendada para prácticamente cualquier sistema.

PHPIIS_03 - Configuracion Recomendada
Renombraremos el fichero como php.ini, pasando a continuación a editarlo.

PHPIIS_04 - Configuracion Recomendada 2

Asignaremos valores a las siguientes variables:<br/>

short_open_tag = On
extensión_dir = “c:\php\ext”
cgi.force_redirect = 0
doc_root = c:\inetpub\wwwroot

PHPIIS_05 - Configuracion Recomendada 3

Como paso previo a la configuración del IIS, editaremos las variables del sistema para incluir el PATH de php en el PATH del sistema. Para ello, haremos click derecho sobre Mi PC y seleccionaremos [Propiedades] (o bien utilizaremos el atajo de teclado [Tecla de Windows] + [Pausa]). Una vez en el menú, seleccionaremos la pestaña [Opciones Avanzadas] y haremos click sobre el botón [Variables de Entorno]. En el menú inferior, buscaremos la variable llamada “Path” y pulsaremos sobre el botón [Modificar].

PHPIIS_07 - PATH
Por último, añadiremos en el valor de la variable “;C:\PHP” para añadir la ruta de nuestro directorio de PHP.

Hecho esto, deberemos configurar nuestro IIS para que reconozca PHP. Para ello abriremos la consola de configuración de IIS, por lo que iremos a [Inicio] > [Herramientas] > [Panel de Control] > [Herramientas Administrativas] > [Servicios de Internet Information Server]. Desplegaremos el equipo local y haremos click derecho sobre Sitio Web Predeterminado, seleccionando la opción [Propiedades].

PHPIIS_08 - Propiedades
A continuación seleccionaremos la pestaña [Home Directory], y haremos click sobre el botón [Configuration].

PHPIIS_09 - HomeDirectory
Se abrirá una ventana en la que se nos mostrarán las distintas extensiones ISAPI.

PHPIIS_10 - AppConfigurationComo queremos registrar una nueva extensión (.php), pulsaremos sobre el botón [Add…].
Hecho esto, seleccionaremos el fichero php5isapi.dll de nuestro directorio PHP y añadiremos como extensión .php.

PHPIIS_11 - ExtensionMapping
Con esto, la extensión .php quedaría registrada.

Finalmente, reiniciamos IIS escribiendo iisreset desde consola…

PHPIIS_13 - iisreset
…y creamos un directorio virtual en el que crearemos un fichero llamado index.php en el que incluiremos el siguiente texto:




Prueba de PHP



<pre>
</pre>




Con esto, entrando en la página “http://localhost/<Directorio Virtual>/index.php”, deberíamos visualizar una página con información sobre la versión.

PHPIIS_14 - ok

Si esto ocurre, la instalación habrá tenido éxito.

Abstracción de datos (I): El patrón DAO


Anteriormente vimos cómo rellenar un DataSet a través de un SqlCommand y un SqlDataAdapter. Haremos ahora una pequeña introducción a un patrón de diseño: el patrón DAO.

En software de computadores, un Data Access Object (DAO, Objeto de Acceso a Datos) es un componente de software que suministra una interfaz común entre la aplicación y uno o más dispositivos de almacenamiento de datos, tales como una Base de datos o un archivo. El término se aplica frecuentemente al Patrón de diseño Object [Wikipedia.org].

Con el permiso de los más puristas, un DAO no es otra cosa que un “adaptador” o un nexo entre la lógica de negocio y la capa de persistencia (generalmente, una Base de Datos). Lo que realiza este adaptador no es otra cosa que codificar la lógica de acceso a datos de una capa de persistencia en concreto.

¿Qué significa todo esto? Significa que por cada fuente de datos deberemos implementar un DAO específico para ella, dejando inmaculado el resto del código: cualquier cambio en la fuente de datos implicará la utilización de un DAO distinto, pero la lógica de negocio permanecerá indiferente ante tal cambio. El DAO es pues, una suerte de traductor entre el idioma que habla nuestra aplicación y el idioma que habla una fuente de datos específica (habrá un DAO para SQL Server, otro para MySQL, otro para ficheros, etc.)

La pregunta que surge ahora es la siguiente: ¿cómo realizamos esta traducción? Pues a través de una clase intermedia, que se encargará de “calcar” la estructura de una tabla de la fuente de datos y almacenará de forma temporal los datos de éste, al que llamaremos DTO o Data Transfer Object.

Patrón DAO

A partir de este momento, nuestra aplicación “entregará” la información que quiera compartir con la fuente de datos al DAO encapsulada en un DTO. El DAO entonces extraerá la información de esa cápsula y la codificará de forma que la fuente de datos la entienda (por ejemplo, escribiendo una sentencia SELECT e instanciando los objetos necesarios para comunicarse con la base de datos).

Como es lógico, al revés ocurrirá lo mismo: la fuente de datos entregará al DAO información. Éste se encargará de encapsularla de forma que nuestra lógica de negocio sea capaz de entenderla. De este modo, manteniendo una misma interfaz para los métodos del DAO (por ejemplo, un método SELECT que esperará siempre un objeto DTO), podremos cambiar la fuente de datos sabiendo que, si ésta cambia, únicamente será necesario modificar el “traductor” de la lógica de la aplicación a la de datos.

A riesgo de parecer repetitivo, los pasos que cumple un patrón DAO son los siguientes:

  1. Nuestra aplicación encapsula la información en un DTO.
  2. El DAO toma ese DTO, extrae la información y construye la lógica necesaria para comunicarse con la fuente de datos (sentencias SQL, manejo de archivos…).
  3. La fuente de datos recibe la información en el formato adecuado para tratarla.

En sentido contrario, ocurrirá lo mismo:

  1. Nuestra aplicación le envía al DAO un DTO vacío.
  2. El DAO realiza una petición de datos a la fuente de datos.
  3. La fuente de datos envía al DAO la información.
  4. El DAO recopila esa información, la encapsula en el DTO (o en otro elemento que la aplicación entienda) y se la devuelve a nuestra lógica de negocio.

Tras esta introducción teórica, en sucesivos envíos aprenderemos a implementar un patrón DAO en C#. A medida en que vayamos aprendiendo conceptos, comenzaremos a utilizar métodos más avanzados y sofisticados para conseguir una capa de persistencia lo más débilmente acoplada posible, todo ello mediante herramientas como ADO.NET, Reflection y el Data Access Application Block de la Enterprise Library.

El sueldo de un informático


Durante los años de bonanza económica, los profesionales de IT nos hemos quejado mucho de nuestros sueldos. Abanderados del mileurismo, hoy por hoy podemos comprobar que, pese a la omnipresente y mediática crisis en la que nos hallamos inmersos, la demanda de profesionales del sector sigue siendo muy elevada.
Curioso por el sueldo medio de un profesional de IT ajeno a mi actual empresa, me decidí a realizar un pequeño estudio acerca de lo que puede cobrar un profesional del gremio en relación a la experiencia aportada, y el resultado queda reflejado en la siguiente tabla:

EVOLUCIÓN SALARIAL POR CATEGORÍAS € Bruto/anual € Neto/mes (14 pagas) € Neto/mes (12 pagas)
Programador Junior (0-2 años) 13.000 820 972
14.000 860 1.020
15.000 910 1.080
16.000 960 1.130
17.000 1.020 1.200
Programador Semisenior (2-3 años) 18.000 1.060 1.260
19.000 1.100 1.310
20.000 1.160 1.370
21.000 1.200 1.420
22.000 1.260 1.490
Programador Senior (3+ años) 23.000 1.300 1.500
24.000 1.350 1.600
25.000 1.400 1.670
26.000 1.450 1.715
27.000 1.500 1.780
28.000 1.540 1.820
29.000 1.590 1.880
Analista-Programador Senior 30.000 1.650 1.950
31.000 1.680 1.990
32.000 1.730 2.050
33.000 1.780 2.110
34.000 1.840 2.170
35.000 1.870 2.210
Analista / Jefe de Proyecto 36.000 1.920 2.270

Hay que destacar un par de hechos importantes:

  • En primer lugar, los sueldos varían muchísimo de unas comunidades a otras. Un programador senior en Madrid puede cobrar perfectamente más de 5000€ brutos/anuales más que en Castilla y León, por poner un ejemplo. Obviamente, hay que tener en cuenta que el coste de la vida en la capital es mucho mayor que, por ejemplo, en Soria, Ávila o Zamora.
  • Como segundo apunte, las estimaciones dadas son orientativas. He tomado como fuente de los sueldos netos una herramienta online para calcular salarios, mientras que los datos de los sueldos medios han sido extraídos de Infojobs Trends Salarios y de un estudio previo del tema.
  • La tecnología también influye dramáticamente. Las tecnologías o especialidades mejor pagadas son, en orden ascendente, PHP, Java, .NET, Administración de Sistemas y SAP, por lo que habría que calibrar positiva o negativamente estos datos acorde a la especialidad. El punto medio está tomado en Java / .NET, por lo que un programador PHP cobrará algo menos de lo marcado en la tabla, y un especialista en SAP cobrará sensiblemente más.
  • Por último, me he permitido la licencia de marcar los objetivos más clásicos a nivel de sueldo: un mileurista cobra, como mínimo, cerca de 17.000 € brutos anuales. Para cobrar 1.200 serán necesarios 21.000 € brutos/año, mientras que si queremos alcanzar los 1.500 hará falta una nómina de 27.000 € brutos/año.
  • Otro dato a tener en cuenta: las empresas tienden a actuar como las operadoras de telefonía móvil, bancos o proveedores de ADSL: es más fácil mantener a un trabajador que atraer a uno nuevo, por lo que los incentivos salariales para los recien llegados tienden a ser mayores que para “los de casa”. Es fácil que un nuevo trabajador con 3 años de experiencia cobre más que un trabajador que lleva 5 años en la empresa. Por lo tanto, la movilidad laboral tiende a influir positivamente en el aumento de sueldo (como describía con mordaz ironía sinergia sin control).

Rellenando un DataSet con una invocación a procedimiento almacenado


Cuando queremos ejecutar un procedimiento almacenado, por norma general necesitaremos dos parámetros: una cadena con el nombre del procedimiento, y una lista de parámetros que contendrá los filtros por los cuales el procedimiento almacenado limitará su búsqueda. Tomando como ejemplo una base de datos Sql Server, pensemos en el siguiente procedimiento almacenado:


SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
-- =============================================
-- Author:	Daniel García
-- Create date: 11/05/2009
-- Description:	Autentica un usuario
-- =============================================
CREATE PROCEDURE SP_AutenticarUsuario

@NombreUsuario	VARCHAR(20)	= NULL,
@Password		INT	= NULL

AS
BEGIN
SET NOCOUNT ON;

SELECT IdUsuario, Nombre, Apellido1, Apellido2 FROM USUARIO WHERE NombreUsuario = @NombreUsuario AND Password = @Password
END
GO

El código necesario para ejecutar un procedimiento almacenado sería el siguiente:


' Declaramos una conexión, pasándole como parámetro la cadena de conexión a la Base de Datos
Dim conexion As New SqlClient.SqlConnection("Data Source=SRVSQL2005;Initial Catalog=DatosCliente;User Id=DBUser;Password=DBPassword;")

' Declaramos un SqlCommand a partir del nombre del procedimiento y la conexión que hemos creado.
Dim command As New SqlClient.SqlCommand("SP_AutenticarUsuario", conexion)

' Indicamos que vamos a ejecutar un procedimiento almacenado
command.CommandType = CommandType.StoredProcedure

' Rellenamos los parámetros de entrada del procedimiento.
' NOTA: Se trata de un EJEMPLO. No hagáis esto en casa, niños, seguridad ante todo... =)
Dim nombreUsuario As String = "admin"
Dim password As String = "123456"

' Añadimos los parámetros al SqlCommand
command.Parameters.Add(New SqlClient.SqlParameter("@NombreUsuario", nombreUsuario))
command.Parameters.Add(New SqlClient.SqlParameter("@Password", password))

' Declaramos un DataAdapter a partir del SqlCommand y un DataSet para almacenar los datos.
Dim da As New SqlClient.SqlDataAdapter(command)
Dim ds As New DataSet

' Por último, rellenamos el DataSet
da.Fill(ds)

Con esto rellenaríamos el DataSet con una tabla que contendría los campos IdUsuario, Nombre, Apellido1 y Apellido2, tal y como mostraba el procedimiento.
Este proceso es sencillo, pero tiene un problema: aumenta de forma dramática el acoplamiento, al codificar la lógica de acceso a datos de forma monolítica. Si posteriormente necesitáramos añadir o eliminar algún campo de la consulta, deberíamos acceder a este código y modificarlo. Más adelante veremos cómo abstraer un poco la entidad a la cual se referencia y el acceso a datos, utilizando el llamado patrón DAO.

Rellenando un DataSet con una invocación a procedimiento almacenado


Cuando queremos ejecutar un procedimiento almacenado, por norma general necesitaremos dos parámetros: una cadena con el nombre del procedimiento, y una lista de parámetros que contendrá los filtros por los cuales el procedimiento almacenado limitará su búsqueda. Tomando como ejemplo una base de datos Sql Server, pensemos en el siguiente procedimiento almacenado:


SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
-- =============================================
-- Author:	Daniel García
-- Create date: 11/05/2009
-- Description:	Autentica un usuario
-- =============================================
CREATE PROCEDURE SP_AutenticarUsuario

@NombreUsuario	VARCHAR(20)	= NULL,
@Password		INT	= NULL

AS
BEGIN
SET NOCOUNT ON;

SELECT IdUsuario, Nombre, Apellido1, Apellido2 FROM USUARIO WHERE NombreUsuario = @NombreUsuario AND Password = @Password
END
GO

El código necesario para ejecutar un procedimiento almacenado sería el siguiente:


' Declaramos una conexión, pasándole como parámetro la cadena de conexión a la Base de Datos
Dim conexion As New SqlClient.SqlConnection("Data Source=SRVSQL2005;Initial Catalog=DatosCliente;User Id=DBUser;Password=DBPassword;")

' Declaramos un SqlCommand a partir del nombre del procedimiento y la conexión que hemos creado.
Dim command As New SqlClient.SqlCommand("SP_AutenticarUsuario", conexion)

' Indicamos que vamos a ejecutar un procedimiento almacenado
command.CommandType = CommandType.StoredProcedure

' Rellenamos los parámetros de entrada del procedimiento.
' NOTA: Se trata de un EJEMPLO. No hagáis esto en casa, niños, seguridad ante todo... =)
Dim nombreUsuario As String = "admin"
Dim password As String = "123456"

' Añadimos los parámetros al SqlCommand
command.Parameters.Add(New SqlClient.SqlParameter("@NombreUsuario", nombreUsuario))
command.Parameters.Add(New SqlClient.SqlParameter("@Password", password))

' Declaramos un DataAdapter a partir del SqlCommand y un DataSet para almacenar los datos.
Dim da As New SqlClient.SqlDataAdapter(command)
Dim ds As New DataSet

' Por último, rellenamos el DataSet
da.Fill(ds)

Con esto rellenaríamos el DataSet con una tabla que contendría los campos IdUsuario, Nombre, Apellido1 y Apellido2, tal y como mostraba el procedimiento.
Este proceso es sencillo, pero tiene un problema: aumenta de forma dramática el acoplamiento, al codificar la lógica de acceso a datos de forma monolítica. Si posteriormente necesitáramos añadir o eliminar algún campo de la consulta, deberíamos acceder a este código y modificarlo. Más adelante veremos cómo abstraer un poco la entidad a la cual se referencia y el acceso a datos, utilizando el llamado patrón DAO.