31 enero 2008

Consumo de memoria de las imágenes

Las imágenes en .net se guardan como Bitmap, única herencia de Image (la otra es Metafile). Como nosotros tenemos propiedades de imágenes en las entidades, mientras una entidad está cargada se mantienen en memoria sus imágenes. No se cargan con la entidad, sólo cuando se necesitan, pero una vez cargadas ya se mantienen con la entidad. Al hojear el Catálogo comercial de N2 Frax, se carga una colección con los 130 artículos del catálogo, y si vamos pasando las páginas una a una al llegar a la 30 tenemos en uso 500 Mb de memoria RAM, y Windows se vuelve loco usando memoria virtual, el sistema prácticamente se congela.
Las imágenes de este catálogo son mucho más grandes que temporadas anteriores (se han sacado con máxima calidad), lo que ha agravado el problema. Aunque sean archivos png de unos 70 Kb, en memoria se guardan como Bitmap, por lo que el uso de memoria es tremendo.
He encontrado pocas historias parecidas en Internet. Un tío dice que le falla en ASP, pero nadie le contesta. Hay pocas referencias a la cuestión de cachear imágenes en memoria. Un colega cuenta su experiencia desarrollando una caché de imágenes que libera imágenes cuando supera un número de Kb en memoria.
Así que vemos la necesidad de limitar el número de imágenes almacenadas en memoria, para lo que deben almacenarse en un lugar centralizado: CentroImagenes, en vez de variables privadas en las entidades. Así puede controlarse el volumen de memoria utilizado, y liberar cuando se alcance una cantidad que podrá parametrizarse.
Surge un problema: cuando asignamos una imagen a una entidad, no debe guardarse en disco hasta guardar la entidad. Así que creamos nuevos métodos en CentroImagenes:
  • Preasignar(clave, imagen) para incluir la imagen en caché pero no en disco. Debe llamarse en el set de la propiedad imagen de la entidad.
  • Confirmar(clave) para guardar en disco una imagen que ya hemos preasignado en caché. Debe llamarse al guardar la entidad.
  • Descartar(clave) para quitar de caché una imagen que hemos preasignado antes. Debe llamarse en el deshacer de la entidad.

Con esto nunca se almacena una imagen en negocio, sino que todas se centralizan en CentroImagenes, y así podemos liberar a voluntad... aunque hay que intentar no liberar una imagen preasignada (si no cuando se intente guardar su entidad se habrá perdido la imagen que asignó el usuario).

Para liberar, vamos a consultar el consumo en memoria, pero no encuentro cómo consultar cuanta memoria ocupa la caché, o cuanta ocupa cada imagen, así que habrá que estimarla: al incluir cada imagen, calcularemos su número de pixeles y supondremos que cada uno ocupa 4 bytes (lo he leído en algún sitio). Habrá que descontar también cuando quitemos imágenes de la caché.

Etiquetas: ,

25 enero 2008

Origenes de datos en VS2005

Ayer me pasó una cosa curiosa, algo muy tonto que me tuvo ocupado toda la mañana:

Estoy añadiendo a una librería (L1) una clase (C1) que antes tenía en otra librería a parte (L2), al cambiarla de sitio también cambié el espacio de nombres, hasta aquí todo normal. El problema vino cuando para probar que los cambios eran todos correctos intenté utilizar un formulario muy simple (con un grid y un binding a objetos) de un proyecto que ya creé anteriormente para probar la clase en la librería L2. Como la clase había cambiado de espacio de nombres, lo primero que hice fué quitar el bindingSource y añadir uno nuevo, cuando desplegué la propiedad DataSource me dió un error "Instancia de objeto establecida a null" o algo así, ahí ya me quedé extrañado. Pensé que algo se había quedado colgado en el formulario por lo que volví a eliminar el binding y de paso eliminé también el grid. Volví a introducir un grid y binding nuevo, y al intentar desplegar otra vez el DatSource, el mismo error. Probé a enganchar el Datagrid y desplegué también el datasource del grid (aquí ya me queé muerto) y me petó el VisualStudio. ¿?
Añadí un nuevo formulario para ver si era cosa del anterior y los mismos errores.

Ya pensaba que era algo del VS y repare la instalación del VS, volvía a fallar, desisntalé y volví a instalar y lo mismo.

Al final con la ayuda de un compañero descubrimos que en los orígenes de datos se había quedado el origen de datos a objeto de la clase cuando estaba en la librería L2 con el antiguo espacio de nombre. Simplemente con eliminar esat referencia funcionó todo correctamente.

Atención: Cuando movemos clases de espacio de nombres hay que eliminar los origenes de datos a estas.

23 enero 2008

Generar Instalable del Catálogo

Necesitamos una instalación que haga estos pasos:
  1. Instalar la aplicación (el exe y las dll).
  2. Instalar .net 2.0 si no está en el equipo.
  3. Instalar SQL Server Express y crear una instancia.
  4. Restaurar la base de datos de los catálogos sobre la instancia de SQL instalada.
  5. Copiar la carpeta de imágenes del catálogo.
  6. Además, hay que dar permisos al usuario para SQL: incluirlo en el nuevo grupo SQLUser.
Para los catálogos anteriores todos estos pasos se realizaron manualmente, con una instalación que sólo contenía la aplicación y a la que se añadió después las imágenes (sobre la carpeta Data de SQL Server en Archivos de programa).

Una primera aproximación fue Clickonce, usando la opción Publicar de los proyectos de Visual Studio 2005. Esto nos permitía en un único paso instalar .net 2.0, SQL Express y la aplicación, pero no controla la restauración de la base de datos ni de la carpeta imágenes (salvo que se quiera incluir en la carpeta de la aplicación). El problema con Clickonce es que sólo instala lo que se incluye en el proyecto, por lo que tendríamos que incluir los 100 Mb de imágenes. Y que por defecto se instala en la ruta de Archivos de programa, y para que no sea así puede cambiarse una etiqueta <TargetPath> en el proyecto. Esto tiene 2 inconvenientes: hay que modificar la etiqueta para todas las imágenes una a una, y por otro lado no puede definirse una ruta basada en las carpetas del sistema. Este segundo problema es más grave, ya que hemos decidido incluir las imágenes en la carpeta de datos de programa común a todos los usuarios (All users en XP, Public en Vista), en Datos de programa\Nibisoft\N2Frax\Imagenes.

La solución adoptada pasa por un proyecto de Setup de Visual Studio 2005, tal como ya hicimos en los catálogos anteriores, pero ampliado.

En primer lugar, incluimos los prerrequisitos de la aplicación: .net 2.0, SqlExpress, MDAC 2.8, Informes de Visual Studio (por si acaso). Además pedimos que se incluyan con la instalación, es decir, que no haya que descargarlos de Internet. Esto nos incluye una carpeta para cada uno, aportando unos 70 Mb, pero permitirá acelerar la instalación, y no pensamos instalar desde Internet sino desde un disco (CD).

Añadimos también las imágenes, en una nueva carpeta creada en Datos de programa de Todos los usuarios. Esta carpeta no se ofrece en el Sistema de archivos del Setup, en cambio sí se ofrece Datos de programa del Usuario. Pero descubrí que se puede cambiar en el archivo de proyecto del Setup, sustituyendo AppDataFolder por CommonAppDataFolder. En Visual Studio se sigue mostrando como título que la carpeta es del usuario, pero al guardar de nuevo respeta el Common..., y al instalar sí usa All users.

Para instalar la BD sobre la nueva instancia instalada de SQL Express (o una anterior si ya existía), utilizo acciones personalizadas en la instalación (custom actions). En primer lugar, incluyo un archivo bak con la copia de seguridad de la base de datos en el Setup, situándolo en la misma carpeta anterior de All users. Para restaurar la base de datos, añado una clase Installer (Instalador.cs) en el proyecto del catálogo (exe), en la que sobreescribo el método Install. En un principio usé el método Commit, pero me parece más lógico hacerlo en Install después de llamar al base para que realice la instalación de los archivos (y copie el archivo .bak en su destino). Además el Commit me dio un fallo, que aunque encontré la solución me convenció aún más de usar el Install. Otra diferencia está en que durante el Install se ofrece disponible al usuario el botón Cancelar para abortar la instalación, mientras que durante el Commit se deshabilita. En ese método Install utilicé nuestra librería AdministradorBD para restaurar (tras hacerle algunos cambios para que funcionara sin pivotar, en modo local, y con distintos nombres lógicos y de base de datos, aunque esto precisa una discusión mayor).

En este punto nos encontramos con el problema de que aunque SQL Express se había instalado correctamente, el usuario actual no tenía permisos para conectarse, por lo que no podíamos restaurar la base de datos. Así que como primer paso del método Install en la nueva clase Instalador incluimos al usuario actual (Enviroment.UserName) en el grupo creado para SQLUser. Para hacer esto sobre el sistema local (no sobre AD - ActiveDirectory), utilizamos el espacio de nombres System.DirectoryServices, pero con la raíz WinNT: que identifica el sistema local. No me funcionó usando la propiedad member, y sí con invoke al método Add. Por supuesto el usuario debe tener permisos para hacer esto, pero estamos instalando la aplicación, ¿no?

Así tenemos un Setup en condiciones que podemos distribuir en un disco (son 180 Mb con las imágenes y los prerrequisitos, lo que es la aplicación son unos 3 Mb).

Etiquetas:

14 enero 2008

Sql server y el collation de los huev...

A ver, por donde empezar....ummmm...sí, como, no por el principio:

Tenemos un cliente con un equipo en el que está instalado el Sqlserver express con dos bases de datos de nuestro programa, hasta ahora todo funcionaba correctamente, lleva más de un año trabajando sin problemas. Pero hace un par de días nos llamó porque al realizar un proceso de importación de tarifas le fallaba, sin entrar en detalles del programa, el hecho es que durante la importación de las tarifas se genera una vista de datos de una tabla de una de las bases de datos sobre la otra base de datos. El error en cuestión era algo así como que no se podían comparar campos definidos como "Modern_Spanish_CI_AS" y "Modern_Spanish_CI_AI".

Tras investigar un poco averiguamos que este parámetro se llama collation o en español intercalación. Pues bien este parámetro define si las comparaciones de cadenas tendrán en cuenta mayúsculas, acentos...

Teniendo ya un poco más de idea empezamos a investigar que configuración de collation teníamos en las bases de datos y he aquí que encontramos el error, cada una estaba definida con un collation diferente.

La solución era sencilla cambiar el collation de una de ellas, nos decidimos por la más pequeña y sencilla, tenía menos tablas y apenas tenía FOREIGN KEYS (cuando existe una FOREIGN KEY no se puede cambiar el collation del campo debes eliminar antes la clave). Una vez solucionado el problema probamos la importación de tarifas y @#! fallaba otra vez.

¿Porqué sería esta vez? Eco, porque el servidor también define un collation por defecto (sí, en Sqlserver 2005 se puede definir el collation para servidor, base de datos y campos dentro de las tablas), desgraciadamente cuando creas uan vista esta coge el collation del servidor no de la base de datos donde la creas y como no el collation del servidor era el contrario al de las bases de datos.

Aquí teníamos un problema mayor, no se puede cambiar el collation del servidor, bueno sí se puede cambiar pero hay que cambiar la master y reconfigurar el servidor. Vaya que no, optamo por algo más lento pero seguro, instalamos una segunda instancia.

Indagando un poco en internet leimos que el collation se configura dependiendo de las opciones de configuración regional, nos decidimos a ahcer una prueba. Probamos a cambiarlas de español internacional (que son al que tenía el equipo) a español tradicional, pero no funcionó se volvió a instalar el servidor como "Modern_Spanish_CI_AS". Así que decidimos hacerlo a las bravas volvimos a instalar el servidor por esta vez habilitando las opciones avanzadas en el proceso de instalación para poder definir manualmente el collation. Simplemente era desmarcar la opción sensitivo a acentos y listo se solucionó.

Etiquetas: