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.