24 enero 2006

Pruebas de binding, y estudio de filtrado

Después de un nutrido banco de pruebas realizado por Caña, nada menos que 10 pruebas, parece claro que la implementación de nuestra colección de negocio debe ser una BindingList. No directamente, sino una colección que herede de ella, para contar con su funcionalidad. En resumen, las pruebas realizadas consistieron en enlazar (FListado o FObjeto) -> [BindingSource] -> [List o BindingList] -> objeto(s) y las conclusiones son:
  • No importa de qué forma combinemos un BindingSource con un List, la inclusión de nuevos objetos en la colección no se refresca en el interfaz de usuario. Para esto, es necesario que la colección implemente IBindingList.
  • Si conectamos el interfaz de usuario a la colección directamente, esta debe ser IBindingList para refrescarse, en otro caso (que sea una List) el interfaz sólo se refresca manualmente.
  • En cambio, al conectar directamente el BindingSource al objeto omitiendo la colección, sí se refresca todo correctamente, por lo que es un método válido para las Fichas. No lo consideramos para listados, ya que el BindingSource es un componente de interfaz. ¿La colección de negocio podría heredar de BindingSource en lugar de hacerlo de BindingList? No es este el planteamiento de la librería .NET ni de los que diseñaron el enlace a datos.
Una vez que conseguimos mostrar datos en un DataGridView, ahora nos encaminamos a la realización de filtros y ordenaciones sobre dichos listados. En principio estamos construyendo la interfaz gráfica para esto, nuestra archiconocida línea de filtros, aunque ligeramente potenciada adaptándose al tipo de cada columna. Una vez desarrollado el interfaz, veremos quién se encarga de realizar el filtro. Hay una cadena de objetos (UCListadoFiltros -> FListadoBase -> DataGridView dgv -> BindingSource bind -> EEmpleadoCol col -> DEmpleado oad), y la responsabilidad puede caer en cualquiera de ellos. En el caso de la ordenación, es muy probable que la implemente el dgv (pendiente probarlo), lo que nos facilitaría enormemente el trabajo. El caso del filtrado es mucho más complejo, dudo que lo haga el dgv, por lo que tenemos otras opciones:
  1. El bind ofrece el interfaz para hacerlo (eso sí, muy sencillo, tipo cláusula WHERE de SQL), pero no ofrece su implementación, sino que confía en la posible implementación del DataSource del que bebe. En este caso dicho DataSource es nuestra colección, que en su diseño actual (heredando de BindingList) no ofrece esta funcionalidad. Si no encontramos clases en la librería que nos permitan realizar fácilmente esta funcionalidad, es muy posible que esta opción sea descartada.
  2. Pero aún en el caso de no implementarla, la colección ofrecerá la funcionalidad. ¿Qué quiero decir? Que si la colección no sabe filtrarse, tendrá que buscar otro sistema: realizar el filtro directamente sobre la base de datos, con el sobrecoste de rendimiento que supone, pero de forma transparente para el objeto llamante. Esto nos situaría en igualdad de rendimiento con la implementación actual de Motorwin en VB6, donde todo filtro es convertido en SQL y resuelto por la BD, aunque sea un subconjunto de los datos que antes estábamos mostrando. No nos gustaría tirar por este camino, así que pensamos en una tercera opción.
  3. Utilizar la funcionalidad de filtros que ofrece ADO.NET. No olvidemos que ahí está todo hecho (recordar el antiguo modelo de cursor del lado de cliente de ADO 2.0), aunque trabajando con una representación concreta de la base de datos, por lo que no nos sirve para tratar directamente con nuestros objetos de negocio. Así que ¿cómo podemos aprovechar esta funcionalidad? En lugar de llegar a repetir la consulta a la base de datos, ya en la DAL, si somos capaces de detectar que el nuevo filtro siempre será un subconjunto del anterior, podemos aplicar el filtro al DataSet (posiblemente usando un DataView) y refrescar la colección, quizá manteniendo 2 colecciones paralelas, una de objetos del filtro actual y otra con todos los objetos originalmente cargados. Este es más costoso de implementar, como vemos, pero por la mejora en el rendimiento es muy deseable. Y no debemos olvidar que el filtro sobre un filtro anterior para ir afinando una búsqueda es de lo más habitual que hace un usuario sobre un listado, por lo que cuanto más eficiente sea, mejor.
La implementación de la colección de negocio queda pendiente, actualmente vamos a trabajar en el interfaz con una colección poco dinámica (que no notifica sus cambios), no necesitamos más para probar.

2 Comments:

Blogger Pablo said...

Ya encontré algo de cómo hacer el filtrado y la ordenación directamente sobre una colección de negocio tipada: mira en este artículo y en el que se recomienda desde ahí, del famoso Rocky Lhotka (aunque es en VB). En el artículo hacen una vista por encima de la colección, ¿qué os parece? Yo pensaba hacerlo sobre la colección directamente, pero sólo por simplificar. A lo mejor es igual de fácil crear una vista que filtre y ordene la colección original. Falta pensar una cosilla: cuando mostremos un listado en pantalla, podremos ir refinando la búsqueda filtrando sobre la colección, con lo que el resultado es un subconjunto de la colección. Pero si ampliamos el rango de fechas (apertura), realmente tenemos que volver a cargar la colección de la base de datos. ¿A alguien se le ocurre cómo tratar elegantemente con estos 2 casos, filtro de refinamiento y filtro de apertura? Queda pendiente.

30 enero, 2006 13:03  
Blogger Bernardo said...

Umm, bueno ahí va mi primer comentario. Antes de nada pediros disculpas porque mi retórica no es ni mucho menos como la de Pablo.
Bueno entramos en materia, despues de leer el artículo "Sorting the unsortable collection" el como mostrar distintas vistas ordenadas de una misma colección queda bastante claro. Nuestra primera idea era ampliar la funcionalidad de la colección de negocio para que esta proveyera al DataGridView (dg a partir de ahora) los datos filtrados y ordenados. El problema es que entonces una misma colección no puede ser mostrada en dos ventanas distintas porque comparten el orden y si ampliamos pra que acepte filtros, pues peor. Así que decidimos empezar las pruebas como nos sugiere Rocky y es creando una colección intermedia que sea la que filtre y ordene la colección original (imitando el funcionamiento de los DataView y DataTable). Basandonos en esto la idea es enlazar el dg con la lista que funcionará como Vista (sv a partir de ahora) y a su vez enlazar esta con la colección de negocio que hará de origen de datos (oList a partir de ahora).
Este es el entorno a grandes trazos, pasemos a verlo en detalle. El dg no tiene mucha complicación simplemente tendrá como DataSource la sv, esta es la que debe implementar el meollo. Para que la sv se comunique de forma correcta con el dg debe heredar de IBindingList, en el otro extremo también oList debe heredar de IBindingList por lo mismo, para que la comunicación de los cambios entre las dos listas sea automática (según me ha comentado Sergio, para que oList comunique cambios en las propiedades de sus objetos solo es necesario que herede de IList, pero sin embargo para que la adición o eliminación de objetos en la colección se propage si es necesario heredar de IBindingList). Bueno vayamos al grano y veamos como la sv implementa la ordenación:
Lo primero es inicializar la sv con la oList que será el origen de datos, la sv añadirá un handler al evento ListChanged de la oList para captura los cambios en el origen. La idea es mantener un array con el par [clave, objeto] (¿porqué un array? Pues porque este nos provee con el método Sort que viene de perlas para ordenar la colección) y con cada cambio en oList, modificamos el contenido del array y lo ordenamos. Para poder ordenarlo, todo objeto que utilicemos como clave debe cumplir con IComparable como es lógico.
Pongamos un ejemplo, imaginemos que en la sv estamos ordenando por el Nombre del objeto y en la oList se añade un nuevo objeto, nuestra sv capturará el evento ListChanged y deberá modificar el array para añadir el nuevo elemento, pero ¿como obtenemos el nombre del nuevo objeto?, fácil simplemente teniedo un objeto mSortBy del tipo PropertyDescriptor y con la línea mSortBy.GetValue(obj) obtenemos limpiamente el valor de la propiedad Nombre del nuevo objeto. ¡A que mola?. Pues listo, simplemente añadimos el objeto al array con su nombre como clave de la ordenación y reordenamos el array.
Pero os preguntareis ¿y como se muestra en el dg la nueva ordenación? Vamos a ello:
Lo primero que debemos hacer, despues de ordenar el array, es que nuestra sv debe lanzar su evento ListChanged.Reset para notificar al dg que la lista ha cambiado y forzar al dg a recargarse. Lo siguiente es ir proveyendo al dg con los objetos en el orden correcto, para ello sobreescribimos la propiedad Item de la sv y cada vez que se nos pida el elemento nº X se le devuelve el elemento X del array, con esto conseguiremos devolverle los elementos en orden.
Si quereis más profundidad leeros el artículo de Rocky.
Otros temas que deberemos probar serán:

1-El filtrado de elementos. Aunque a primera vista suponemos que será tan fácil (o dificil) como a la hora de añadir objetos al array comprobar si esos objetos cumplen los filtros.
2-Como inicializar la sv con las PropertyDescription que almacenarán los filtros para cada propiedad. ¿Quizas creando un array de las propiedades visisbles con relexión?
3- Como hacer multi ordenación y como cambiar la PropertyDescription que se está usando como ordenación.

Pero todo esto en próximos capítulos que para ser la primear vez que escribo es suficiente.

31 enero, 2006 11:28  

Publicar un comentario

<< Home