Recordatorio para desarrollos con requerimientos ligados al rendimiento

Hola,
No olvidar: en todo proyecto donde existan requerimientos técnicos ligados a los tiempos de respuesta, incluir en el contrato que desde el día 1 se contará con el juego de datos que se utilizará como banco de pruebas para realizar dichas pruebas, y que en caso de que dicho juego de pruebas varíe en tipología o tamaño, debe revisarse convenientemente por ambas partes.
Gracias.
Un saludo.

Proyectos de Base de Datos en Visual Studio 2010

Hola,
Unos  interesantes artí­culos que describen cómo realizar un manejo básico de los proyectos de Base de Datos en Visual Studio 2010.
https://www.codeproject.com/Articles/245612/Creating-a-Database-Project-with-Visual-Studio
https://msmvps.com/blogs/deborahk/archive/2010/05/02/vs-2010-database-project-an-introduction.aspx

Un saludo.

TOAD Data Modeler

Para los desarrolladores que trabajan con base de datos Oracle, el TOAD, la ranita para muchos, es una herramienta más que conocida para trabajar contra el gestor de base de datos.
Con la versión básica de desarrollador disponemos de un entorno que permite realizar tí­picas operaciones de administración de la base de datos, así­ como la creación y edición de procedimientos almacenados.
Pero, en dicha versión se echan en falta otro tipo de funcionalidades avanzadas, como es la posibilidad de definición y mantenimiento del modelo de datos a través de diagramas entidad relación. A medida que se manipulan los diagramas, estos van modificando a bajo nivel la estructura real de la base de datos, lanzando los scripts de creación y modificación necesarios.
De esta funcionalidad se obtienen ventajas significativas tales como:

  • Aumento de la productividad en la construcción. A medida que se está definiendo el modelo entidad-relación en las fases de análisis, la estructura real pasa a estar disponible de forma inmediata.
  • Documentación. No es necesario utilizar otros modeladores para representar el entidad-relación, la propia definición ya forma parte de la documentación técnica.
  • Generación automática de scripts de cambio. Ante cualquier modificación en el modelo podemos optar por solicitar los scripts de cambio para poder lanzar las actualizaciones en otro entorno.

¿Y qué herramienta nos ofrece todo esto? Pues tal como marca la entrada del post, la versión Data Modeler de TOAD.
https://www.quest.com/Toad-Data-Modeler/
Saludos.
Miguel.

AgilePoint, Algunos Puntos de Mejora

En este blog tenemos ya unos cuantos artí­culos sobre AgilePoint, hablando de todas las mejoras incorporadas, y de los aciertos en ciertos aspectos tanto de su arquitectura como de su facilidad de explotación y facilidad de integración con otros productos.
Pero como todo producto, siempre hay cosas a mejorar, y hoy vamos a dedicar un pequeño artí­culo a detallar ciertos aspectos, que bajo mi punto de vista podrí­an ser mejoras a tomar en cuenta en próximas versiones.
Procedimientos Almacenados
El propio motor de AgilePoint cuenta con una base de datos en la cual almacena toda la información relacionada con los flujos, actividades, tareas usuarios… Toda esta información es explotable desde el exterior mediante la capa de servicios que AgilePoint provee. Lo curioso de todo esto es que ante una magní­fica decisión de arquitectura (la de orientar todo a servicios), queda al aire un pequeño aspecto de seguridad en base de datos, y es que el usuario que utiliza el propio motor de AgilePoint tiene capacidad de realizar cualquier tipo de consulta, modificación e inserción directa sobre el modelo. No existe ni un sólo procedimiento almacenado que bloquee dicho acceso y que sirva de punto de entrada a las tablas. Tal como hemos hablado en este mismo blog, es una buena práctica de seguridad el crear un usuario que íºnicamente tenga permisos de ejecución sobre procedimientos almacenados en la base de datos, obligando así­ a que todo acceso se haga de forma controla a través de los procedimientos almacenados ya definidos.
Nuevas Propiedades de AgileWorks y AgileParts no Visibles a Través de la Capa de Servicios
Uno de los puntos fuertes de AgilePoint es la capacidad de poder crear nuevas actividades a medida a través de un plugin que incorporan en Visual Studio y que permite al desarrollador crear nuevos AgileWorks y AgileParts (de los cuales aíºn tenemos pendiente hablar en el blog). Entre otras cosas, dichas actividades a medida, permiten definir nuevas propiedades, que pueden ser rellenadas en tiempo de diseño a través del diagramador Envision, provisto por el mismo AgilePoint. Una vez definidas y rellenadas las propiedades, estas pueden ser consumidas a través de los propios AgileWorks y AgileParts con llamadas al API de AgilePoint, pero bajo ningíºn caso pueden ser consultadas directamente a través de servicios web. Es decir, yo puedo tener una tarea instanciada que parte de una actividad de tipo AgileWork a la cual hemos asociado una nueva propiedad llamada “NombreCiudad”, pues no podemos pedirle a un servicio web que retorne el valor de dicha propiedad para poder utilizarlo. íšnicamente podrá ser consumido este valor desde dentro del propio AgileWork en uno de los eventos que tiene relacionados… como por ejemplo, al asignarse la tarea, al cancelarse, al crearse, al finalizarse…
Qué tareas ha generado la finalización de una tarea
En muchas ocasiones nos puede ser íºtil el saber el conjunto de tareas que se han generado a partir de la finalización o completado de una tarea anterior. Esto no es posible conocerlo a partir del propio AgilePoint, ya que en su modelo de datos no está contemplado ningíºn campo que almacene dicha información. La íºnica manera que tenemos es realizar ciertas consultas por fecha y comparar, pero esto no garantiza que en momentos de alta concurrencia nos está retornando fechas que a priori pueden ser válidas, pero que no tienen nada que ver con la actividad procesada.
Grupos a los que pertenece un usuario
Incomprensiblemente podemos saber los usuarios que pertenecen a un grupo, pero no podemos obtener a través de un servicio web los grupos a los que pertenece un usuario. Para resolverlo tenemos que hacer cosas como entrar en cada uno de los grupos, y mirar si hay algíºn usuario que coincida con el que estoy buscando.
Falta de soporte para instalaciones sobre BBDD Oracle y DB2
No se dispone de guí­a de instalación para el producto si se asienta sobre BBDD Oracle, ni tan siquiera aparece en la documentación.
Errores en el guardado de las plantillas en Envision
Incomprensiblemente se produce en ocasiones que al guardar los cambios en una plantilla hay ciertas actividades que al completarlas provocan que el flujo se quede colgado. Se cierra la actividad y aunque esta permanezca en el flujo unida por una flecha con otra, esta nunca se llega a ejecutar, es como si la flecha no estuviera bien conectada. Esto obliga en muchas ocasiones a tener que crear la plantilla desde cero para solventar el problema.
Aquí­ finalizamos con las propuestas, más adelante iremos incorporando nuevos aspectos.
Un saludo.
Miguel.

Where 1=1

Un truco sencillo cuando estemos preparando consultas sql con condiciones dinámicas, en un buscador de elementos con varias condiciones de filtro por ejemplo, es utilizar en la base de la select la condición where 1=1.
Si tomamos como ejemplo la consulta
SELECT CAMPO1, CAMPO2, CAMPO3, CAMPO4
FROM TABLA1
Y quisiéramos añadir dinámicamente condiciones tendrí­amos que comprobar de base si ya tenemos una condición incluida para añadir el prefijo “AND”.
Incluyendo when 1=1 dejamos de lado este problema. Esta condición evalíºa siempre cierto, y tiene un consumo impercetible en el cálculo, y ayuda a la hora de montar las condiciones de forma dinámica, ya que desde la primera concatenación de condiciones podemos partir de un “AND nuevacondicion”.
SELECT CAMPO1, CAMPO2, CAMPO3, CAMPO4
FROM TABLA1
WHERE 1=1 { AND… AND… AND… }
Es un truco un poco tonto, pero a la vez sencillo para dejar un código más limpio.
Saludos.

El dí­a a dí­a del modelado Entidad-Relación. Enunciado.

[Actualizado a 05/10/2008]
A continuación os planteo un tí­pico problema de modelo entidad-relación en base de datos, en el que se debe modelar una situación bastante comíºn en un desarrollo de software. Os pedirí­a que intentarais resolver el problema sin ver antes las tres soluciones que marco al “ejercicio” en la parte final del post, y comprobar así­ finalmente cuál ha sido el tipo de solución más habitual. Os agradecerí­a además que razonarais la respuesta. En el caso de que vuestra solución no se ajuste a ninguna de las tres descritas, por favor, enviadme un diagrama a info@miguelmatas.es para que pueda adjuntarla al artí­culo. Serí­a estupendo además que en el razonamiento de vuestra solución no se limitara a las razones estrí­ctamente del modelo y la base de datos, si no que estuviera acompañada de algíºn razonamiento relacionado con arquitectura de software, orientación al negocio, eficiencia a nivel de datos, protección del dato, etc. En el caso de que no queráis “perder” tanto tiempo, me doy por satisfecho si contestáis al post indicando si vuestra solución se acerca a una de las tres primeras o ha sido otra 🙂
Gracias!
Enunciado:
Una conocida empresa de Rent a Car quiere informatizar su sistema de gestión de vehí­culos. En esencia, dicha empresa alquila de forma temporal los coches de los que dispone, y, en un primer proceso de ingenierí­a necesita contar con una descripción técnica de cómo se almacenarí­an el repositorio de los diferentes coches con los que cuenta en cartera.
La empresa cuenta con diferentes tipos de coches, son realmente los tipos de coches que maneja lo que la diferencia de los Rent a Car al uso, ya que los usuarios tienen la oportunidad de alquilar coches anfibios, coches voladores y coches oficiales blindados. Estos tres tipos de coche tienen algunas caracterí­sticas en comíºn que se desean conocer, como son la matrí­cula, el níºmero de puertas y la marca. Además, de forma especí­fica se necesita conocer el máximo de nudos a la hora a la que puede navegar un coche anfibio, los milí­metros de blindaje y el tipo de blindaje de un coche oficial blindado, y la autonomí­a de horas de vuelo con las que cuenta un coche volador.
Posibles soluciones de modelado entidad-relación:
Os pedirí­a por favor que no consultarais las soluciones hasta que hayáis desarrollado vuestra propia solución, para así­ no interferir en el resultado.
Solución 1
Solución 2
Solución 3
Solución 4 [nuevo 05/10/2008]
Saludos y gracias 🙂
Miguel.

TableAdapters Transaccionales sin usar TransactionScope

La verdad es que el mundo de los TableAdapters y los Datasets Tipados abre muchí­simas puertas de cara a la productividad en el desarrollo y a la disminución de tasa de errores a la hora de trabajar contra una fuente de datos.
El problema que nos hemos encontrado muy amenudo es querer utilizarlos transaccionalmente pero sin emplear la herramienta TransactionScope de ADO.NET, y seguir utilizando una conexión y transacción controlada por nosotros mismos. No sé por qué pero en su dí­a intenté hacerlo así­ y no hubo manera y tuve que morir al palo del TransactionScope. Recientemente ha vuelto a surgir la necesidad, y, esta vez con Visual Studio 2008 y el Framework 3.5, parece que la cosa ha funcionado.
No sé si es cosa del Framework 3.5 (en su dí­a lo intenté con la versión 2.0), o que hoy me ha dado por estar líºcido, o las otras veces demasiado poco líºcido, pero hoy con una tonterí­a de código he conseguido trabajar de forma transaccional entre dos TableAdapters que están en dos Datasets distintos, compartiendo la misma conexión y transacción.
Sin más rodeos, el código:

public void InsertMarca(string marca, int veces)
{

SqlTransaction tran = null;
SqlConnection con = null;
try
{
// abrimos una conexión
con = new SqlConnection(“Data Source=.\\SQLEXPRESS;AttachDbFilename=|DataDirectory|\\BBDD.mdf;Integrated Security=True;User Instance=True”);
con.Open();
//establecemos que la conexión de la marca es la que hemos creado
MarcaTableAdapter adapterMarca = new MarcaTableAdapter();
adapterMarca.Connection = con;
// establecemos que la conexión del coche es la que hemos creado
CocheTableAdapter adapterCoche = new CocheTableAdapter();
adapterCoche.Connection = con;
// iniciamos una transacción sobre dicha conexión
tran = con.BeginTransaction();
// le pasamos a los adaptadores la transacción que acabamos de crear
adapterMarca.Transaction = tran;
adapterCoche.Transaction = tran;
for (int i = 0; i < veces; i++)
{
adapterMarca.InsertMarca(marca);
adapterCoche.InsertCoche(“4444-cxv”, 4, 8);
if (i > 10)
{
// forzamos un error para ver qué pasa
throw new Exception(“prueba de transacción”);
}
}
tran.Commit();
}
catch (Exception ex)
{
tran.Rollback();
}
finally
{
con.Close();
}
}
Aquí­ la pieza que falta es la siguiente, en el diseñador de Datasets, hay que irse a las propiedades de cada uno de los TableAdapter implicados, y establecer la propiedad “ConnectionModifier” a “public”.
Existen otras soluciones a este mismo problema que podéis encontrar descritas en el siguiente artí­culo que explica bastante bien el problema:
https://www.codeproject.com/KB/dotnet/transactionta.aspx
En concreto el autor del artí­culo defiende una solución utilizando Reflection, ya que el otro tipo de solución con la que se suele trabajar para este mismo problema es con el uso de clases parciales.
El problema del uso de las clases parciales es que necesitas generar una clase parcial por cada uno de los TableAdapters, y esto a la larga puede hacer generar mucho código. La solución con Reflection tampoco es la panacea, ya que aunque te evites generar una clase parcial por TableAdapter, aparece el problema de siempre al utilizar Reflection, que nadie te garantiza que tras una actualización del framework la propiedad que has explotado cambie su nombre…
La solución que aporto tiene alguna ventaja, por ejemplo no usa Reflection así­ que nos evitamos problemas de compatibilidades futuras, tampoco es necesario generar una clase parcial por TableAdapter, lo íºnico que tendremos que hacer es en los casos de actuar de forma transaccional (por experiencia el volumen de accesos transaccionales a base de datos no suele llegar ni a la mitad de los accesos de modificación en base de datos) añadir algunas lí­neas más en el código.
Eso sí­, hay una cosa que no me gusta un pelo, que es que tengamos ahí­ en el código la apertura y el cierre de la conexión. Que nos dejemos la apertura lo veo complicado ya que si lo probamos aunque sea una vez nos dará error. El terrible problema lo veo en el cierre de la conexión, ya que es la historia de antaño, como te dejes el finally acabas de reventar tu sistema. Dándole más vueltas, este problemas con una clase que maneje la conexión, que herede de la interfaz IDisposable y que utilicemos mediante un using podrí­amos solventar el problema de olvidarnos cerrar la conexión, pero vamos a seguir teniendo que tragarnos el asignar la conexión y la transacción a todos los adapters que participen.
Saludos.
Miguel.

Caché en la Capa de Acceso a Datos

En muchas aplicaciones suele ser comíºn que existan conjuntos de datos que cambien muy poco o nada a lo largo del tiempo. Por ejemplo, si almacenamos en nuestra base de datos tablas con Paí­ses, Provincias, Comunidades Autónomas… lo más seguro es que los registros que conformen la tabla no varí­en en el tiempo o si lo hacen sean cambios totalmente excepcionales.
Si esto es así­ e imaginando el tí­pico formulario web donde tenemos que seleccionar un desplegable con provincias, podrí­a resultar ineficiente hacer una consulta a base de datos por cada carga del formulario que realizaran los distintos usuarios de la aplicación. ¿Para qué vamos a consultar otra vez la base de datos si podemos asegurar en un 99% que no habrán cambiado? Sobre todo teniendo en cuenta que el acceso a base de datos suele ser de lo que más penaliza el rendimiento de una aplicación.
Una buena idea serí­a que en segíºn que métodos de nuestra capa de acceso a datos, antes de realizar una consulta directa a una tabla, comprobáramos si los datos ya se han almacenado en entidad fí­sica mucho más rápida de acceder que la base de datos, por ejemplo, en una caché. Inicialmente esta caché estarí­a vací­a, en la primera carga se rellenarí­a a partir de la información de la base de datos, y el resto de veces ya se atacarí­a siempre a la memoria cache.
El resultado, claramente es un tiempo de respuesta mucho más bajo en cada transacción de este tipo.
¿Algíºn ejemplo con pseudo-codigo?
public Lista<ProvinciaDTO> GetProvincias()
{
  if (IsVacio(CacheProvincias))
  {
    CacheProvincias = RellenaConAccesoATablaProvincias();
  }
  return TransformaCacheEnLista(CacheProvincias);    
}
Como véis sólo rellenaremos la caché de provincias con los datos de la tabla si la caché está vací­a, si no lo está, directamente retornaremos los datos desde la caché.
En el pseudo-código no se están resolviendo temas de concurrencia (cómo resolver si dos usuarios al mismo tiempo comprueban si está vací­o y da verdadero), y se abstrae 100% la solución propia de cómo cachear precisamente para hacerlo independiente de la tecnologí­a en que queráis aplicar el método.
Otro tema interesante que podrí­an incorporarse, es la posibilidad de que la caché caduque, es decir, que no se almacene la información en la caché de forma infinita mientras la aplicación no se reinicie, si no que se establezcan periodos temporales en los como máximo la caché será renovada. Por ejemplo si tenemos una tabla que almacene Marcas puede que nos interese que se reuneve la caché cada 24 horas ya que almenos una vez al dí­a se añade una marca nueva (me lo estoy inventando obviamente, es por dar un ejemplo). Si ante la petición de un usuario se comprueba que la caché está caducada, esta se vaciarí­a y se volverí­a a rellenar de forma automática tras una consulta a la tabla correspondiente de la base de datos.
¿Interesante?
Saludos.
Miguel.
 

Mala Definición de la Estructura de un INSERT

Esta sentencia SQL es correcta sintácticamente y por tanto ejecutable:
INSERT INTO
COCHE
VALUES
(1, ‘Seat’, ‘IB-4345-CK’, ’12/06/2008′)
Pero tiene un grave problema, no estamos indicando cual es el campo de la tabla donde se va a insertar la información, por lo que por defecto, el sistema gestor de bases de datos pertinente lo asignará a los campos siguiendo el orden en el que fueron creados en la instrucción CREATE TABLE que en su momento se utilizó para dar de alta la tabla en la base de datos.
¿Qué pasa si hago un ALTER sobre la tabla y añado un campo más? Que el INSERT deja de funcionar.
Además, este mal uso es un arma de doble filo porque inicialmente todo funcionará bien, pero de repente, sin saber cómo ni por qué y por “arte de magia” tendremos errores o incosistencias al dar de alta un nuevo coche en nuestro sistema, ya que el orden de los campos habrá cambiado. Si coincide que el campo al que asignamos es del mismo tipo podremos provocar una incosistencia (meter matrí­culas en el campo donde guardamos la marca del coche), y si no coincide, la base de datos lanzará un error.
Por lo tanto, siempre deberí­as utilizar la siguiente sintaxis SQL para un INSERT, en lugar de la que especificábamos anteriormente:
INSERT INTO
COCHE
(CO_CODIGO, MARCA, MATRICULA, FECHA_ALTA)
VALUES
(1, ‘Seat’, ‘IB-4345-CK’, ’12/06/2008′)
Saludos.
Miguel.

Mal uso de las Transacciones

Una transacción permite que al realizar una acción determinada, el estado de la entidad o entidades a las que estemos atacando se mantenga como al principio en el caso de que se produzca algíºn tipo de error en la transacción.
Un tí­pico ejemplo de transacción es la que realizamos contra un cajero automático.
1) Introducimos cantidad a retirar
2) Introducimos nuestra identificación
3) Se resta la cantidad a retirar de la cuenta corriente
4) El cajero extrae los billetes para que los cojamos
Si tras el paso 3 ocurre un error en la aplicación y todos estos puntos no se llevan a cabo de manera transaccional verí­amos como en nuestra cuenta se ha restado el efectivo, pero nunca recibirí­amos los billetes fí­sicamente. En cambio, en un ámbito transaccional todo volverí­a a su estado inicial, quedando el efectivo de la cuenta intacto.
Hasta aquí­ nos ubicamos con el concepto de transacción y ahora vienen las malas prácticas al respecto.
1) La primera y obvia es no utilizar transacciones cuando trabajamos en un contexto como el que hemos citado con el ejemplo. Los datos se perderán o quedarán inconsistentes.
2) Es un error realizar una transacción cuando íºnicamente realizamos consultas a la base de datos. No hay cambios en la misma, no tiene sentido volver a un estado anterior porque nada a cambiado.
3) No tiene sentido utilizar una transacción si se realiza una consulta y una modificación (inserción, actualización, borrado…). Si el error se produce en la modificación no va a pasar nada porque la primer consulta no cambió nada de la base de datos (aunque aquí­ incluyo un matiz que podréis ver en los comentarios de ese post, que podrí­an darse casos donde por el contexto de vuestro aplico necesitéis bloquear la tabla con una transacción para que las modificaciones que realicéis después de vuestra consulta no se vean afectadas por otras llamadas concurrentes a las tablas con las que trabajais).
Saludos.
Miguel.