El coste de los servicios en la nube

De una forma u otra estamos abocados a que la mayor parte de nuestra infraestructura y servicios terminen en la nube. Bajo mi punto de vista, esto es algo indiscutible, no porque nos guste más o menos, si no porque los costes decidirán finalmente que es más barato utilizar estos servicios que una estructura propia. Sin embargo, existen algunos factores que desconocemos para evaluar correctamente los costes de los servicios en la nube y éste es uno de los mayores problemas, de hecho, existe por así decirlo, cierta falta de transparencia, si oyes hablar a un comercial de venta de servicios en la nube, todo se convierte en ahorro de costes pero esto no es del todo verdad, en algunos casos es incluso al contrario.

Hace unos días estuve estudiando el coste de trasladar alguno de estos servicios a la Microsoft Azure, en concreto el relativo a SharePoint Online y Office 365, lo primero que llama la atención es la publicidad que se visualiza en la página.

clip_image002

Cualquiera que lea esto sin tener conocimientos suficientes podría llegar a pensar que una pequeña infraestructura sobre SharePoint para 10 usuarios podría costar 3,8 * 10 * 12 = 456 € al año y esto es completamente falso, no debemos olvidar que además del coste de los servicios de los que hablaremos más adelante, existen muchos otros que debemos seguir pagando, algunos como la administración que lejos de desaparecer se hace más importante, debemos entender que implantar un servicio como el expuesto en la imagen, va a requerir de unos servicios de configuración, administración, modelado, implantación, etc, que lógicamente no están incluidos en este precio, también hay que contar que necesitaremos seguir utilizando los equipos de clientes para acceder a la estos servicios, así que habrá que seguir manteniendo nuestro parque de pc´s, tablets, redes e infraestructura y algún que otro servidor para poder acceder y trabajar con estos.

Algunos costes se incrementaran, como los relacionados con el acceso a internet, en el que habitualmente tendremos que mejorar los caudales de subida y bajada para obtener mejores tiempos de respuesta, incluso integrar comunicaciones síncronas y esto no es barato. Hay que tener en cuenta que la velocidad en el trabajo normalmente será más lenta si lo comparamos con una red local, en el manejo de archivos pequeños no notaremos grandes diferencias, pero el trabajo con archivos grandes llevara más tiempo para abrirlos y modificarlos y estos tiempos tienen un coste en el trabajo diario de cada persona que habría que estimar, algunas veces este coste hará que el uso de alguno de estos servicios sea temporalmente inviable, algo que las mejoras de velocidad y tecnología permitirán con el tiempo, recordemos que el video bajo demanda de alta calidad empieza a ser una realidad y no hace mucho tiempo era impensable. Como pequeño ejemplo en nuestra empresa tenemos archivos de diseño 3d que superan los 200 megas, cada vez que quieres manejar uno de estos ficheros y dependiendo del caudal que tengas entre la empresa y el proveedor del servicio pueden pasar varios minutos solo en abrirlo y otros tantos cada vez que actualicemos su información, en estos casos quizás sea más adecuado no subir este tipo de archivos a la nube, pero entonces tendremos otro problema añadido, deberemos tener otro sistema que nos apoye en la gestión documental y ese es otro coste que debemos asumir.

Es necesario también tener en cuenta, como con cualquier otro software que habrá que destinar partidas para formación tanto en la parte de administración como la de usuarios para poder sacar partido a estos servicios, en el caso de SharePoint estos serán de vital importancia, pues podemos encontrarnos con muchos problemas, desde usuarios capaces de subir grandes ficheros que luego no podremos utilizar hasta que el sistema se bloquee porque hemos agotado la cuota de disco o el caudal de internet, así que aspectos como la formación, configuración, alertas, mantenimiento serán de vital importancia, algunos incluso más que si nuestra infraestructura fuera interna.

Existen algunas preguntas que tenemos que tratar de responder, como las relativas a los limites del caudal de nuestra conexión con internet y con mi proveedor, en este caso Microsoft, ¿Qué velocidad tengo cuando utilizo estos servicios?, ¿Cuál es el tamaño máximo de los archivos que puedo subir?, ¿Cómo he de trabajar con archivos grandes?,  Si necesito sincronizar muchos archivos con OneDrive, ¿Qué problemas de rendimiento voy a tener?, etc., muchas de ellas se contestan en la información del producto pero otras no, mi consejo aquí, además mejorar la formación, estudiar y leer todo lo posible es empezar con un proyecto pequeño y realizar pruebas reales, para ello Microsoft ofrece acceso a Azure de forma gratuita durante un mes y creo que es de vital importancia realizar varios test antes de realizar cualquier despliegue.

Una de las cosas que no me gustan mucho es esa falta de transparencia en algunos aspectos, por ejemplo en SharePoint Plan 1 te ofrecen 10 Gigabytes de tamaño base para una instalación normal y luego 500 megas por cada usuario, pero que pasa si este tamaño se sobrepasa y esto hay que tenerlo muy en cuenta, en este caso una instalación para 10 usuarios tendría una cuota de 15 Gigas, ya de entrada me choca bastante por lo poco que parece, sobre todo tratándose de un gestor documental.

Pues para resolver algunas de estas dudas llame a Microsoft, después de preguntar a varias personas me pasaron con los servicios facturación de Office 365 en donde me aclararon algunas de ellas, ante la pregunta de que sucede cuando se sobrepasa el limite de espacio contratado me dijeron que a partir de la base de 10 gigabytes mas 500 megas por usuario, el coste por gigabyte adicional es de 0.15 € por giga/ mes y aunque parezca poco, estimamos una instalación no muy grande digamos de un Tera de información correspondientes 1024 gigabytes, algo supongo que bastante habitual en un sistema de gestión documental, si ampliamos nuestra capacidad a un Tera nuestro coste aumenta considerablemente.

10 Usuarios * 3,80 € Usuario/Mes * 12 meses = 456 €

Con la licencia inicial para 10 usuarios tendría una capacidad limitada a 15 gigas correspondientes a 10 Gigas de la base + 500 por usuario y mes, es decir que me faltarían 1009 gigas que tendría que pagar adicionalmente y que supondrían:

1009 * 0.15 € * 12 = 1816,2 € al año por Tera, aquí ya la cosa cambia, y si utilizamos 2 Teras pues el doble, vamos que si tienes 100 usuarios y empiezas a subir información la sorpresa puede ser grande.

A mí me parece que este factor debería estar anunciado en la página principal y subrayado en rojo, ya he comenzado a oír algunas historias de sorpresas en algunas facturas sobre servicios en la nube.

 

Otra cosa que encontré leyendo sobre las características de los servicios en SharePoint Online y que me sorprendió un poco es que en la versión Plan 1 como la llaman no está incluida la versión OneDrive for Bussiness, se supone que es la herramienta adecuada para sincronizar archivos grandes con SharePoint entre otras cosas y esta se cobra aparte en este plan y cuesta otros 3,80 por usuario/mes, es decir que habría que sumar otros 456 € o contratar otro plan diferente que lo incluya.

10 Usuarios * 3,80 € Usuario/Mes * 12 meses = 456 €

En resumen que nuestro coste final se ha convertido en 456 € de licencias de SharePoint Online mas 1816 € por utilizar un Tera más otros 456 € de las licencias de OneDrive for Bussiness, ya estamos en 2724 €, a esto habría que sumarle los gastos por la infraestructura que no desaparece, como pc, tablets, infraestructura de red, así como todo lo derivado de la consultoría, administración, formación, implantación, etc.

Otros temas a tener en cuenta y que son muy importantes son las limitaciones en el tamaño de archivos, en este caso 250 megas, la limitación de subir archivos ciertos archivos, por ejemplo los que tienen extensión .exe, el número máximo de archivos por biblioteca, etc.

Debemos tener en cuenta también que el coste de estos servicios es por uso, es decir que si el primer mes solo utilizamos 100 Gigas solo pagaremos por estos y este se irán incrementando o decrementando dependiendo del uso que le demos, así que los cálculos anteriores no son del todo reales. Pero ya nos podemos ir dando cuenta de la importancia de utilizar planes de  mantenimiento, reglas, alertas, etc., para evitar pagar más de lo necesario, la administración de los sistemas en la nube al contrario de lo que pueda parecer, será de vital importancia para ahorrarnos costes. También es importante saber, que una de las ventajas que obtendremos es la versatilidad, podemos eliminar y añadir licencias en cualquier momento y esto es una gran ventaja, pues la filosofía sigue siendo paga por aquello que utilices, así pues si tenemos una punta de trabajo y necesitamos adquirir algunas licencias mas, lo haremos facilmente a golpe de ratón, lo mismo para eliminarlas.

Otro factor a tener en cuenta son los plantes de pago, Microsoft cobra un precio si contratamos el servicio de forma mensual y otras más barata de forma anual, si bien en ambos permite facturación mensual, esto es así porque los precios están en constante evolución y varían muy rápido, si contratas el plan anual el precio es menor, pero no sufre variaciones a lo largo de los doce meses y si lo haces de forma mensual el precio es más caro, pero puede sufrir variaciones, como ejemplo no hace muchos los precios por giga de almacenamiento en SharePoint costaban muchísimo más, creo que más de un euro en lugar de los 0,15 céntimos que cuestan ahora.

Esta es una de las claves de los servicios en el futuro, estos costes van a continuar bajando considerablemente, sobre todo debido a la competencia en grandes empresas Microsoft, Amazon, Google, Oracle, Apple, DropBox y otros que están librando esta batalla en un negocio que se convertirá en el pilar fundamental de sus empresas durante muchos años, de hecho, creo que este fue uno de los principales motivos de la elección del actual presidente de Microsoft ya que él era el encargado del grupo de desarrollo de servicios para la nube.

Esta lucha continua por ganar usuarios y las mejoras tecnológicas constantes bajaran los precios durante los próximos años y harán que tarde o temprano la mayor parte de las empresas suban la mayor parte de sus infraestructuras a la nube, si bien estas no desaparecerán del todo y durante los próximos años la mayor parte de las soluciones serán híbridas.

Si hablamos de servicios como Sql Server en Azure la complejidad todavía es mayor, ya que te cobran por conceptos como transacciones, ancho de banda, redundancia, cache, etc.

Una herramienta útil para calcular los costes esta publicada desde hace algún tiempo es http://azure.microsoft.com/en-us/pricing/calculator/ y facilita bastante el proceso, existen también calculadoras de proveedores de servicios externos. Sin embargos todavía hay algunos servicios que no están suficientemente claros y debemos leer toda la letra pequeña si no queremos llevarnos sorpresas en la factura.

A modo de resumen, adjunto un pequeño informe que utilice para poder tener una idea aproximada de algunos servicios en la nube, seguro que puede existir otras combinaciones y quizás pudiera faltar o sobrar alguna cosa, pero os servirá para daros una idea, los precios no llevan incluido el IVA.

Versión SharePoint Online Plan 1 (Incluye solo SharePoint, OneDrive tiene cargo adicional)

No incluye licencias de Office online ni Office versión escritorio ni OneDrive para manejar archivos grandes que tiene un coste adicional por Usuario/Mes.

Se podría eliminar el servidor de SharePoint y su mantenimiento

Las diferencias entre Sharepoint Plan 1 y Plan 2 son en funcionalidad, el plan dos tiene enlaces con base de datos, búsquedas avanzadas, etc

10 usuarios con 15 Gb de capacidad en SharePoint: 480 € al año

20 usuarios y 425 Gb de capacidad en SharePoint: 960 € + (10 usuarios * 3,80 Usuario/mes de OneDrive) + 720 € almacenamiento adicional 400 Gb = 2136 € al año

Versión Office 365 Enterprise E1 (Incluye servidor de Correo, SharePoint, Yammer, OneDrive, Office Online)

Incluye las licencias de Office Online pero no incluye licencias de Office versión escritorio.

Se podría eliminar el servidor de Exchange, SharePoint y su mantenimiento. No incluye las versiones de Office 2013 en equipos Windows.

10 usuarios: 732 € al año con 15 Gb de capacidad en SharePoint

20 usuarios y 425 Gb de capacidad en SharePoint: 1464 € + 720 € almacenamiento adicional 400 Gb = 2184 € al año.

Una buena oferta en relación calidad/precio, pues incluye One Drive y Office Online y los servicios de correo.

Versión Office 365 Enterprise E4 (Incluye servidor de Correo, SharePoint, Yammer y licencias de Office Online y versión de escritorio para equipos). La más completa

Se podría eliminar el servidor de Exchange, SharePoint y su mantenimiento.

Incluye todas las versiones de Office Online y Office versión escritorio.

10 usuarios con 15 Gb de capacidad en SharePoint: 2400 € año

20 usuarios y 425 Gb de capacidad en SharePoint: 4800 € + 720 € almacenamiento adicional = 5520 € al año.

Recordar que los precios y planes están en constante evolución y en poco tiempo estos ejemplos pueden no servir de nada.

Es una pena que el la idea del pago por uso no sea real, pues continúan
obligando al pago de una licencia de software por usuario, sin embargo sería más
adecuado pagar solo cuando se utiliza el servicio, pueden existir usuarios que
no utilicen el sistema más que unas horas al año y otros que lo hagan de forma extensiva,
de forma similar, muchos usuarios de Office 365, solo utilizaran Word y Excel y
solo algunos pocos utilizaran las otras herramientas, así que el pago de la
licencia sigue siendo desigual, sin embargo el pago por almacenamiento me parece mas adecuado pues pagas solamente por lo que estas usando.

Los servicios en la nube son una oportunidad de negocio para muchas empresas y están aquí para quedarse, pero debernos estudiarlos a fondo antes de realizar cualquier implantación, recordar que en el caso de Microsoft se proporcionan versiones de prueba gratuitas http://azure.microsoft.com/en-us/pricing/free-trial/ y podemos evaluar mejor el coste real de estos servicios, otra posibilidad es utilizar el crédito mensual gratuito que nos ofrece la licencia de MSDN para desarrolladores.

EF Code First 6.1 – Estúdialo bien antes de usarlo

Últimamente he estado trabajando parar intentar migrar nuestros modelos de datos a EF, la razón principal es que buscamos ser más productivos en desarrollo, reducir nuestro número de procedimientos almacenados y vistas porque su coste de mantenimiento comienza a ser bastante alto. 

Después de estudiar como se comportaba el modelo tradicional de EF con Edmx con modelos de más de 600 tablas, decidí que una aproximación mas adecuada seria utilizar EF Code First, la simplicidad, los metadatos escritos con Fluent Api, la posibilidad de realizar migraciones y aparentemente un rendimiento mayor, fueron algunas de las razonas para seleccionar esta tecnología. En Code First los metadatos de las entidades y toda la información sobre sus relaciones están escritas directamente en nuestras clases C#, así que comencé a pelearme durante algunos días con las plantillas T4 para realizar algunos cambios que requerían nuestras entidades, acceder a los metatados en Code First es mucho más sencillo que hacerlo sobre un modelo tradicional basado en un fichero Edmx, de hecho en el este último hay datos como el nombre del esquema de la tabla y el nombre real de los campos que no pueden ser consultados si no es a través de funciones propias a la base de datos o la lectura directa del fichero de configuración Edmx en formato xml. En nuestro caso necesitaba generar un enumerador en cada entidad con los nombres de los campos de las tablas y el nombre real de la tabla con su esquema. 

General.Divisas

public enum Fields
{
Codigo,Descripcion,Name,Simbolo,Pais,Grafico,Observaciones
}

Algo inicialmente sencillo se complica un poco con aspectos como la pluralización y algunos cambios en los nombres de los campos que EF genera y a los que debemos prestar atención. La pluralización varia el nombre de algunas entidades y campos, si tienes una tabla llamada ‘clientes’ y un campo llamado ‘cliente’, la entidad pasa a llamarse cliente, el dbset clientes y el campo ‘cliente1’, así que hay que prestar atención si en algún momento queremos hacer cambios sin contar con el contexto de EF, lo bueno es que una vez generado, no cuesta mucho Refactorizar algunas entidades si queremos solucionar estos problemas, recordar que en EF Code First partimos de la base de que el modelo es escrito por nosotros. De momento la pluralización en Code First no se puede anular como en el modelo tradicional cuando lo generas a partir de la base de datos por primera vez, tampoco se puede generar el código de una o varias entidades previamente seleccionadas. Un pequeño truco para lograr esto es utilizar un login únicamente con permisos de acceso a las tablas que queramos generar.

La tabla anterior genera la entidad siguiente, o veis cambia el nombre del campo Test

namespace Entities.EntityFramework

{

using System;

using System.Collections.Generic;

public partial class Test

{
    public int Test1 { get; set; }
    public System.DateTime Fecha { get; set; }
    public string Zona { get; set; }
    public Nullable<System.DateTime> Fecha_alta { get; set; }
    public System.DateTime Fecha_modificacion { get; set; }
    public string Description { get; set; }
    public double Precio { get; set; }
    public decimal Cantidad { get; set; }
    public Nullable<double> Total { get; set; }
    public string Observaciones { get; set; }
    public byte[] Foto { get; set; }
}

}

Otros problemas surgen en las vistas, EF quiere que todos los registros tengan un campo clave para poder diferenciar cada uno, en vistas actualizables tiene sentido pero en vistas que solo realizan consultas no, cuando EF genera las entidades de las vistas establece la mayoría de los campos no nulos como campos claves, así que aplique la solución de añadir un rowId a cada vista y utilizar NullIf para identificar los campos que no quieras que sean claves, vamos un coñazo que no sé hasta qué punto tiene sentido, pero no quiero ver esos warnings en amarillo, la otra solución de cambiar el mapeo de las vistas a mano es también otra tarea tediosa y si vuelves a generar el modelo, lo deberás hacer de nuevo.

ISNULL(ROW_NUMBER() OVER (Order By fieldList),-1) as RowID, Nullif(Nombre,»)

Al menos casi todas mis vistas tienen un campo clave :), otras por temas de rendimiento no pude modificar, hay que analizar en detalle cada una, por los problemas de rendimiento que esto pueda ocasionar, sobre todo en aquellas que utilizan unión, recursividad y otros aspectos.

Surgieron también otros problemas con las relaciones, que aún hoy no he logrado explicarme bien y seguro que tienen algún motivo lógico en EF, pero tener que cambiar tu modelo de base de datos para que tu modelo de EF funcione bien no sé si tiene mucho sentido, por ejemplo relaciones de una tabla con solo dos campos en las que cada uno tiene relación con una tabla, provoca problemas al generar las relaciones, aunque hay alguna regla al respecto que dice que las claves primarias de una tabla no deberían relacionarse con ninguna otra.

Nuestro sistema trabaja con diferentes bases de datos, la mayor de mas de 700 entidades, la segunda con unas 70 y algunas otras con menos, así que definí un proyecto independiente para cada contexto con las mismas plantillas T4 para que las entidades que se generasen fueran iguales.

Después de resolver estas incidencias, comienzo a realizar algunos test, una simple consulta a la base de datos sobre el modelo mas grande que devuelve un solo valor y la primera sorpresa, nada más y nada menos que 65 segundos, comienzo a leer algunos artículos sobre bajo rendimiento de EF Code First en la primera query, al parecer el sistema, lo que hace es leer los metadatos de las clases de mapeo, luego genera un fichero Edmx internamente para guardar compatibilidad hacia atrás con EF, posteriormente lo compila y comienza a funcionar igual que EF tradicional, eso si esto se toma la friolera de 65 segundos para hacer todo esto y alguna cosilla mas, en mi maquina un I7 con 4 nucleos, 8 gigas de ram y un disco SSD, vamos , de asustar…

Utilizando el profiler de .net me di cuenta de que el problema estaba en el motor de EF ya que la penalización viene de una la linea similar a esta:

var metadata = ((IObjectContextAdapter) context).ObjectContext.MetadataWorkspace;

En ella EF carga todos los metadatos de las entidades, relaciones, campos compuestos, etc. así que en un principio no me queda mucho que hacer si no investigar un poco al respecto, de todos los artículos que he leído, os aseguro que no me quedan muchos, voy a destacar tres de ellos que os pueden ayudar un minimizar parte del problema. El primer blog de indispensable lectura para todos aquellos que trabajen con EF. Os aconsejo su lectura para entender el artículo al completo.

http://romiller.com/category/entity-framework/
http://www.fusonic.net/en/blog/2014/07/09/three-steps-for-fast-entityframework-6.1-first-query-performance/
http://www.dotnet-tricks.com/Tutorial/entityframework/J8bO140912-Tips-to-improve-Entity-Framework-Performance.html

De todas las ideas que se comentan aquí, la compilación de vistas proporciona algunos segundos de mejora, pero que queréis que os diga, sigue siendo una chapuza, cada vez que modificas algo en el modelo, tienes que volver a regenerar las vistas y lo cierto es que en nuestro modelo el rendimiento de unos pocos segundos más con los tiempos de los que hablamos apenas se notan.

Utilizar ngen con EntityFramework.SqlServer.dll en el GAC, también ahorra algunos segundos, pero tener que integrarlo en cada equipo cada vez que salga una versión de EF también tiene su tela. En resumen algunas mejoras pero que complican el trabajo con EF Code First.

Así que se me ocurrió la idea de analizar exactamente las necesidades básicas de mi modelo, lo cierto es que los campos compuestos y relaciones para acceder a los datos, hacen que la escritura de las consultas en EF sea más cómoda, pero podía prescindir fácilmente escribiendo las relaciones yo mismo tal y como lo hago en Sql Server, ya sé que esto rompe con el modelo EF Code First y alguno pondrá el grito en el cielo, pues cuando haga una migración estas no se generan, pero no se me ocurría otra manera que aumentar el rendimiento, así que a matar pulgas a cañonazos….

Con el replace de Visual Studio y con ayuda de las expresiones regulares, voila, me cargue todo el código relativo a relaciones y campos de referencia a otras entidades.

Y por supuesto después de eliminar tanto código, logre una mejora sustancial, el tiempo bajo a los 32 segundos desde los 65, que le voy a hacer, tengo un montón de relaciones con integridad referencial, continúe y genere las vistas compiladas y metí la librería de Entity Framework en el GAC y logre bajar a la friolera de 14 segundos, pero aquí llegue a un punto de no retorno, ya no sabía cómo podía reducir mas el tiempo, con ayuda de Resharper me puse a buscar entidades no utilizadas y me encontré unas cuantas sobre todo derivadas de vistas que ya no se utilizaban, así que reduje mi modelo en casi 40 entidades más, no hay mal que por bien no venga y lógicamente, mejoro un par de segundos más hasta los 12 segundos.

Realizo otra prueba en un equipo virtual con menos memoria sin disco duro SSD para ver cómo se comporta el sistema de nuevo el tiempo se incrementa hasta los 25 segundos, algo que no entiendo muy bien, pues los dos procesadores no pasan de un 25% en el proceso de carga, pero bueno, en este punto, casi que prefiero no saber lo que EF estará haciendo internamente, como soy muy positivo comienzo a pensar que los equipos que utilizamos son mejores, incorporan procesadores de 64 bits y 8 gigas de Ram y como el tiempo es solo en la carga inicial del sistema, decido asumirlo temporalmente hasta ver si el equipo de desarrollo de EF proporciona alguna solución, así que el miércoles por la noche lo pongo en producción.

A las 8 de la mañana ya tenía un par de mensajes de algunos usuarios para entrar en la aplicación tienen una demora de más de 2 minutos, incluso para abrir un formulario después de haber cargado los metadatos, la apertura de los contextos que apenas penaliza en mi equipo en la de ellos si lo hace, la única diferencia es que ellos utilizan Windows 7 64 bits y yo Windows 8 64, pues aunque parezca increíble sucede esto, compruebo que ambos tienen instalado .net Framework 4.5.1, lo curioso es que mientras en Windows 8 cargo el sistema en 14 segundos, la misma prueba en Windows 7 64 en un equipo sin SSD tarda 22 minutos!!!!!!, así que no me queda mas remedio que buscar alguna solución, se me ocurrió grabar los metadatos de cada contexto en una tabla de la base de datos y cargarlos desde ahí, por supuesto con un DataReader sin contextos ni nada que ponga EF, los metadatos los almaceno en un campo de formato Xml y los cargo al inicio de la aplicación, el modelo completo de más de 800 entidades de contextos diferentes son cargados en menos de dos segundos, evitando hacerlo desde EF Code First, no me queda más remedio que volver a utilizar mis entidades poco y no usar los contextos de EF Code First hasta encontrar una solución a estos problemas de rendimiento.

Cada entidad del modelo almacena la información de cada entidad en un campo XML, como ejemplo:

La parte positiva es que mis entidades, vistas de sql y otros aspectos han sido preparadas para en un futuro, poder utilizar EF Code First, (el que no se conforma es porque no quiere….),

Espero que en algún momento corrijan estos problemas, algo que empiezo a dudar, pues buscando información he encontrado incidencias sobre el rendimiento con modelos de muchas entidades desde el 2011, como ejemplo http://social.msdn.microsoft.com/Forums/en-US/d6edb32d-8479-4a0d-8dc0-caa50f181e5d/extremely-slow-performance-using-codefirst-with-entity-framework-41-release?forum=adodotnetentityframework, así que pondré una vela a San Judas Tadeo…

Sobre los metadatos de EF en el modelo Edmx, todo hay que decirlo, es una auténtica aberración, hasta Rowan Miller Product Manager de EF lo reconoce en algunas de las incidencias en codeplex, pero están trabajando para mejorar esto, pienso que Code First debería ser en parte un modelo desacoplado del actual Entity FrameWork, pues si tiene que generar un archivo edmx desde las clases de mapeo nunca aprovechara todo su potencial, hace algunos años hice algunas pruebas de concepto para cargar los metadatos de esta manera y los resultados son espectaculares, es una pena no poderle sacarle partido.

No entiendo como una tecnología de acceso a datos genere tantos problemas con modelos de muchas entidades sobre todo ahora que las necesidades de las bases de datos son cada vez mayores, quizás es que la mayor parte de la gente trabaja con modelos que no exceden las 100 tablas y no se preocupan de analizar modelos mayores, personalmente sigo pensando que la idea de EF Code First es excelente, desgraciadamente les queda bastante trabajo todavía, sobre todo para modelos este.

Tengo alguna tentación para hacer algún comentario en numerosos post que hablan sobre las bondades de EF Code First, como en http://www.itworld.com/development/405005/3-reasons-use-code-first-design-entity-framework, pero voy a contenerme.. :), pese a todo confió en que algún día solucionen estos problemas y confió en que pronto EF Code First sea una apuesta de futuro.

Lo cierto es que el equipo de Entity Framework sobre todo Rowan Miller ha contestado a todas y cada una de las dudas e incidencias que he comentado, de hecho han asignado a una persona del equipo de EF para analizar nuestro modelo y ver si podemos optimizarlo de alguna forma, el soporte técnico ha sido excelente.

Mi consejo es que si utilizas modelos pequeños de menos de 100 entidades sin muchas relaciones EF Code First puede ser una buena alternativa, pasando de aquí hay que estudiar en detalle los problemas de rendimiento que se puedan producir. Espero que en la versión 7 se corrijan algunos de estos.

 

Maldivas – Implantación

Hace ya casi un año que Maldivas comenzó a funcionar, me gustaría deciros que todo el proyecto ha sido un camino de rosas y que todo ha funcionado a la perfección, sin embargo en estos últimos años hemos luchado lo indecible para sacar este proyecto adelante.

Por el camino y a pesar del conocimiento de muchos de los problemas que se producen en el desarrollo de un proyecto y aunque no es intención de este post buscar responsabilidades, si me gustaría comentar que hemos vuelto a cometer muchos de los errores que se pueden realizar en una gestión de proyectos (si no todos), estimaciones erróneas realizadas por personas ajenas al desarrollo, constantes interrupciones, múltiples cambios de contexto, problemas con la arquitectura y con las herramientas de trabajo, problemas con el equipo de desarrollo, decisiones como dejar de lado la calidad para intentar hacerlo más rápido, muchas horas de trabajo sin descanso, etc.

Maldivas 1

Maldivas – Erp

La parte positiva es que hemos aprendido de primera mano la importancia de una buena gestión de proyectos, de la utilización de pruebas unitarias, de la aplicación de reglas con herramientas como FxCop y ReSharper que tanto nos han ayudado, de la utilización de algunos patrones de diseño, de la importancia de las reglas de estilo en un equipo de trabajo, de la aplicación de metodologías de trabajo, de la automatización de procesos, de las ventajas de una formación continua y sobre todo, de la importancia de la colaboración entre todas las personas que participan en el proyecto.

No tiene sentido hablar del éxito o del fracaso del proyecto, ya que en este apartado y aunque me pese en cierto sentido, son los usuarios los que han de responder a esta cuestión, también aquí hemos tenido diversidad de opiniones, desde los que dicen que es una maravilla respecto al sistema anterior, algunos que no notaron ningún cambio o aquellos que argumentan que a pesar de las mejoras prefieren sin lugar a dudas el sistema de gestión antiguo. Todas las opiniones son válidas y debemos valorarlas, es importante entender que a lo largo de un proceso de desarrollo, todos cometemos errores y estos deben ayudarnos a mejorar, recuerdo el Sprint Retrospective basado en la mejora continua.

Un aspecto que si he aprendido en este proyecto es el relativo al equipo de trabajo, casi siempre diferenciamos el equipo de desarrollo (‘los raritos y frikis de la empresa’) de los demás, pero aquí más que en ningún otro proyecto en los que he trabajado, he aprendido la importancia que tiene el hacer partícipes a todas las personas del proyecto desde el inicio y que quizás, es mejor pararse o no hacer algo si no contamos con el suficiente apoyo e implicación de estos. Si alguien piensa que tener un buen equipo de desarrollo es garantía de éxito en un  proyecto es que no tiene ni idea de lo que está hablando, el cliente es parte indivisible del equipo, podemos tener un equipo de desarrollo de nivel medio y un cliente que colabore con nosotros y nos aporte feedback para mejorarlo, si tuviera que elegir apostaría siempre por lo segundo. Sin colaboración no hay proyecto posible, da igual lo buenos o malos que sean los desarrolladores, será un fracaso total, al menos hasta el día que estos puedan leer la mente del cliente, que pensándolo bien, quizás con mi Kinetic y aplicando cierta técnica Jedail todo es posible…

Maldivas - Stocks

Maldivas – Erp

Respecto a los usuarios, muchos realizaron su trabajo correctamente lo que nos permitió anticiparnos y resolver los problemas al finalizar cada entrega, mucho antes de la puesta en funcionamiento del sistema, de esta forma su transición fue mucho mejor, sus problemas minimizados y algunos mejorados porque nos aportaron Feedback, los costes fueron menores pues los abordamos en el momento adecuado. Sin embargo, otros usuarios alegaban tener una carga de trabajo alta, lo que les impedía destinar tiempo a comprobar y testar sus aplicaciones al finalizar cada sprint, algunos por diversos motivos creían que sus atribuciones no eran la de testar los módulos con los que iban a trabajar y otros pocos simplemente pasaban de hacerlo. Por supuesto para este segundo grupo, la transición al nuevo sistema fue mucho peor, corrijo, ‘para nosotros fue mucho peor’, ya que tuvimos que trabajar más horas en corregir aquellas incidencias no detectadas para que estos pudieran realizar su trabajo a tiempo, ha veces tuvimos que modificar ciertas reglas de negocio que afectaban a muchos módulos relacionados y tuvimos que abordarlas en un tiempo muy limitado.

Un aspecto en el que también sufrimos fue la migración de datos que comenzamos al inicio del desarrollo utilizando Data Transformation Services y que finalmente tuvimos que abandonar para utilizar scripts ya que las reglas de integridad y los cambios constantes en las estructuras hicieron de la migración un problema muy complejo que abordar. Finalmente conseguimos realizar una actualización completa de un sistema a otro, que pudimos ejecutar varias veces hasta poner en marcha el proyecto, esto mejoro también el testeo de los usuarios ya que podrían hacerlo con datos reales un factor importante a tener en cuenta en futuros desarrollos, logramos hacer la migración completa sin que ningún usuario introdujese ningún dato, todo un logro para un sistema con más de 500 tablas.

Maldivas - Proformas

Maldivas – Erp

Sigo pensado que la presión en el trabajo no hace que este mejore sino todo lo contrario, nos hace equivocarnos y cometer más errores. Creo que este debe ser un aspecto a reflexionar con detenimiento, algo que también he observado aplicando Scrum y es la presión que ha veces se ejerce al finalizar cada Sprint, a veces, si la estimación no es adecuada hace que esta aumente cuando el equipo de desarrollo trata de finalizar cada Sprint, es importante tratar de mejorar en las estimaciones para que estos cada vez se adapten mejor, aunque muchas veces cuando hacemos algo nuevo de lo que no tenemos información es ciertamente difícil acertar, debemos recordar que en el Sprint Review también se analizan aquellas tareas que por los motivos que sean no han podido ser finalizadas y trabajar en disminuir la presión del equipo de desarrollo, esta solo nos entorpece y bloquea.

Al final, mucha de la parafernalia de una gestión de proyectos ya sea utilizando Scrum, CMMI o cualquier otra metología se puede resumir en una sola palabra ‘confianza’, la mayor parte de lo que hacemos solo sirve para justificar ante nuestros clientes nuestro buen hacer, me pregunto, si tuviéramos confianza plena, ¿Sería necesario esforzarse tanto para tener una buena gestión de proyectos?, seguro que se pueden argumentar mil y una razones para hacerlo, que si queremos controlar su coste o conocer las desviaciones, que si los requerimientos deben quedar claros, etc., etc., pero no nos engañemos, la respuesta es ‘no’. Lo cierto es que los desarrolladores no ofrecemos demasiada confianza y esto es porque hay algo que todavía no hacemos bien. Estoy convencido de que la confianza de un cliente, debe basarse en entregas constantes de software, estas entregas son las que deben convencer al cliente, si no hay entregas, que más dan los análisis, la documentación y todo el trabajo que hayamos realizado, las entregas demuestran nuestro trabajo, hay algo que funciona y que hemos terminado, que a veces el cliente puede comenzar a amortizar y a utilizar mucho antes de que el proyecto finalice, que esta abierto a mejoras con la aportación del feedback, minimizando los costes al asumir cambios de forma temprana, que mejor demostración de nuestro trabajo que la entrega de software, aunque lo cierto es que también hay clientes complicados, pero bueno, el mundo no es perfecto.

Maldivas - Centros

Desde la puesta en marcha del proyecto, a pesar de las pruebas unitarias y del trabajo que hemos realizado por escribir código de calidad, se han resuelto más de 1600 incidencias y se han realizado unas 800 nuevas propuestas de mejora. Día a día continuamos progresando y adaptando el sistema a nuevas especificaciones. Debemos recordar que las pruebas unitarias no garantizan el correcto funcionamiento de un programa, existen muchos tipos de pruebas mas que debemos abordar para que una aplicación funcione correctamente, la aportación de la visión de los usuarios es siempre indispensable para asegurar un correcto funcionamiento, es muy difícil simular todo aquello que puede hacer un usuario con un programa y si encima es lunes o ha bebido un par de copas la cosa se torna imposible… 🙂

Hemos aplicado diversas tecnologías, como sabéis, nuestro modelo de datos está basado en entidades POCO, utilizando un data-mapper propio basado en generics y reflexión, lo cierto es que si hubiéramos contado con Entity FrameWork nos hubiéramos ahorrado un montón de trabajo pero así es la vida unas veces por delante y otras muy por detrás, pensar que un proyecto que nos ha llevado varios años y finaliza en Windows Forms, una tecnología actualmente en ‘desuso’, me entran hasta escalofríos de pensarlo, aunque supongo que en la mayoría de proyectos a largo plazo esto es así.

Maldivas - Capacidad

El desarrollo se comenzó en el 2006 con VS 2005 y se ha finalizado con VS 2010, aunque actualmente ya funciona con VS 2012, utilidades como FxCop, ReSharer, CodeRush, Stylecop, Pex and Molex, Visual Studio for Database Developers, nos han ayudado mucho a mejorar la calidad, como gestor de proyectos hemos utilizado TFS, hemos integrado la suite de controles de Devexpress, Sql Server como servidor de base de datos e IIS 7 como servidor web. Hemos implementado procesos en segundo plano utilizando programación asíncrona, paralelización, cache, Servicios Windows, Servicios web, Linq, Serialización de archivos, etc.

Maldivas - Planificacion

La aplicación tiene múltiples opciones Gestión de usuarios y permisos, enlaces con Office Outlook, Excel y Word, Gestor documental, Estructuras de fabricación, Gestión de órdenes de trabajo, Planificación en base a la demanda basada en sistemas Lean Manufacturing, , Multiempresa con bases de datos diferentes, Generación automática de asientos contables, Gestión de rutas, Enlaces bancarios norma 58 y Gestión de remesas de Confirming, Generación automática de asientos contables, Scheduler, Envió de información automatizado, Gestión de costes, Modulo de consultas y estadísticas, Diseño de Informes por usuarios avanzados, Gráficos interactivos, Cubos OLAP, Picking a través de dispositivos móviles, Comercio electrónico y muchas de las opciones que se integran en un ERP, desde el control de stocks, pasando por la Calidad, Compras, Ventas, Financiera, Recursos Humanos, etc.

Y para el futuro… pues Maldivas continuara creciendo y actualizándose, nos quedan muchas mejoras que realizar, intentaremos aumentar la cobertura de código con pruebas unitarias, integración con Sharepoint, desarrollo de parte del sistema de producción, aumento de las aplicaciones con Dispositivos móviles y porque no, quizás hasta alguna aplicación en Tablet, migración del sistema de comercio electrónico a ASP MVC, introducción de EF como modelo de entidades, creación de y adaptación de formularios en WPF, etc.

Aprovecho este post para dar las gracias especialmente a mi compañero Eduardo Obregon, que tanto me ha aguantado, sobre todo en los momentos difíciles de estos últimos años y que gracias a su calidad personal y trabajo ha contribuido a que este proyecto fuera posible, a los usuarios que nos han ayudado aportando Feedback y que han contribuido con sus ideas a mejorar este programa, a mi jefe que siempre me ha escuchado y con el que he podido discutir abiertamente de mis problemas y aquellas personas que siempre han confiado en que pese a los problemas y el tiempo invertido lograríamos realizar este proyecto del que más de una vez pensé en abandonar.

Bueno, si habéis llegado hasta aquí, (vaya rollo que os he contado), espero que al menos echéis un vistazo a unos videos que diseñamos para formación interna que os permitirán haceros una idea del proyecto. Todos los datos del video de formación son ficticios. Se realizaron pensando en que la mayor parte de los usuarios no disponía de tarjeta de sonido sin audio, pero tienen subtítulos que podéis activar desde el menú inferior de YouTube, también os aconsejo subir la resolución para visualizar correctamente el video, estos se han filmado en 1280 x 800 aunque la resolución óptima del programa es de 1440 x 900, así que algunos formularios se verán bastante comprimidos.

Video 1 – Maldivas – Características generales

La primera versión del sistema de terminales se desarrolló en el 2002 con Compaq Framework Beta 1, este ha sido adaptado para su integración con Maldivas

Video 2 – Maldivas – Terminales móviles

Video 3 – Maldivas – Terminales gestión

La primera versión del sistema de comercio electrónico se desarrolló en ASP.Net 2.0 en el año 2005 y actualmente estamos trabajando para su integración completa en Maldivas con ASP Mvc.

Video 4 – Maldivas – Comercio electrónico

Espero que os gusten y os aporten alguna idea en vuestros desarrollos.

 

Para adaptar la configuración en Youtube:

clip_image001

El icono amarillo muestra donde activar la resolución.

clip_image002
El icono amarillo muestra como activar los subtítulos.

Scrum – Comparte tu Backlog con el Cliente

Los mayores problemas que se producen en la gestión de proyectos se dan en el flujo de información, ocurre muchas veces que esta se tergiversa cuando pasa entre las diferentes personas que trabajan en el proyecto y al final se desarrolla algo que muchas veces no ofrece una solución para el problema presentado por el cliente. Esto hace que la relación con tu cliente se degrade y al final tu proyecto se demore haciendo que este tenga un mayor sobrecoste y perjudicando tu relación con el cliente.

Creo que todos conocéis la graciosa imagen que representa esto:

clip_image001

Un problema habitual se origina en el control de la entrada de información. Se dice, que normalmente debe existir una sola persona que trate con el cliente, yo pienso que esto es algo complicado de gestionar, sobre todo en grandes proyectos, en los que participan muchas personas, pues el cliente a veces, prefiere tratar con miembros del equipo de desarrollo con los que ha tenido mayor relación, con lo que la entrada de información puede originarse a través de los diferentes miembros del equipo y de esta forma las alteraciones con seguridad serán mayores, pues cada uno tendemos a interpretar la información a nuestra manera. Cuanto mas cercano se encuentra el equipo con respecto al cliente y mayor número de personas participan en el proyecto más difícil es controlar esta entrada de información.

Yo creo que la información debe introducirse solo por un sitio, y a ser posible sin interaccionar con más personas, para evitar filtros, modificaciones o interpretaciones personales y que esta sea fiel reflejo de los pensamientos del cliente, inclusive si esta presenta algún error para que posteriormente pueda ser analizada por todo el equipo. Pienso que debemos huir del pensamiento de que sea una sola persona la que hable con el cliente, que procese, filtre y finalmente traslade la información al equipo de desarrollo. Esto traslada toda la responsabilidad a una sola persona que puede cometer errores en la planificación y el diseño, al no contar con información relevante de otros miembros del equipo de desarrollo que seguramente conozcan a fondo áreas del cliente en las que han estado trabajando. Creo que deber ser el equipo al completo, el que analice la información y tome las decisiones en conjunto pues entre todos contaremos con una visión general mas completa para resolver el problema.

A veces el cliente llama directamente o la persona encargada del proyecto habla con el personalmente, el cliente traslada cualquier problema o necesidad con una prioridad determinada para resolverla. En este caso se producen varios problemas adicionales:

– Por un lado el cliente interrumpe el trabajo que estas haciendo y aquí se producen las mayores pérdidas de productividad, porque estas interrupciones nos obligan a veces a dejar tareas importantes. haciendo que la productividad decaiga en picado, no solamente se pierde el tiempo de la interrupción, si no que después obliga a que la persona tenga que volver a concentrarse para poder continuar el trabajo que estaban realizando. Debemos intentar eliminar estas interrupciones, cuando veo equipos de desarrollo en los que habitualmente hay una o dos personas hablando constantemente por teléfono suelo pensar que las cosas se están haciendo mal. Debemos eliminar estas interrupciones, las reuniones de scrum están para algo y debemos tratar de minimizar las conversaciones con el cliente, sobre todo aquellas que son por teléfono y que no quedan debidamente registradas o pueden sufrir diferentes interpretaciones y que además, afectan al rendimiento de las demás personas del equipo que se encuentra en el mismo entorno. Pienso que una buena solución aunque a algunos les suene un poco radical es simplemente descolgar el teléfono y bloquear las entradas de información como skype, en nuestro caso intentamos centrarnos solo en el email que nos permite estar al tanto de cualquier incidencia urgente que requiera una atención inmediata, las conversaciones con un cliente no aportan absolutamente nada, la información no queda registra y esta normalmente llega alterada al repositorio, lo que si puede aportar valor como veremos mas adelante es un diálogo con un cliente que quede registrado en el sistema y que no obligue a ninguna de las dos partes a interrumpir su trabajo.

– Puede ser además, que cuando el cliente llame le atienda una persona que no conozca a fondo su contexto de trabajo y de esta forma no sepa captar adecuadamente sus necesidades trasladando información incompleta o errónea al equipo de desarrollo.

– La información que nos traslada el cliente no queda registrada, con lo cual nunca sabremos si esta ha sufrido alguna alteración, es importante que todos los diálogos sobre un ítem determinado queden registrados y esta información sea accesible tanto por el propio cliente como por los miembros del equipo de desarrollo.

– Por otro lado a veces el cliente establece una prioridad determinada abduciendo que la necesidad es urgente y prioriza tu trabajo, de manera que esto nos obliga a dejar de lado aquello que estamos haciendo, para solucionar el problema lo antes posible, dejando aquello que teníamos planificado y que a veces puede tener una mayor prioridad.

– Muchas veces, parte del equipo de trabajo no cuenta con esta información a tiempo, ya que se comienza a desarrollar mucho antes de que el equipo pueda evaluar su solicitud. En estos casos a lo mejor no conocemos toda la información necesaria y ofrecemos una solución errónea debido a dependencias desconocidas y hacemos algo que debería haber sido correctamente planificado y estudiado.

Este y otros problemas son derivados del flujo de información y se convierte en una de las principales causas por la que muchos proyectos fracasan.

Una solución que a nosotros nos ha funcionado muy bien es la de intentar trasladar y compartir el backlog del proyecto con el cliente, con ello evitaremos la mayoría de estos problemas y lograremos varias ventajas:

– Se minimizan las interrupciones: Ya no tenemos que atender al cliente por teléfono o skype cada vez que nos llame, podemos visualizar las incidencias y requisitos que van llegando y pararnos solo si detectamos que estas requieren una atención inmediata, de esta forma podemos trabajar sin constantes interrupciones, el cliente también se beneficia del mismo sistema de trabajo, si tenemos que realizar alguna pregunta sobre un requisito en concreto utilizaremos el mismo sistema de dialogo con el, evitando interrumpirle en su trabajo y minimizando nuestro tiempo para contactar con el.

– Registro del flujo de información: Por un lado tanto el cliente como el equipo son los que escriben la información y esto queda registrado, con lo que evitaremos las alteraciones en la información cuando las filtra cualquier persona que hace de intermediario.

– Una mejor información disminuye los tiempos de desarrollo: Muchas veces una llamada no es suficiente para describir adecuadamente un problema, si el sistema de gestión del backlog permite adjuntar archivos, el cliente puede fácilmente introducir un documento, un gráfico o cualquier tipo de información que considere de interés para facilitar su comprensión, esto facilita mucho que la información este localizada en un lugar determinada y que el equipo entienda el problema adecuadamente evitando que perdamos el tiempo buscando o intentando entender mejor el proceso.

– Seguimiento del proceso: El cliente puede conocer en tiempo real la situación de su incidencia, mejora o el desarrollo de una nueva funcionalidad que haya solicitado, pudiendo además obtener información adicional, fecha de planificación, personas que lo desarrollaran, estado actual, etc, etc. Esto es muy importante pues evita constantes interrupciones con preguntas del tipo: ¿Ya has terminado aquello?, ¿Por qué no se ha hecho? ¿Qué problemas habéis tenido en el desarrollo?, de esta forma el cliente tiene constancia de las incidencias que ocurren, puede ocurrir que el desarrollo de un módulo tal y como propone el cliente afecte a otros módulos que el desconoce y es importante que si no se realiza como él ha solicitado pueda conozca las causas o los motivos por los que se ha realizado de otra manera.

– Por otro lado esta información va quedando almacenada en un histórico, de manera que otros usuario pueden acceder a esta información sobre problemas que ya tienen una respuesta determinada. Este debería ser un proceso que el cliente debe hacer antes de introducir una nueva incidencia, de manera que pueda beneficiarse de soluciones que ya hayan sido realizadas obteniendo una respuesta inmediata evitando requerir al equipo de trabajo.

– La información fluye hacia todos los miembros del equipo: Esto es fundamental en un equipo de desarrollo, algunos miembros del equipo desconocen como se han desarrollado otros módulos y las dependencias entre ellos, todo el equipo debe conocer el trabajo de los demás para así poder ofrecer la información relevante a otros miembros del equipo que la desconocen y lograr la solución mas adecuada.

– Filtrado de información: Normalmente no nos interesa compartir toda la información con toda la gente que participa del proyecto, nuestro sistema permite compartir solo aquellos ítems que deseemos con determinadas personas que trabajan en el cliente, de manera que el director financiero quizás no quiera visualizar información que solo es relevante para el responsable de nóminas, es importante que podamos controlar con quien queremos compartir esta información, no todas las personas que trabajan en el proyecto necesitan estar informadas de todos los aspectos de este. Con lo que logramos que la información fluya solo a aquellas personas relevantes evitando sobrecargar de información a otras que no la necesitan.

– El sistema de alerta nos avisa de cualquier incidencia urgente: Es importante que existan ciertos mecanismos que nos permitan notificar rápidamente una incidencia urgente, que se bloquee un servidor de BD requiere atención inmediata, nuestro sistema de alertas a través de email hace que estemos informados en tiempo real de cualquier incidencia grave y esto nos permite reaccionar con rapidez.

– El equipo de desarrollo planifica y prioriza: Somos nosotros los que debemos priorizar, no el cliente, el cliente no tiene toda la información relevante del desarrollo del proyecto y el equipo si, muchas veces ocurre que para el cliente es muy importante la realización de una tarea en concreto, pero en el proyecto pueden existir otras tareas que tengan una prioridad mayor, con un backlog compartido podemos cambiar la prioridad y notificar al cliente del porqué de estas decisiones, para justificar y hacerlo participe de nuestro trabajo.

Voy a poner un ejemplo de un ítem de un backlog para ver su funcionamiento, en este caso hemos implementado un pequeño programa para realizar esta operación, que notifica de forma automática a los usuarios a través del correo electrónico de cualquier cambio en el backlog, he leído en alguna parte que en la nueva versión del TFS, esto va a ser posible, seguro que El bruno puede avanzarnos algo sobre este tema.

1º – El cliente notifica un nuevo ítem.

Código: 4212 Fecha: 12/07/2012 9:12 Prioridad: Urgente

Usuario: Jose Angel García (Cliente – Agente comercial)

Descripción: Necesitamos que en el informe de facturación aparezca el número del agente de aduanas. Te adjunto un pdf con el informe de la factura para que veáis como debe quedar.

2 º – Hemos recibido su notificación con fecha 12/07/2012

3º – Respuesta de Ana Martínez (Responsable del proyecto) a la notificación nº 4212 el día 13/07/2012 10:00

La propuesta indicada será realizada por Julio Otero (Desarrollador).

Uno de los miembros del equipo comenta que las facturas pueden contener varios albaranes y estos podrían utilizar un agente de aduanas diferente, como procedemos en este caso.

4º – Respuesta de José Ángel García (Agente comercial) a la notificación nº 4212 el día 12/07/2012 11:45

Este caso no se puede dar, ya que antes de hacer la factura se comprueba que todos los albaranes tengan el mismo agente de aduanas, si existiesen albaranes con diferente agente se generarían varias facturas.

5º – Respuesta de Ana Martínez (Responsable del proyecto) a la notificación nº 4212 el día 12/07/2012 12:25

Bien, en este momento no podemos hacerla pues existen dos incidencias prioritarias que debemos resolver antes, cambio la fecha de planificación para el día 16/07/2012, para salir del paso podéis editar el PDF de la factura e introducirlo de forma manual.

6º – Respuesta de Julio Otero (Desarrollador) a la notificación nº 4212 el día 17/07/2012 10:25

La notificación 4212 ha sido resulta, te envió el pdf adjunto para que verifiques si todo esta correcto.

7º – Respuesta de José Ángel García (Agente comercial) a la notificación nº 4212 el día 17/07/2012 11:00

             Lo he verificado y funciona correctamente.

8º – Cerrada la notificación número 4212 por José Ángel García (Agente comercial)

De esta forma la información fluye sin que tengamos que controlar por donde entra y quien la procesa, ya que existe un solo punto de entrada (la aplicación que permite acceder a nuestro backlog), se disminuyen las interrupciones ya que no tenemos que hablar directamente con el cliente, la información sobre los cambios se distribuye de forma automática notificando al cliente por email de cualquier cambio que se produce, nos permite planificar y cambiar la prioridad de la tarea y estudiar mejor los cambios a aplicar, es el cliente quien escribe sus necesidades, y estas no se tergiversan, el registro guarda la información tal y como el cliente la ha introducido, el equipo trabaja como un conjunto en la planificación de tareas y la corrección de errores evitando errores en el diseño por el desconocimiento de las dependencias y ofrece entre todos sus miembros una solución mas adecuada y el sistema de alertas notifica en tiempo real de cualquier incidencia grave que pueda surgir para permitirnos reaccionar con rapidez.

He puesto Scrum porque es la metología que utilizamos, pero creo que este sistema de trabajo es trasladable cualquier metodología Ágil, si habéis llegado hasta aquí, espero vuestros comentarios.

Visual Studio 2012 – Primeras impresiones

Llevo ya varios días trabajando con la nueva versión de VS, lo cierto es que después de las últimas versiones, me esperaba mas de lo mismo, los mismos errores sin corregir versión tras versión, los cuelgues con el diseñador de formularios que se producían a diario, la carga eterna de los controles del toolbar, los enormes tiempos de compilación, etc, etc, nada mas lejos de la realidad, por fin, después de estos años parece que las cosas han empezado a cambiar, los desarrolladores somos un valor al alza, (me gustaría saber cuanta culpa tiene Google y Apple) en todo esto.

La primera vez que arrancas VS 2012 y observas el menú, ya te dan ganas de salir corriendo, vaya un diseño que han aplicado, colores grises, fuentes del menú principal en mayúscula, etc, he leído que se están acercando el diseño Metro, yo creo que el diseñador principal debería acudir a un par de desfiles de Victoria Secret, a ver si se le pega algo sobre la combinación de colores, aunque lo cierto es que después de unos pocos días de trabajo, el diseño es, con diferencia lo que menos importa, por mi, como si en la siguiente versión solo funciona en una pantalla de fosforo verde.

En lo que al entorno de trabajo se refiere, han corregido los errores de las versiones anteriores y han mejorado en gran medida el performance general de todas las operaciones, verificación de las reglas de fxcop, unit test, el diseño de los formularios ya no da errores constantemente debido a la carga en memoria de librerías, la carga del toolbar ya no te hace esperar varios minutos, algunos de nuestros proyectos tienen un gran número de controles y esto hace que cada vez que querías diseñar un formulario después de compilar se tuviera que regenerar de nuevo toda la tabla de controles, la carga de los proyectos se realiza en paralelo y en soluciones con muchos proyectos el tiempo de espera se reduce considerablemente, mi conexión remota al servidor de TFS utilizando una VPN a funcionado a la primera, por fin no pierdo las credenciales cada dos por tres, los tiempos de compilación han mejorado mucho, increíblemente no he encontrado ni un solo problema, he instalado Coderush y Resharper y funcionan de maravilla a pesar del aumento de los recursos que requieren.

En el desarrollo diario con Visual Studio 2010, os puedo asegurar que muchos de estos problemas suponían unas perdidas de tiempo enormes, hemos calculado que mas de una hora diaria por persona, esto supone para algunas empresas de desarrollo unas perdidas enormes, no entiendo porque no han corregido estos problemas hasta ahora, pero al menos lo han logrado en esta versión, ya era hora, en estos días no hemos tenido ni un solo problema trabajando con VS 2012, de hecho acabo de borrar de mi equipo la versión de VS 2010, a su favor diré que era mucho mas bonito…

Por fin, Entity FrameWork 5, permite realizar diferentes diagramas, al fin podremos trabajar en el diseñador con bases de datos que contiene muchas tablas, aunque los nombres de estas siguen sin incorporar el esquema de seguridad, algo con lo que sigo sin estar de acuerdo, no es lo mismo una tabla llamada dbo.Articulos que gestion.Articulos y en este caso EF llama a la primera Articulos y a la segunda Articulos1 y no muestra el nombre completo con su esquema, algo que para nosotros es importante, pues forma parte del esquema de seguridad de la tabla y siempre debe ser reflejado en las consultas.

Uno de los problemas que he visto y que no me ha gustado nada, es que después de actualizar uno de los proyectos a la versión 4.5 del FrameWork basado en Windows Forms, nos hemos dado cuenta que al instalar este en equipos que usen XP no funciona, en la información de Microsoft aparece que solo se da soporte a versiones que parten desde Windows Vista hacia adelante, parece que Microsoft quiere forzar a que los usuarios actualicen a Windows Vista o Windows 7, pienso que es un gran error no dar soporte a Windows XP, hay muchos equipos que funcionan muy bien con este S.O. y que no tienen ninguna necesidad de actualizarse o que su hardware no les permitirá hacerlo, pienso que deberían haberse esforzado en hacerlo compatible al menos para soluciones realizadas en Windows Forms, que todavía hoy siguen siendo la mayoría.

Sigo sin entender que pasa con el desarrollo de los dispositivos móviles industriales en Windows CE y Pocke PC, están abandonando un sector que tiene un potencial increíble, no he visto que Windows Phone se integre todavía con estos dispositivos, pero en fin, ya llegara alguien para hacerse con este hueco y luego llegaran las prisas como con Windows Phone y Windows 8, parece que algunos no reaccionan hasta que viene alguien y se hace con su mercado.

No he tenido tiempo de ver mucho mas, algún proyecto en ASP.NET y ASP MVC que han funcionado sin problemas, pero si os diré que en general, la primera impresión ha sido muy positiva, pienso que han realizado un excelente trabajo en esta versión, así que animaros Visual Studio 2012 supone un cambio enorme respecto a las versiones anteriores, solo las mejoras de rendimiento ya justifican el cambio, espero que continúen así.

Flexibilidad laboral

Me asombra ver como a día de hoy con la gran crisis en la que estamos inmersos, la mayoría de empresas siga optando por los métodos de control hacia sus empleados y no utilicen métodos para aumentar la flexibilidad de sus trabajadores, esta es una de las razones del porque España y otros países nos encontremos a la cola en lo que a productividad se refiere.

No puedo entender cómo, ya no las Empresas, si no algunos compañeros e incluso amigos, se asombren cuando les comentas que trabajas fuera de tu centro de trabajo, la mayoría llegan rápidamente a la conclusión de que esto es totalmente improductivo y se quedan perplejos ante este tipo de prácticas.

En mi caso, tengo la “suerte”, (parece como que me haya tocado la lotería…), de que mi Empresa me permite realizar este tipo de prácticas, es curioso como mi productividad y mi calidad de vida aumentan cuando trabajo fuera del entorno laboral, bajo mi punto de vista se producen varias ventajas:

Por un lado este planteamiento me permite eliminar los límites de horarios y tu trabajo se convierte en algo cotidiano y más cercano, dejas de ver el trabajo como una obligación con el que tienes que vivir.

Una de las mayores ventajas, es que el individuo se siente mucho más valorado pues entiende que hay una gran relación de confianza y de esta manera se siente más integrado con su entorno laboral.

Hay momentos en que uno está centrado y se encuenta cómodo trabajando más y otros sin embargo, en los que no tienes la cabeza para resolver un problema determinado, en este trabajo, muchas veces creativo, todos sabemos que hay momentos en el que podemos solucionar un problema al cabo de toda una semana y otros, en tan solo unas horas, la flexibilidad laboral nos permite adaptar nuestro estado y maximizar nuestra productividad. Comentaba Joel Spolsky en uno de sus artículos en los que analizaba sus ratios de productividad que entre reuniones, llamadas, aspectos burocráticos y otros factores, concluía que sus ratios de trabajo real estaban en torno a las 2 y 3 horas diarias, “en el caso de los Españoles lo tendríamos que dividir por PI…”,así que contamos con un gran marguen de mejora.

Supone un ahorro de costes para las dos partes, la empresa disminuye sus gastos en medios, energía, oficinas y otros factores y los empleados el gasto enorme en combustible, mantenimiento y amortización del vehículo.

Se produce un importante ahorro de tiempo en desplazamientos que podemos utilizar en otras tareas lo curioso es que hay gente que todos los días pierde dos o tres horas diarias en desplazamientos a sus centros de trabajo para realizar trabajos administrativos que fácilmente pueden estar sujetos al tele-trabajo.

Por otra parte, es sumamente importante maximizar nuestro tiempo libre, esto nos permite despejar la mente y disfrutar más de nuestra vida y se convierte en uno de los principales motivos para aumentar nuestra productividad, si llegamos contentos y despegados a nuestro trabajo, este se convierte en un elemento más de nuestra vida cotidiana y desaparece como tarea para convertirse en algo importante en nuestras vidas.

Las interrupciones disminuyen, en tu empresa, ya no te llaman para cualquier cosa sino para las que realmente te necesitan, ocurre muchas veces cuando te encuentras en el centro de trabajo que mucha gente demanda tus servicios para cosas sin importancia, solo por el hecho de que estas ahí y presuponen que para ti, solucionar un problema es más fácil que para ellos, así que prefieren llamarte a intentar solucionarlo por ellos mismos, esto aumenta las interrupciones en tu trabajo y te impide ser productivo. Si te encuentras fuera, te suelen llamar o convocar para aquello en lo que eres realmente necesario disminuyendo los recesos.

Para los empresarios se convierte también en un elemento que permite incentivar a sus trabajadores. Estos valoran mucho este tipo de prácticas, a veces muy por encima incluso de los incentivos dinerarios, para mi es un factor determinante a la hora de plantearte siquiera cambiar de trabajo, la flexibilidad laboral supone un arma muy potente a la hora de negociar un contrato laboral.

Pero con todo, también existen desventajas, no todos cumplimos con un perfil para trabajar en un entorno de este tipo y no todos los trabajos permiten la flexibilidad laboral, para ello se deben dar una serie de circunstancias, tu implicación con la empresa y tu trabajo debe ser alta, debes ser responsable e intentar cumplir con los objetivos, existen individuos que sin control no son capaces de trabajar en un entorno de flexibilidad o están habituados a trabajar en entornos controlados, cuando eliminas estas barreras lejos de mejorar la productividad, esta disminuye. Habría que preguntarse también si queremos contar con estas personas en nuestro entorno laboral, aunque a veces no queda más remedio.

Algunos individuos, cuando se encuentran lejos del entorno laboral se sienten en la obligación de trabajar a todas horas y esto, aunque cueste creerlo, al final también disminuye la productividad y empeora la calidad de vida, pues la presión del trabajo constante se va haciendo mayor, es muy importante aprender a que todas las actividades diferentes con tu familia, el deporte, los amigos, tus hobbies y por supuesto el trabajo se complementan ayudando a las personas a conseguir un equilibrio que posteriormente les permitirá abodar mejor los diferentes problemas de su vida laboral, debe existir un equilibrio en la vida de cada persona para poder sacar lo mejor de cada uno, algunos necesitaran más tiempo libre, otros en cambio necesitaran estudiar o trabajar más, es importante aprender a conocernos mejor para aumentar nuestro bienestar y consecuentemente nuestra productividad, esto es algo que han entendido a la perfección empresas como Microsoft, Google y otras, por algo son ellas las que mayores beneficios obtienen.

Entiendo que siempre debe existir cierto control y para ello contamos con infinidad de herramientas, debemos habituarnos a estimar e intentar cumplir con los objetivos marcados, en la mayor parte de los trabajos no utilizamos ninguno de estos métodos, no existe evaluación continua ni por objetivos. La utilización de metodologías puede ayudarnos mucho en este aspecto. En resumen, creo en el control, pero en el control intentado dotar al individuo de una mayor libertad y confianza para incentivarle y ayudar a mejorar su desarrollo profesional.

Creo que los empresarios, al menos yo cuando lo era, lo único que buscaba eran personas de confianza que resuelvan los trabajos que había que realizar, lo demás es completamente secundario. Las Empresas tienen la obligación de utilizar todos estos métodos para mejorar sus ratios de productividad y deben de empezar a comprender que aquellos basados en el “control” son completamente improductivos, y está siendo sobradamente demostrado por todos aquellos Países más avanzados que nosotros que utilizan este tipo de prácticas de forma natural y logran indices de productividad mayores. Debemos cambiar la forma de trabajar, pienso que muchos de los problemas existentes hoy en día, podrían resolverse fácilmente si todos fuésemos un poco más “flexibles”. Sigo creyendo que el verdadero valor de las empresas esta en las personas que las forman y será el conjunto de todas ellas las únicas que podrán hacer que estas tengan un futuro.

Productividad-forg

Quiero con este post animar a las Empresas para que utilicen estos métodos que por otra parte son “gratis”, como elementos que les permitan mejorar la productividad, la captación de mejores empleados y la mejora del ámbito laboral. También animaros a todos vosotros para que la flexibilidad se convierta en un elemento de negociación importante a la hora de trabajar para una empresa, no solo hay que negociar el sueldo, debemos luchar porque las empresas apliquen estos métodos que permitirán mejorar nuestra productividad.

Un saludo.

 

Pd. Es increíble que desde hace años las empleadas de líneas eróticas realizan el trabajo desde su casa. Curiosamente a nadie le choca este hecho…

Utilizando Arrays de parametros en Procedimientos Almacenados con Sql Server

Una característica muy solicitada por los desarrolladores en Sql Server es la de poder pasar varios valores a un procedimiento almacenado en forma de array. Desde la versión 2000 utilizaba una función de Sql que me permitía trabajar con Arrays a través de una cadena de texto delimitada por comas, normalmente los utilizaba en procesos de selección como en el ejemplo siguiente:

CREATE PROCEDURE GetCustomers @codes VARCHAR(MAX) AS
SELECT CustomerID, Name, Phone, Country
FROM Customers
WHERE CustomerID IN (SELECT * FROM [dbo].[ArrayToTable] (@param))

 
En este procedimiento el parametro @codes recibe una cadena delimitada por comas con los valores ’12,54,89,104,221,423,1239’, el sp devuelve todos los clientes con estos códigos, la definición del parametro ‘@codes’ se realiza con varchar(MAX), para que la cadena pueda almacenar la mayor cantidad de códigos posible, sin embargo varchar(MAX) tiene la limitación de 8000 caracteres y en algunas situaciones se nos puede quedar pequeño.
 
La definición de la función en Transact-Sql es la siguiente:
CREATE FUNCTION [dbo].[ArrayToTable] (@delimStr NVARCHAR(max))
RETURNS

@StrValTable TABLE
(
-- Add the column definitions for the TABLE variable here
StrVal VARCHAR(20)
)
AS
BEGIN
-- Fill the table variable with the rows for your result set
DECLARE @strlist NVARCHAR(max), @pos INT, @delim CHAR, @lstr NVARCHAR(max)
SET @strlist = ISNULL(@delimStr,'')
SET @delim = ','

WHILE ((len(@strlist) > 0) and (@strlist <> ''))
BEGIN
SET @pos = charindex(@delim, @strlist)

IF @pos > 0
BEGIN
SET @lstr = substring(@strlist, 1, @pos-1)
SET @strlist = ltrim(substring(@strlist,charindex(@delim, @strlist)+1, 8000))
END
ELSE
BEGIN
SET @lstr = @strlist
SET @strlist = ''
END

INSERT @StrValTable VALUES (@lstr)
END
RETURN
END

GO

Básicamente lo que hace es crear una tabla temporal conformada por un campo y cada valor en un único registro, de esta forma la consulta de arriba puede ser ejecutada sin problemas, hay que tener en cuenta que el uso de tablas temporales puede reducir el rendimiento, sin embargo es mejor esto a llamar al SP muchas veces. Podriamos tambien optar por pasar los arrays con XML y readaptar la función.

Sin embargo en la versión 2008 aparecen los User-Defined Table Type, que permiten definir la estructura de una tabla, que posteriormente puede ser utilizada para pasar valores a un SP.

clip_image001

Para el ejemplo partimos de la tabla siguiente:

clip_image002

La definición de este tipo de estructura es relativamente sencilla, similar a la creación de una tabla, el tipo anterior se definiría con la siguiente instrucción transact-sql

CREATE TYPE [dbo].[UsersTable] AS TABLE
(
[UserID] [int] NOT NULL,
[Postal] [char] NULL
PRIMARY KEY([UserID])
)
GO

Este nuevo tipo de estructura nos aporta varias ventajas, podemos definir la tabla con varios campos  y limitar el contenido del campo [UserID] utilizando primary key para que no se repitan los valores.

Una vez definida la estructura podemos utilizarla como un parámetro más.

La definición del SP que utiliza la nueva estructura es la siguiente:

CREATE PROCEDURE [dbo].[GetUsers] @users dbo.UsersTable READONLY
AS
SELECT * FROM Users WHERE UserID IN (SELECT userId from @users)

GO
 
El programa para llamar al SP desde c# podria ser similar al  ejemplo siguiente:
using System;

using System.Data;

using System.Data.SqlClient;

 

namespace ConsoleApplication1

{

    class Program

    {

        static void Main()

        {

            using (SqlConnection sqlConnection = new SqlConnection("Server=(local);Integrated Security=SSPI;Initial Catalog=Database"))

            {

                using (SqlCommand sc = new SqlCommand("dbo.GetUsers", sqlConnection))

                {

                    using (IDataReader idr = GetParams())

                    {

                        sc.Parameters.Add("@users", SqlDbType.Structured).Value = idr;

 

                        sqlConnection.Open();

                        if (sqlConnection.State == ConnectionState.Open)

                        {

                            sc.CommandType = CommandType.StoredProcedure;

                            using (SqlDataReader sqlDataReader = sc.ExecuteReader())

                            {

                                if (sqlDataReader != null && sqlDataReader.FieldCount > 0)

                                {

                                    while (sqlDataReader.Read())

                                    {

                                        Console.WriteLine(String.Format("Código {0}, Nombre {1}", sqlDataReader[0], sqlDataReader[1]));

                                    }

                                    Console.ReadLine();

                                }

                            }

                        }

                    }

                }

            }

        }

 

        public static DataTableReader GetParams()

        {

            using (DataTable dt = new DataTable("Prueba"))

            {

                dt.Columns.Add("UserID", typeof(int));

 

                DataRow dr1 = dt.NewRow();

                dr1["UserId"] = 3;

                dt.Rows.Add(dr1);

 

                DataRow dr2 = dt.NewRow();

                dr2["UserId"] = 1;

                dt.Rows.Add(dr2);

 

                DataRow dr3 = dt.NewRow();

                dr3["UserId"] = 4;

                dt.Rows.Add(dr3);

 

                dt.AcceptChanges();

 

                return dt.CreateDataReader();

            }

        }

    }

}

Limitaciones de los User Defined Table Type:

– No se pueden pasar parámetros table-valued a user-defined functions

– Las estructuras solo pueden ser indexadas para soportar valores únicos o primary keys. Sql server no mantiene estadísticas sobre parámetros table-valued.

– Los parámetros table-valued son de solo lectura en el código transact-sql, no se pueden actualizar valores de filas y no se pueden insertar datos.

– No se puede alterar o modificar el diseño de una table-valued, para conseguir esto se debe borrar y volver a crear la estructura.

No debemos olvidar que en Entity FrameWork este problema se resuelve de una forma mucho más sencilla sin la utilización de SP, como usuarios de EF debemos huir del uso de procedimientos almacenados siempre que podamos, ya que debemos abstraernos de la base de datos y habitualmente la perdida de rendimiento es inapreciable, sin embargo pueden existir algunas ocasiones en que el uso del Store Procedure esté justificado, si bien, la mayoría de las veces podemos resolverlo de forma muy sencilla tal y como demuestran este par de ejemplos.

int[] customerIds = new int[] { 1, 2, 3 };

var customers = from customer in context.CustomerSet
where customerIds.Contains(customer.Id)
select customer;

---------------------------------------------------------------------

string[] cities = new string[] { "New York", "London", "Seattle" };

var query = context.Orders.Where(c => cities.Contains(c.Customer.Address.City)).Select(o => o);

 
Y recordar, alegaros de algunas practicas, si bien funcionan, son fruto de muchos errores en las aplicaciones de hoy en dia, como muestra el siguente código:
 
SqlCommand("SELECT UserID, Name, Phone FROM Users WHERE UserID IN (2,4,5,24)", sqlConnection);

Constrúyete tu propio gestor documental

Uno de los programas más utilizados en nuestro sistema de gestión es el gestor documental, como todos sabéis Sharepoint se conforma entre otras cosas como un gestor de contenidos, que permite almacenar todo tipo de documentos, pero hay veces que buscamos una mayor integración en nuestros sistemas o que simplemente no queremos depender de Sharepoint. En estos casos, desarrollar un gestor documental puede ser una buena alternativa y el trabajo para construir este programa lejos de parecer complicado es relativamente sencillo.

El gestor que os propongo se basa en el mismo sistema que utiliza sharepoint, los ficheros se serializan, se encriptan si es necesario y finalmente se guardan en una tabla de una base de datos tal y como hace sharepoint, posteriormente se leen desde la base de datos y se guardan en una ruta local o se utilizan directamente, aprovechándonos del almacenamiento en la base de datos con todas las ventajas que esto conlleva, copias de seguridad, particionado de tablas, indexación de contenidos, etc.

El gestor que vamos a desarrollar debe tener capacidad para almacenar cualquier tipo de fichero, documento Word, hojas de cálculo, archivos pdf, archivos comprimidos, emails, jpgs, archivos de autocad, etc.

Para comenzar hemos creado una tabla llamada Documentación en una base de datos independiente, porque como supondréis esta puede llegar a ser muy grande y en un momento determinado quizás tengamos que alojarla o particionarla en otro servidor por escalabilidad,  tamaño o rendimiento.

clip_image002

El campo [documento] (varbinary(max)) será el encargado de almacenar los ficheros serializados.

Buscamos un método sencillo para poder subir los archivos a la base de datos, con lo que queremos arrastrar uno o varios ficheros a un contenedor para que la aplicación pueda traspasarlos posteriormente a la BD.

Para llevar a cabo esta operación, hemos creado un formulario con el control System.Windows.Forms.WebBrowser que hace referencia a una ruta temporal en el disco, en este caso hemos mapeado una unidad temporal en nuestro PC ‘c:temporal’, aunque esta podria ser tambien una ruta compartida de red. La configuración de esta carpeta se realiza a través de la propiedad “Path” del control WebBrowser. Este control ya tiene por defecto implementados el sistema Drag & Drop, que nos permite arrastrar, copiar o pegar cualquier tipo de archivo de sistemas Windows, así podemos arrastrar varios correos desde Outlook, una hoja de cálculo de Excel o cualquier fichero simplemente utilizando el ratón, al realizar esta operación sobre los archivos seleccionados estos se mueven o se copian a la ruta temporal que hemos establecido, podemos además utilizar los atajos para copiar archivos Ctrl-c / Ctrl-v, etc.

El control se puede configurar para que muestre diferentes vistas, hemos configurado una que muestra el nombre de los archivos, su tipo, la fecha, el tamaño, esta vista se puede alterar cambiando las propiedades de la carpeta temporal.

Esta primera parte que parecía complicada queda resuelta facilmente sin apenas tener que escribir un par de lineas de código, correspondientes a la configuración del control Webbrowser.

clip_image004

Una vez hemos arrastrado los archivos y estos se copian automáticamente a la ruta temporal pasamos al proceso de verificación, en esta opción se nos permite asociar cada uno de los archivos a varios metadatos, de esta forma podemos añadir información adicional a cada elemento como una descripción más detallada, observaciones, usuario, fecha o cualquier información que nos parezca relevante.

clip_image006

Finalmente los ficheros se procesan uno a uno y se van traspasando a la base de datos, para realizar esta operación podéis utilizar Directory.GetFiles(path) que permite obtener los nombres de los archivos de una ubicación determinada y FileInfo(file) que permite obtener información detallada sobre cada uno de los ficheros, fecha de creación, tamaño, etc. No debemos olvidar guardar el nombre completo con la extensión para identificar su tipo cuando vayamos a utilizarlo. 

Con todos estos datos formados por los nombres de los ficheros, sus atributos y los metadatos que podemos variar conformamos una colección temporal, lógicamente estos se grabaran posteriormente en la base de datos con lo que tendrán que tener un campo asociado en la tabla que creamos inicialmente.

Para realizar las operaciones de serialización de un fichero utilizamos la siguiente función:

//Cargar en un array de bytes el documento.
using (FileStream archivoStream = new FileStream(file, FileMode.Open, FileAccess.Read))
{
    byte[] documento = new byte[(int) archivoStream.Length];
    archivoStream.Read(documento, 0, (int) archivoStream.Length);
}

Despues de realizar este paso, si deseamos encriptar el documento para mayor seguridad podriamos hacerlo en este punto, utilizando las librerias de .net o aprovechando Sql Server para realizar esta operación.

Posteriormente grabamos el archivo y todos los datos adjuntos al documento (Metadata) utilizando un Procedimiento almacenado.

cmdOrden.Parameters.Add("@documento", SqlDbType.VarBinary).Value = documento.Archivo;
cmdOrden.Parameters.Add("@descripcion_modulo", SqlDbType.NVarChar, 100).Value = documento.Descripcion_modulo;
cmdOrden.Parameters.Add("@descripcion", SqlDbType.NVarChar, 200).Value = documento.Descripcion;
cmdOrden.Parameters.Add("@ruta", SqlDbType.NVarChar, 200).Value = documento.Ruta;
cmdOrden.Parameters.Add("@archivo", SqlDbType.NVarChar, 200).Value = documento.Archivo;
cmdOrden.Parameters.Add("@modulo", SqlDbType.NVarChar, 50).Value = documento.Modulo;
cmdOrden.Parameters.Add("@codigoAuxiliar", SqlDbType.Int, 10).Value = documento.Codigo_auxiliar;
cmdOrden.Parameters.Add("@codigoAsociado", SqlDbType.Int, 10).Value = documento.Codigo_asociado;
cmdOrden.Parameters.Add("@usuario", SqlDbType.NChar, 5).Value = documento.Usuario;
cmdOrden.Parameters.Add("@tipo", SqlDbType.NVarChar, 100).Value = documento.Tipo;
cmdOrden.Parameters.Add("@observaciones", SqlDbType.NVarChar, 1000).Value = documento.Observaciones;
cmdOrden.Parameters.Add("@codigo", SqlDbType.Int, 10).Direction = ParameterDirection.Output;

De esta forma tan sencilla los documentos se traspasan a la base de datos.

Pensando que este proceso puede ser de varios ficheros y que además algunos pueden tener un tamaño considerable, hemos dotado al programa de un mecanismo asíncrono utilizando BackGroundWorked, de forma que si el proceso tarda un tiempo la aplicación nunca llegue a bloquearse y los usuarios puedan continuar realizando su trabajo mientras se trasladan los ficheros a la BD.

Una vez hemos procesado los ficheros, lo unicó que nos queda por hacer es explotarlos, para ello tenemos que realizar la operación inversa:

Creamos otro formulario, en nuestro caso hemos utilizado un control TreeView para poder visualizar los datos de forma jerarquica, habitualmente los documentos estan relacionados con un registro de una o varias tablas, en la foto de abajo se muestran varios registros relacionados con la ficha de un artículo. Si el usuario pulsa abrir, se lee el registro de la base de datos, se deserializa el contenido del campo [documento], se desencripta si fuera necesario y finalmente se guarda en una ruta temporal o se abre directamente en memoria. Para deserializar el documento utilizamos la siguiente función, donde [doc.Documento] hace referencia al campo de tipo byte[] de nuestra entidad y [fichero] a la ruta y nombre del archivo que se va a crear como por ejemplo ‘c:tempestadisticas.xls’.

using (FileStream archivoStream = new FileStream(fichero, FileMode.Create))
{
    archivoStream.Write(doc.Documento, 0, doc.Documento.Length);
    archivoStream.Close();
}

Finalmente abrimos el fichero, para realizar esta operación y que el programa relacionado (word, excel, outlook), realice esta operación automáticamente utilizamos la siguiente función.

if (File.Exists(fichero))
{
    Process process = new Process { StartInfo = { FileName = fichero } };
    process.Start();
}

 

clip_image008

Y voila, ya tenemos nuestro gestor documental. En nuestro caso para complementar la utilidad vamos a llamar a un webservice que nos permite almacenar el fichero también en sharepoint de manera que este accesible en los dos gestores, podemos además implementar filtros para realizar búsquedas no solo por los metadatos si no por el contenido de los ficheros, implementar un control de versiones, etc, etc., pero si continuamos al final lo que tendríamos es otro “Sharepoint”.

La ventaja de esta utilidad además de la integración con nuestro sistema y la facilidad de uso es que podemos asociar el contenido a nuestros registros de la base de datos y posteriormente visualizarlos o agruparlos en otro registro para facilitar el acceso a la información.

image

En nuestro caso cada registro de cada mantenimiento tiene accesible el gestor documental de manera que en el ejemplo de la foto posterior cada registro de artículos tiene asociados varios documentos, otra ventaja es que en ciertos mantenimientos podemos visualizar la información de forma jerárquica, por ejemplo en un cliente se verían de esta forma, con lo que acceder a la información es muy sencillo.

clip_image010

Así que animaros, construiros un pequeños gestor documental como este es muy sencillo, y permitira dotar a vuestras aplicaciones de una herramiena muy potente para centralizar y acceder a todo tipo de información.

Macro sustitución en C#

Una de las características de Visual Foxpro (mi anterior lenguaje antes de .net) y del que todos los días me acuerdo cuando me peleo con .net, era la utilización de macro sustitución, esta permitía realizar cosas como la siguiente:

   1: FOR i = 1 TO 12

   2:    xcam = 'Formas_pago.poraplaza' + ALLTRIM(STR(i))

   3:    IF &xcam > 0

   4:    ENDIF

   5: ENDFOR

De esta forma utilizando el símbolo &, podía evaluar el contenido de los campos sin tener que escribirlos uno a uno y ahorrando líneas de código.

Por desgracia c# no posee esta característica con un uso tan sencillo, se pueden realizar aproximaciones compilando el código al vuelo con codedoom o similares, pero personalmente evitaria utilizar estas técnicas si no es absolutamente necesario.

Ya metidos en harina, estaba en .net transcribiendo un bloque de código desde Fox y claro el pequeño programa de arriba daba lugar a un programa similar a este:

   1: /// Carga la configuración dependiente de la forma de pago correspondiente

   2: if (!string.IsNullOrEmpty(cvc.Forma_pago))

   3: {

   4:     using (ManagerBI<Formas_pago> manager = new ManagerBI<Formas_pago>() )

   5:     {

   6:         if (manager.GetRecord(cvc.Forma_pago))

   7:         {

   8:             cvc.PorcentajesAplazamientoAdd(manager.Data.Porcentaje_aplazamiento_1);

   9:             cvc.PorcentajesAplazamientoAdd(manager.Data.Porcentaje_aplazamiento_2);

  10:             cvc.PorcentajesAplazamientoAdd(manager.Data.Porcentaje_aplazamiento_3);

  11:             cvc.PorcentajesAplazamientoAdd(manager.Data.Porcentaje_aplazamiento_4);

  12:             cvc.PorcentajesAplazamientoAdd(manager.Data.Porcentaje_aplazamiento_5);

  13:             cvc.PorcentajesAplazamientoAdd(manager.Data.Porcentaje_aplazamiento_6);

  14:             cvc.PorcentajesAplazamientoAdd(manager.Data.Porcentaje_aplazamiento_7);

  15:             cvc.PorcentajesAplazamientoAdd(manager.Data.Porcentaje_aplazamiento_8);

  16:             cvc.PorcentajesAplazamientoAdd(manager.Data.Porcentaje_aplazamiento_9);

  17:             cvc.PorcentajesAplazamientoAdd(manager.Data.Porcentaje_aplazamiento_10);

  18:             cvc.PorcentajesAplazamientoAdd(manager.Data.Porcentaje_aplazamiento_11);

  19:             cvc.PorcentajesAplazamientoAdd(manager.Data.Porcentaje_aplazamiento_12);

  20:  

  21:             cvc.DiasPagoAdd(manager.Data.Dia_pago_1);

  22:             cvc.DiasPagoAdd(manager.Data.Dia_pago_2);

  23:             cvc.DiasPagoAdd(manager.Data.Dia_pago_3);

  24:             cvc.DiasPagoAdd(manager.Data.Dia_pago_4);

  25:             cvc.DiasPagoAdd(manager.Data.Dia_pago_5);

  26:             cvc.DiasPagoAdd(manager.Data.Dia_pago_6);

  27:             cvc.DiasPagoAdd(manager.Data.Dia_pago_7);

  28:             cvc.DiasPagoAdd(manager.Data.Dia_pago_8);

  29:             cvc.DiasPagoAdd(manager.Data.Dia_pago_9);

  30:             cvc.DiasPagoAdd(manager.Data.Dia_pago_10);

  31:             cvc.DiasPagoAdd(manager.Data.Dia_pago_11);

  32:             cvc.DiasPagoAdd(manager.Data.Dia_pago_12);

  33:  

  34:             cvc.DiasAplazamientoAdd(manager.Data.Dias_aplazamiento_1);

  35:             cvc.DiasAplazamientoAdd(manager.Data.Dias_aplazamiento_2);

  36:             cvc.DiasAplazamientoAdd(manager.Data.Dias_aplazamiento_3);

  37:             cvc.DiasAplazamientoAdd(manager.Data.Dias_aplazamiento_4);

  38:             cvc.DiasAplazamientoAdd(manager.Data.Dias_aplazamiento_5);

  39:             cvc.DiasAplazamientoAdd(manager.Data.Dias_aplazamiento_6);

  40:             cvc.DiasAplazamientoAdd(manager.Data.Dias_aplazamiento_7);

  41:             cvc.DiasAplazamientoAdd(manager.Data.Dias_aplazamiento_8);

  42:             cvc.DiasAplazamientoAdd(manager.Data.Dias_aplazamiento_9);

  43:             cvc.DiasAplazamientoAdd(manager.Data.Dias_aplazamiento_10);

  44:             cvc.DiasAplazamientoAdd(manager.Data.Dias_aplazamiento_11);

  45:             cvc.DiasAplazamientoAdd(manager.Data.Dias_aplazamiento_12);

  46:         }

  47:     }

  48: }

Qué horror, 36 líneas para hacer lo mismo, pero imaginar que en lugar de 36 campos tuviéramos 100. Se me ocurrió la idea de usar reflection para poder evitar la escritura de tantas  líneas y resulto relativamente fácil, la solución es interesante y en casos similares os permitirá reducir en gran medida vuestro código. El rendimiento de reflexión apenas penalizará la aplicación.

   1: /// Carga la configuración dependiente de la forma de pago correspondiente

   2: if (!string.IsNullOrEmpty(cvc.Forma_pago))

   3: {

   4:     using (ManagerBI<Formas_pago> manager = new ManagerBI<Formas_pago>() )

   5:     {

   6:         if (manager.GetRecord(cvc.Forma_pago))

   7:         {

   8:             Type t = manager.Data.GetType();

   9:  

  10:             for (int i = 1; i < 12; i++)

  11:             {

  12:                 string contador = i.ToString().Trim();

  13:                 decimal porcentajeAplazamiento = (decimal)t.InvokeMember("Porcentaje_aplazamiento_" + contador, BindingFlags.GetProperty, null, manager.Data, null, CultureInfo.CurrentCulture);

  14:                 cvc.PorcentajesAplazamientoAdd(porcentajeAplazamiento);

  15:                 byte diaPago = (byte)t.InvokeMember("Dia_pago_" + contador, BindingFlags.GetProperty, null, manager.Data, null, CultureInfo.CurrentCulture);

  16:                 cvc.DiasPagoAdd(diaPago);

  17:                 Int16 diasAplazamiento = (Int16)t.InvokeMember("Dias_aplazamiento_" + contador, BindingFlags.GetProperty, null, manager.Data, null, CultureInfo.CurrentCulture);

  18:                 cvc.DiasAplazamientoAdd(diasAplazamiento);

  19:             }

  20:         }

  21:     }

  22: }

Espero que os sirva.
 

Database Professionals 2010. Pruebas Unitarias de Bases de datos y materiales de la charla.

El otro día tuve el placer de compartir una charla sobre Visual Studio 2010 organizada por Juan Carlos Gonzalez del CIIN con Rodrigo Corral e Ibon Landa. Os dejo los materiales de la charla y un pequeño artículo sobre las diferentes pruebas de calidad en Base de datos comentado durante la presentación. Realice tres ejemplos, basados en un procedimiento almacenado que actualizaba un registro en la tabla Employee de AdventureWorks2008. Su definición es la siguiente:

 
CREATE PROCEDURE [HumanResources].[uspUpdateEmployeePersonalInfo]
    @BusinessEntityID [int], 
    @NationalIDNumber [nvarchar](15), 
    @BirthDate [datetime], 
    @MaritalStatus [nchar](1), 
    @Gender [nchar](1)
WITH EXECUTE AS CALLER
AS
BEGIN
    SET NOCOUNT ON;
 
    BEGIN TRY
        UPDATE [HumanResources].[Employee] 
        SET [NationalIDNumber] = @NationalIDNumber 
            ,[BirthDate] = @BirthDate 
            ,[MaritalStatus] = @MaritalStatus 
            ,[Gender] = @Gender 
        WHERE [BusinessEntityID] = @BusinessEntityID;
    END TRY
    BEGIN CATCH
        EXECUTE [dbo].[uspLogError];
    END CATCH;
END;

El ejemplo básico consistía simplemente en llamar al SP con unos datos ficticios que no realizaba ninguna actualización, esta prueba solo cubría que la llamada al SP se realizase de forma correcta.

El segundo ejemplo consistía en insertar un determinado registro antes de la prueba, llamar al SP para que realizase la actualización sobre ese registro y finalmente eliminar el registro de prueba insertado, esta prueba cubría que la llamada al SP se realizase de forma correcta y además permitía conocer si el sp funcionaba correctamente actualizando un registro de la BD.

El tercero y más interesante consistía en blindar el SP frente a cualquier cambio, uno de los problemas más habituales que sufrimos es que los SP, se suelen modificar a menudo y muchos de los fallos no detectados vienen derivados al cambiar las longitudes de los campos, el orden, tipo de dato y otros aspectos del procedimiento.

Para testar el SP, realice la consulta siguiente para averiguar la configuración completa procedimiento:

SELECT DISTINCT dbo.sysobjects.name, dbo.sysobjects.xtype AS type, dbo.syscolumns.name AS param, dbo.syscolumns.colorder AS corder, 
dbo.syscolumns.length, dbo.syscolumns.colstat AS keyc, dbo.syscolumns.isoutparam AS colisout, dbo.systypes.xtype, dbo.syscolumns.prec as precision, 
dbo.syscolumns.scale, dbo.syscolumns.isnullable, dbo.syscolumns.iscomputed, dbo.syscolumns.number 
FROM dbo.syscolumns 
INNER JOIN dbo.sysobjects ON dbo.syscolumns.id = dbo.sysobjects.id 
INNER JOIN dbo.systypes ON dbo.syscolumns.xtype = dbo.systypes.xtype 
WHERE (dbo.sysobjects.name = 'uspUpdateEmployeePersonalInfo') AND (dbo.systypes.status <> 1) 

image

Con los datos obtenidos podemos desarrollar una consulta que utilize los valores mostrados para comprobar que el procedimiento almacenado tiene la configuración mostrada por la consulta.

SELECT COUNT(DISTINCT dbo.syscolumns.name) 
FROM dbo.syscolumns 
INNER JOIN dbo.sysobjects ON dbo.syscolumns.id = dbo.sysobjects.id 
INNER JOIN dbo.systypes ON dbo.syscolumns.xtype = dbo.systypes.xtype 
WHERE (dbo.sysobjects.name = 'uspUpdateEmployeePersonalInfo') AND (dbo.systypes.status <> 1) 
AND ((dbo.syscolumns.name = '@BirthDate' AND dbo.syscolumns.colorder = 3 AND dbo.systypes.xtype = 61 AND dbo.syscolumns.length = 8 and dbo.syscolumns.prec = 23 and dbo.syscolumns.scale = 3)
OR (dbo.syscolumns.name = '@BusinessEntityID' AND dbo.syscolumns.colorder = 1 AND dbo.systypes.xtype = 56 AND dbo.syscolumns.length = 4 and dbo.syscolumns.prec = 10 and dbo.syscolumns.scale = 0)
OR (dbo.syscolumns.name = '@Gender' AND dbo.syscolumns.colorder = 5 AND dbo.systypes.xtype = 239 AND dbo.syscolumns.length = 2)
OR (dbo.syscolumns.name = '@MaritalStatus' AND dbo.syscolumns.colorder = 4 AND dbo.systypes.xtype = 239 AND dbo.syscolumns.length = 2)
OR (dbo.syscolumns.name = '@NationalIDNumber' AND dbo.syscolumns.colorder = 2 AND dbo.systypes.xtype = 231 AND dbo.syscolumns.length = 30))

La consulta devuelve (5), el numero de campos del procedimiento uspUpdateEmployeePersonalInfo que cumplen las condiciones especificadas. Si el valor retornado por la consulta no es 5, supondra que hemos alterado algun aspecto de configuración del procedimiento almacenado, longitud, tipo de valor, orden del campo en el store procedure, etc.

La prueba completa quedaría de esta forma:

PreTest

/* Desabilita el trigger que impide el borrado de registro en la tabla .[HumanResources].[Employee] */
DISABLE Trigger [HumanResources].[dEmployee] ON [HumanResources].[Employee] 
 
/* Borra el registro de pruebas si por alguna razon estuviera ya insertado */
DELETE [AdventureWorks2008].[HumanResources].[Employee]
WHERE [BusinessEntityID] = 2292
 
/* Realiza la inserción del registro de pruebas, hay que analizar los datos de la insercción ya que hay campos que tienen restricciones a nivel de tabla
como MartitalStatus que slo acepta M (Male)  F (Female) */
 
INSERT INTO [AdventureWorks2008].[HumanResources].[Employee]
           ([BusinessEntityID]
           ,[NationalIDNumber]
           ,[LoginID]
           ,[OrganizationNode]
           ,[JobTitle]
           ,[BirthDate]
           ,[MaritalStatus]
           ,[Gender]
           ,[HireDate]
           ,[SalariedFlag]
           ,[VacationHours]
           ,[SickLeaveHours]
           ,[CurrentFlag]
           ,[rowguid]
           ,[ModifiedDate])
     VALUES (2292,'1231231398','adventure-workslyxnr',0x95EF,'Sales Representative',cast('1965-10-31' as date),'M','F',cast('1996-10-31' as date),1,34,37,1,newid(),getdate())
 
/* Comprueba que el registro de pruebas ha sido insertado correctamente */
SELECT [BusinessEntityID] FROM [AdventureWorks2008].[HumanResources].[Employee] WHERE [BusinessEntityID] = 2292 

Test

DECLARE @return_value int
 
EXEC    @return_value = [HumanResources].[uspUpdateEmployeePersonalInfo]
        @BusinessEntityID = 2292,
        @NationalIDNumber = N'13412343ZN',
        @BirthDate = N'01/01/1934',
        @MaritalStatus = N'S',
        @Gender = N'F'
 
SELECT @return_value, COUNT(DISTINCT dbo.syscolumns.name) 
FROM dbo.syscolumns 
INNER JOIN dbo.sysobjects ON dbo.syscolumns.id = dbo.sysobjects.id 
INNER JOIN dbo.systypes ON dbo.syscolumns.xtype = dbo.systypes.xtype 
WHERE (dbo.sysobjects.name = 'uspUpdateEmployeePersonalInfo') AND (dbo.systypes.status <> 1) 
AND ((dbo.syscolumns.name = '@BirthDate' AND dbo.syscolumns.colorder = 3 AND dbo.systypes.xtype = 61 AND dbo.syscolumns.length = 8 and dbo.syscolumns.prec = 23 and dbo.syscolumns.scale = 3)
OR (dbo.syscolumns.name = '@BusinessEntityID' AND dbo.syscolumns.colorder = 1 AND dbo.systypes.xtype = 56 AND dbo.syscolumns.length = 4 and dbo.syscolumns.prec = 10 and dbo.syscolumns.scale = 0)
OR (dbo.syscolumns.name = '@Gender' AND dbo.syscolumns.colorder = 5 AND dbo.systypes.xtype = 239 AND dbo.syscolumns.length = 2)
OR (dbo.syscolumns.name = '@MaritalStatus' AND dbo.syscolumns.colorder = 4 AND dbo.systypes.xtype = 239 AND dbo.syscolumns.length = 2)
OR (dbo.syscolumns.name = '@NationalIDNumber' AND dbo.syscolumns.colorder = 2 AND dbo.systypes.xtype = 231 AND dbo.syscolumns.length = 30))

De esta forma la condición de prueba del segundo valor de la consulta sera 5 (Numero campos que cumplen las condiciones especificadas), si en cualquier momento el valor no es 5, supondra que hemos alterado algun valor de configuración del procedimiento almacenado, longitud, tipo de valor, orden del campo en el store procedure, etc.

PostTest

/* Elimina el registro de pruebas insertado */
DELETE [AdventureWorks2008].[HumanResources].[Employee]
WHERE [BusinessEntityID] = 2292
 
/* Habilita el trigger, hay que realizarlo sobre la clausula EXEC porque la prueba no acepta 'GO', despues del borrado */
EXEC('ENABLE TRIGGER [HumanResources].[dEmployee] ON [HumanResources].[Employee]')

Comprobamos que al alterar la longitud de un campo la prueba fallaba, mientras que las dos primeras pasaban sin problemas. Se garantiza el correcto funcionamiento del SP, ya que inserta un registro de pruebas, testea el SP eliminando el registro de pruebas al finalizar y además blinda este sobre cualquier cambio en la configuración del SP.

Quiero agradecer a los asistentes su presencia, a Juan Carlos Gonzalez por la organización y porque gracias a el tenemos un evento cada poco tiempo en Santander y como no, a mis compañeros Rodrigo Corral e Ibon Landa también conocidos como Grupo Pimpinela, (haber si adivináis ¿quién es quién?…), que animaron la presentación con sus charlas y desparpajo, debido a la emoción los primeros minutos sufrí un pequeño colapso, me gusto especialmente la última parte, pude recuperar el video que hizo uno de los presentes, solo recordalo me emociono… 🙂

Adjunto la PPT de la charla:

Presentación en formato Powerpoint 2003.

Presentación en formato Powerpoint 2007.

Presentación en Adobe Acrobat PDF.