martes, 21 de mayo de 2013

Autocrítica



Hace unas semanas platicaba con mis alumnos acerca de la autocrítica que debe existir para poder entregar un trabajo no perfecto pero sí aceptable, creo que la calidad en el proceso y el resultado depende mucho del nivel de autocrítica que uno pueda tener.

Es difícil darse cuenta de los errores que comete uno mismo, es difícil que estás fallando en algo si es que no sabes que tienes el error por lo que resulta muy complicado el prever problemas cuando la falta de conocimiento es la fuente, ahí creo que la única solución es la investigación y el autoaprendizaje.

Existe un punto muy interesante cuando un programador va a entregar un trabajo, la certidumbre de que se hizo todo lo posible por entregar algo de calidad y haber realizado las pruebas necesarias.

¿Nunca te ha pasado que cuando entregas el trabajo tienes esa sensasión de "ojalá nada falle"?, esa cosquilla en la panza que pide a gritos que no te vayan a regañar o el cliente no se vaya a poner fúrico por un pantallazo de error.

En el momento en que llegas a tener esa sensación debes detenerte y pensar si estás poniendo todo de tu parte para que el resultado sea tal como se desea, la incertidumbre es una señal inequívoca de un trabajo incompleto o de un esfuerzo a medias; si este es el sentimiento, podemos estar seguros que las cosas no se están haciendo como deberían.

Hacer pruebas es de las cosas que a un programador le resultan más tediosas, es un común denominador que las pruebas de integridad o de unidad se sienten como una parte inútil cuando sientes que tienes la habilidad suficiente y comprobable como para evitarlas, recordemos que hasta el mejor cazador se le va la liebre, por algo existen estas pruebas, podemos amparar un buen trabajo respecto a las pruebas que se diseñaron antes de hacer el trabajo, donde éstas cubren los datos de entrada y de salida de nuestros componentes o sistema.

¿Queremos un México con sistemas de calidad?, no hay de otra más que poner el esfuerzo necesario para cumplir con las espectativas, y superarlas si es posible.

Recomiendo ampliamente utilizar las herramientas que tiene Visual Studio para hacer pruebas de unidad e integración, o bien apoyarnos de herramientas externas como nUnit.

Te recomiendo esta página de MSDN para la creación y ejecución de de pruebas de unidad.

En unos días publicaré una entrada donde mostraré cómo crear una prueba de unidad utilizando los proyectos de prueba de .NET

EntityFramework en WCF



La entrada de hoy va a romper un poco la secuencia que venía publicando abordando temas de programación en SQL Server (específicamente de XML), pero hoy que estaba preparando mi curso 70-513 encontré un tema sumamente interesante, al menos a mí me lo parece porque me trabajo encontrar la solución en su momento.

Comencemos por establecer que tendremos 4 proyectos:
  1. Modelo de EntityFramework de una base de datos.
  2. Biblioteca de clases donde se encuentra definido e implementado nuestro servicio WCF.
  3. Aplicación de consola que aloja y expone nuestro servicio WCF.
  4. Aplicación de consola que utiliza el servicio expuesto por la aplicación de consola.
He decidido crear una biblioteca de clases para implementar el servicio para que la implementación sea totalmente independiente del alojamiento de la misma, es decir que esa misma biblioteca de clases la podríamos llevar a WAS, a un servicio de windows, a una aplicación de windows, etc.

Para obtener el script que genera la base de datos basta con dar clic en el siguiente enlace: script para crear base de datos de prueba.

Si le echamos un ojo al script veremos que la tabla Categorias tiene una referencia circular para generar una estructura jerárquica y que la tabla de ProductosRelacionados es básicamente una tabla intersección entre Productos y Productos para establecer los productos relacionados.

Esta entrada primero la voy a llevar por el camino que uno normalmente llevaría para enviar los objetos del EF en un servicio WCF y posteriormente haremos las modificaciones necesarias para corregir el problema que encontraremos.

Vamos a crear una solución en blanco y la llamaremos EFWCF, es importante anotar que tenemos que ejecutar el Visual Studio 2010 como administrador.    Esto es para evitar el uso necesario de netsh para asignar un puerto en particular a nuestra host del servicio WCF.

Vamos a agregar un proyecto de tipo Class Library a nuestra solución y la llamaremos EFWCFDB:
  1. Borramos el archivo Class1.cs que agrega por defecto el template del proyecto.
  2. Vamos a agregar un nuevo elemento de tipo ADO.NET Entity Data Model y lo llamaremos EFWCF.
  3. Elegimos crear el modelo desde una base de datos.
  4. Creamos una nueva conexión que apunte hacia nuestra base de datos llamada EFWCF, utilizaremos autenticación de Windows para efectos de prueba.
  5. Damos clic en Next, elegimos las tres tablas de la base de datos y activamos la casilla de verificación que dice "Pluralize or singularize generated object names" y damos clic en Finish.
El IDE nos presentará un modelo como el que muestro en la siguiente imagen:


Vamos a modificarlo un poco para darle más significado a las entidades y realizaremos los siguientes ajustes:
  1. Renombrar la propiedad Categoria.Categorias1 por CategoriasHijas.
  2. Renombrar la propiedad Categoria.Categoria1 por CategoriaPadre.
  3. Renombrar la propiedad Producto.Productos1 por ProductosRelacionados.
  4. Eliminar la propiedad Producto.Productos dado que su contenido ya está cubierto por la propiedad que acabamos de renombrar.
Con estos cambios nuestro modelo deberá quedar de la siguiente manera:


Con esto podemos dar por terminado nuestro modelo (por el momento) y ahora vamos a crear nuestro proyecto que tendrá la implementación del servicio WCF.

Vamos a agregar un nuevo proyecto a nuestra solución de tipo Class Library y lo llamaremos EFWCFServices:
  • Eliminamos el archivo Class1.cs que trae por defecto la plantilla de este tipo de proyectos.
  • Agregamos un nuevo elemento de tipo WCF Service y lo llamamos EFWCFService.
  • Abrimos el archivo IEFWCFService.cs y eliminamos el procedimiento DoWork con todo y su atributo OperationContract.
  • Abrimos el archivo EFWCFService.cs y eliminamos el procedimiento DoWork.
  • Agregamos referencia al proyecto EFWCFDB.
  • Agregamos referencia a System.Data.Entity.
  • Vamos a agregar una operación a IEFWCFService.cs con la siguiente firma: List<EFWCFDB.Producto> ProductosPorCategoria(int idCategoria)
  • Ajustamos nuestra implementación (EFWCFService.cs) de manera tal que quede así el código:
public class EFWCFService : IEFWCFService
{
    public List<EFWCFDB.Producto> ProductosPorCategoria(int idCategoria)
    {
        using (EFWCFDB.EFWCFEntities db = new EFWCFDB.EFWCFEntities())
        {
            return db.Productos.Where(p => p.Id_Categoria == idCategoria).ToList();
        }
    }
}

Ahora vamos a crear el proyecto que se utilizará como host de nuestro servicio, agreguemos a la solución un proyecto de tipo Console Application y lo llamaremos EFWCFHost.
  1. Vamos a agregar referencia a System.ServiceModel y a nuestro proyecto EFWCFServices.
  2. Agregamos un nuevo elemento de tipo Application Configuration File y lo dejamos con el nombre que trae por defecto.
  3. Vamos a registrar nuestro servicio desde el archivo app.config con el código que he colocado en esta liga: app.config para EFWCFService.    Es importante hacer un ajuste, en system.serviceModel - services - service - host - baseAddresses - add se hace referencia a una dirección que apunta hacia mi computadora, en tu caso deberás modificar esa dirección para que haga referencia al nombre de tu equipo.
  4. Una vez registrado el servicio en el app.config vamos a crear nuestro host en el procedimiento main del archivo Program.cs de nuestro proyecto con el siguiente código: código para host de EFWCFService.
  5. Hay que ejecutar el proyecto EFWCFHost sin depuración, esto es para dejarlo en ejecución y posteriormente agregar una referencia al servicio alojado en él.    Entonces, lo ejecutamos con Ctrl + F5 y lo dejamos corriendo, deberá aparecer una pantalla como esta:

Finalmente vamos a agregar nuestro proyecto cliente el cual será de tipo Console Application y lo llamaremos EFWCFClient.
  1. Agregamos referencia a System.Data.Entity.
  2. Damos clic derecho sobre el proyecto en el Solution Explorer y elegimos Add Service Reference.
  3. En la pantalla que sale vamos a apuntar la dirección base que se configuró en el archivo app.config de la aplicación EWCFHost, en mi caso es http://JorgeToriz-PC:8001/EFWCFServices.
  4. Al dar clic en el botón Go aparecerá nuestro servicio, damos clic en él y le damos el namespace EFWCFServices.
  5. En el procedimiento main de nuestro Program.cs vamos a colocar el siguiente código para mandar a llamar a nuestro servicio
EFWCFServices.EFWCFServiceClient client = new EFWCFServices.EFWCFServiceClient();
Console.WriteLine("Categoría 2: " + client.ProductosPorCategoria(2).Length.ToString());
En la base de datos hay tres productos en la categoría 2, pero está provocando un error debido a las referencias circulares que tenemos en nuestro modelo en el proyecto EFWCFDB.

El mensaje de error es como este: "An error occurred while receiving the HTTP response to http://jorgetoriz-pc:8001/EFWCFServices/v1/EFWCFService.svc. This could be due to the service endpoint binding not using the HTTP protocol. This could also be due to an HTTP request context being aborted by the server (possibly due to the service shutting down)".

¿La solución?, deshabilitar la propiedad "Lazy Loading Enabled" de nuestro modelo, vamos a abrir el archivo EFWCF.edmx que está en el proyecto EFWCFDB, una vez abierto damos clic sobre el fondo, vamos a las propiedades y cambiamos la propiedad de "true" a "false".

Guardamos cambios y compilamos el proyecto EFWCFServices (que mandará a compilar el EFWCFDB por tener referencia en la solución), detenemos nuestro servicio (EFWCFHost) y lo volvemos a arrancar, al ejecutar el cliente el error habrá desaparecido y mostrará algo como esto:


Si queremos que en la respuesta del servicio se incluya la referencia circular, lo deberemos implementar con la función Include de la siguiente manera (proyecto EFWCFServices - EFWCFServices.cs):

return db.Productos.Include("ProductosRelacionados").Where(p => p.Id_Categoria == idCategoria).ToList();

Si depuramos nuestro cliente veremos que ya aparecen los productos relacionados (propiedad ProductosRelacionados) con los productos que formaron parte del resultado, estos datos estarán vacíos por haber deshabilitado la propiedad "Lazy Loading Enabled" del modelo.

Espero te haya resultado de utilidad esta entrada.

jueves, 4 de abril de 2013

XML a Entidad-Relación



En esta entrada veremos cómo operar con documentos XML en un modelo entidad relación.   Para este objetivo utilizaremos el siguiente documento como fuente de datos:

DECLARE @Doc XML = '
<subcategories>
  <subcategory Name="Bib-Shorts">
    <products>
      <product ProductID="855" Name="Men''s Bib-Shorts, S" ListPrice="89.9900" />
      <product ProductID="856" Name="Men''s Bib-Shorts, M" ListPrice="89.9900" />
      <product ProductID="857" Name="Men''s Bib-Shorts, L" ListPrice="89.9900" />
    </products>
  </subcategory>
  <subcategory Name="Bike Racks">
    <products>
      <product ProductID="876" Name="Hitch Rack - 4-Bike" ListPrice="120.0000" />
    </products>
  </subcategory>
</subcategories>'

Podemos ver que hay un nodo raíz llamado subcategories y que contiene elementos subcategory quienes a su vez contienen a los productos que pertenecen a esa subcategoría.

Si queremos obtener los productos para poder cruzar la información del documento con alguna tabla resultaría muy complicado hacer un parser para obtenre la información que necesitamos.    Por este motivo SQL Server tiene funciones que nos permiten convertir el documento a una estructura entidad relación.

Antes de comenzar con los queries me gustaría mostrar los símbolos básicos del XPath, el cual es el lenguaje de búsqueda en XML:

Símbolo Uso
. Elemento actual
.. Elemento padre
/ Elemento hijo
// Búsqueda secuencial y jerárquica
[ ] Filtro
@ Atributo

Debemos tener en cuenta que la navegación de un documento XML se hace nodo a nodo por lo que es preferible convertirlo primero a una estructura entidad-relación y no estar navegándolo una y otra vez, si es un documento pequeño no le vamos a encontrar mucho problema, pero si fuera un documento muy grande seguro vamos a tener problemas de rendimiento.

Supongamos que queremos obtener todos los productos que trae el documento, comenzaremos con obtener la ruta de navegación que tenemos que seguir para llegar a los elementos que necesitamos, en este caso la ruta es /subcategories/subcategory/products/product.    Esta ruta en particular devolverá todos los nodos cuya ruta coincida con la especificada en la función.

La función .nodes recibe como parámetro una sentencia XPath, construirá una tabla con una columna donde estará colocado cada nodo descubierto por la ruta; para sacar los valores del nodo ocuparemos la función .value de la siguiente manera:

SELECT c.value('@ProductID', 'INT') AS ProductID,
    c.value('@Name'
, 'NVARCHAR(50)') AS Name
FROM @Doc.nodes('/subcategories/subcategory/products/product') AS T(c)


En este query podemos ver en uso la función .nodes para obtener los nodos que tengan la ruta XPath especificada y después ejecutar la función .value sobre cada nodo obtenido para sacar su información.    La función .value recibe dos parámetros, el primero es el elemento que quiero obtener del nodo actual y el segundo es el tipo de dato al que lo voy a convertir en la estructura entidad-relación.

El resultado de la ejecución de este query es el siguiente:



Podemos ver que tomamos todos los nodos product y los colocamos en una estructura entidad-relación.

Ahora vamos a ocupar un filtro para obtener los productos que pertenecen a la subcategoría Bib-Shorts.

SELECT c.value(
'@ProductID', 'INT') AS ProductID,
    c.value(
'@Name', 'NVARCHAR(50)') AS Name
FROM @Doc.nodes('/subcategories/subcategory[@Name="Bib-Shorts"]/products/product') AS T(c)

Podemos ver que lo que cambió fue la ruta XPath que se utilizó como parámetro para la función .nodes, hemos agregado unos corchetes para que filtre los nodos subcategory que cumplan con que su atributo Name sea Bib-Shorts.   A continuación muestro el resultado:


¿Qué pasa si nos piden que mostremos el nombre de la categoría en una tabla que tenga todos los productos del documento XML?, recordemos que tenemos el símbolo ".." para XPath, modifiquemos el query para mostrar lo que nos piden:

SELECT c.value('../../@Name', 'NVARCHAR(50)') AS Subcategory,
    c.value('@ProductID', 'INT') AS ProductID,
    c.value('@Name', 'NVARCHAR(50)') AS Name
FROM @Doc.nodes('/subcategories/subcategory/products/product') AS T(c)


Podemos ver que agregamos una columna más al query donde utilizamos el parámetro ".." para irnos moviendo del nodo actual product hacia el padre dos lugares arriba en la jerarquía del documento.   Aquí está el resultado:


En la entrada siguiente abordaré la función XML .exist y en la entrada posterior veremos la función .query.

Espero te haya sido de utilidad esta entrada.