My favorites | Sign in
Project Home Downloads Wiki Issues Source
READ-ONLY: This project has been archived. For more information see this post.
Search
for
Busquedas  
Ejemplo de implementación de las búsquedas
Featured
Updated Feb 4, 2010 by sergi.gi...@gmail.com

Introducción

En este artículo expongo un posible método para implementar las búsquedas en las distintas capas de la aplicación afectadas, a modo de ejemplo. Probablemente no será necesario implementarlo en todas las entidades, sino sólo en aquellas que requieran ofrecer una búsqueda al usuario final, como en los formularios de gestión, o los listados de noticias en la web.

Pasos

Al igual que con la paginación, para habilitar la búsqueda vamos a tener que tocar todas las capas, excepto los DTOs, pero los cambios son sencillos, y no tiene por qué darnos mayores problemas. Pondremos como ejemplo la entidad PERMISO, que es sencilla y es con la que ya he hecho las pruebas, cuyo código ya está disponible en el repositorio para chequearlo.

Esto ya lo sabíamos desde el principio, pero como en la primera fase no implementamos ni búsquedas ni paginación, pues toca hacerlo en este momento.

CAD

Paso 1

En primer lugar, lo más evidente es que hay que modificar el CAD para que los métodos de listados nos devuelvan tan sólo los registros que queremos. Para ello, lo primero que haremos será implementar un método auxiliar que nos devuelva el total de registros que genera la búsqueda, dato necesario para calcular el número de páginas que tenemos.

Para ello, como ejemplo, el count de las búsquedas de permisos:

        public static int numRecordsBusqueda(string permiso, string descripcion)
        {
            if (permiso == "" && descripcion == "")
            {
                return numRecords();
            }
            else
            {
                StringBuilder where = new StringBuilder();

                if (permiso != "")
                {
                    permiso = "%" + permiso.Replace("'", "") + "%";
                    where.Append("permiso like '" + permiso + "' ");
                }
                if (descripcion != "")
                {
                    descripcion = "%" + descripcion.Replace("'", "") + "%";
                    if (where.Length > 0)
                        where.Append(" AND descripcion like '" + descripcion + "'");
                    else
                        where.Append("descripcion like '" + descripcion + "'");
                }

                string sql = "SELECT count(id) FROM dbo.Permiso ";
                if (where.Length > 0)
                {
                    sql += " WHERE (" + where.ToString() + ") ";
                }

                return cInterfazBD.countQuery(sql);
            }
        }

El método recibe como parámetros cada uno de los campos por los que podemos hacer la búsqueda, y se monta la query en función de éstos. Con los parámetros vacíos, debería devolver todos los registros.

He añadido un método más al InterfazBD para lanzar queries que hacen count y que devuelve un scalar.

Paso 2

En segundo lugar, sobrecargamos el método "listado()" que nos va a devolver el conjunto de datos que buscamos, y le pasaremos como parámetro las campos de búsqueda, además de las dos variables Int, que representarán el tamaño de la página, y el índice del registro por el que queremos que empiece a devolver datos, ya que la búsqueda puede ser paginada también:

        public static ArrayList listado(int pageSize, int skip, string permiso, string descripcion)
        {
            ArrayList permisos = null;
            try
            {
                SqlDataReader dr;
                UsuariosDataSet.PermisoDataTable dt = new UsuariosDataSet.PermisoDataTable();

                StringBuilder where = new StringBuilder();

                if (permiso != "")
                {
                    permiso = "%" + permiso + "%";
                    where.Append("permiso like @permiso ");
                }
                if (descripcion != "")
                {
                    descripcion = "%" + descripcion + "%";
                    if (where.Length > 0)
                        where.Append(" AND descripcion like @descripcion");
                    else
                        where.Append("descripcion like @descripcion");
                }

                string sql = "SELECT TOP " + pageSize + " id, permiso, descripcion FROM dbo.Permiso ";
                if (where.Length > 0)
                {
                    sql += " WHERE (" + where.ToString() + ") ";
                    sql += " AND id NOT IN ";
                }
                else
                    sql += " WHERE id NOT IN ";
                sql += "(SELECT TOP " + skip + " id FROM dbo.Permiso";
                if (where.Length > 0)
                    sql += " WHERE (" + where.ToString() + ") ";
                sql += ")";

                Console.WriteLine(sql);

                SqlParameter[] param = new SqlParameter[2];
                param[0] = new SqlParameter("@permiso", permiso);
                param[1] = new SqlParameter("@descripcion", descripcion);

                dr = cInterfazBD.ejecutaReader(sql, param);
                rellenaListado(ref permisos, dr);

                dr.Close();
                dr.Dispose();
            }
            catch (Exception ex)
            {
                throw ex;
            }
            return permisos;
        }

Siguiendo el mismo método que con el recuento de registros, montamos la query en función de los parámetros de búsqueda recibidos. En caso de pasarle todos los parámetros de búsqueda vacíos, esta función devolverá todos los registros.

Utilizamos los métodos sobrecargados "rellenaListado(ref permisos, dr);" y "cargarEN(ref p, dr);" con los datareaders que creamos para la paginación, y que podemos ver en el artículo de paginación.

Con estos cambios, ya tenemos el CAD preparado para poder devolver resultados paginados. Ahora nos queda propagar los cambios a los ENs y CPs.

EN

Una vez tenemos el CAD preparado, pasamos a implementar en el EN las llamadas correspondientes a los nuevos métodos: numRecords() y listado(int pageSize, int skip):

        public static ArrayList listado(int pageSize, int skip, string permiso, string descripcion)
        {
            ArrayList permisos = null;
            try
            {
                permisos = CADPermiso.listado(pageSize, skip, permiso, descripcion);
            }
            catch (Exception ex)
            {
                throw ex;
            }
            return permisos;
        }

        public static int numRecordsBusqueda(string permiso, string descripcion)
        {
            int retorno = 0;
            try
            {
                retorno = CADPermiso.numRecordsBusqueda(permiso, descripcion);
            }
            catch (Exception)
            {
                throw;
            }
            return retorno;
        }

CP

Y lo mismo para el CP:

        public ArrayList listado(int pageSize, int skip, string permiso, string descripcion)
        {
            Console.WriteLine("CPPermiso: listado paginado y filtrado");
            ArrayList retorno = null;

            try
            {
                retorno = cPermiso.listado(pageSize,skip, permiso, descripcion);
            }
            catch (Exception ex)
            {
                Console.WriteLine("[ERROR] " + ex.Source + "\n" + ex.Message + "\n" + ex.StackTrace);
                retorno = null;
            }
            return retorno;
        }

        public int numRecordsBusqueda(string permiso, string descripcion)
        {
            Console.WriteLine("CPPermiso: numRecordsBusqueda");
            int retorno = 0;

            try
            {
                retorno = cPermiso.numRecordsBusqueda(permiso, descripcion);
            }
            catch (Exception ex)
            {
                Console.WriteLine("[ERROR] " + ex.Source + "\n" + ex.Message + "\n" + ex.StackTrace);
                retorno = 0;
            }
            return retorno;
        }

Compilamos el proyecto Negocio y NoticiasComPlus y comprobamos que no hay errores. Si todo está correcto, procedemos a registrar de nuevo el componente COM+ para exponer los nuevos métodos.

CP en Cliente

En el proxy del COM+ en el proyecto ClienteRemoto también tenemos que exponer los nuevos métodos para que el Interfaz los pueda utilizar:

    public static ArrayList listado(int pageSize, int skip, string permiso, string descripcion)
    {
        object[] argumentos = new object[] { pageSize, skip, permiso, descripcion };
        ArrayList resultado = null;

        try
        {
            rem = ConnectRemoting(servicioID);
            resultado = (ArrayList)rem.invocar("listado", argumentos);
        }
        catch (Exception ex)
        {
            throw ex;
        }
        return resultado;
    }

    public static int numRecordsBusqueda(string permiso, string descripcion)
    {
        object[] argumentos = new object[] { permiso, descripcion };
        int resultado = 0;

        try
        {
            rem = ConnectRemoting(servicioID);
            resultado = (int)rem.invocar("numRecordsBusqueda", argumentos);
        }
        catch (Exception ex)
        {
            throw ex;
        }
        return resultado;
    }

En este punto, sólo nos queda utilizar los nuevos métodos desde el Interfaz de Usuario.

Aplicación Windows

Los métodos de búsqueda los podemos utilizar en cualquiera de los dos interfaces de usuario que vamos a desarrollar. En este ejemplo, os pongo el uso que le he dado desde un formulario de la aplicación de Windows.

El primer paso ha sido integrar en el mismo formulario donde está el grid de listados, un panel con los campos de texto para mostrar los atributos de la entidad de negocio seleccionada en el grid. Esto es sencillo, ya que ambos controles (el grid y la ficha de datos) están enlazados al mismo binding source de Permisos. Si veis el diseño del formulario podréis apreciar esta composición.

Después he añadido un botón para habilitar las búsquedas, que se encarga de activar el modo búsqueda en el formulario:

        private void btnBuscar_Click(object sender, EventArgs e)
        {
            searching = true;
            habilitarBusqueda(true);
        }

La función habilitarBusqueda(bool) se encarga de activar los controles necesarios para que el usuario pueda introducir los datos de búsqueda que requiera:

        private void habilitarBusqueda(bool p)
        {
            permisoTextBox.Enabled = p;
            descripcionTextBox.Enabled = p;
            btnGuardar.Enabled = !p;
            btnCancelar.Enabled = p;
            btnCancelSearchDescripcion.Visible = p;
            btnCancelSearchPermiso.Visible = p;

            
            // Si estamos habilitando la busqueda (p==true)...
            if (p)
            {
                // inicializamos el contador de resultados
                resultsBusqueda = results;

                // Quitamos los bindings a Datos
                permisoTextBox.DataBindings.Clear();
                descripcionTextBox.DataBindings.Clear();

                // Vaciamos los campos de datos
                permisoTextBox.Text = "";
                descripcionTextBox.Text = "";

                // Vaciamos el Grid
                dTOPermisoBindingSource.Clear();

                // Inicializamos la paginacion
                ctrlPaginacion.paginacion.refresh(0);
                ctrlPaginacion.actualizaPaginacion();

                // Pasamos el foco al primer campo de texto
                permisoTextBox.Focus();
            }

            // Si estamos deshabilitando la búsqueda (p==false)
            else
            {
                // inicializamos el contador de resultados
                results = -1;

                // Recuperamos el dato seleccionado en el Grid
                if (dTOPermisoDataGridView.CurrentRow != null)
                {
                    DTOPermiso permiso = (DTOPermiso)dTOPermisoDataGridView.CurrentRow.DataBoundItem;
                    this.permisoTextBox.Text = permiso.Permiso;
                    this.descripcionTextBox.Text = permiso.Descripcion;
                }

                // habilitamos los bindings a Datos
                this.permisoTextBox.DataBindings.Add(new System.Windows.Forms.Binding("Text", this.dTOPermisoBindingSource, "Permiso", true));
                this.descripcionTextBox.DataBindings.Add(new System.Windows.Forms.Binding("Text", this.dTOPermisoBindingSource, "Descripcion", true));
            }

            dTOPermisoBindingNavigator.Enabled = !p;
            btnEditar.Enabled = !p;
        }

Se añaden algunos atributos privados al formulario para controlar el estado del formulario y la paginación de las búsquedas, y modificamos el evento Form_Load para que guarde el contador al inicializar la paginación:

        private bool adding = false;  //indica que estamos en modo inserción
        private bool searching = false;  //indica que estamos en modo búsqueda
        private int resultsBusqueda = -1; // Contador de resultados de una búsqueda
        private int results = -1; // Contador de resultados completos

        private void frmPermisos_Load(object sender, EventArgs e)
        {
            results = CPPermiso.numRecords();
            ctrlPaginacion.init(results);
        }

Una vez tenemos el formulario activo, introduciremos los datos en los campos para que el sistema filtre los resultados. Lo he implementado en esta versión al estilo "ajax", es decir, conforme el usuario va introduciendo texto, se van realizando las llamadas a la búsqueda directamente. Quizás no es lo más eficiente, pero si lo más directo. Se podría poner un botón de búsqueda también.

Para ello, en los dos campos de texto de este formulario, he programado estos eventos:

        private void permisoTextBox_KeyUp(object sender, KeyEventArgs e)
        {
            pulsaTecla(e);
        }

        private void descripcionTextBox_KeyUp(object sender, KeyEventArgs e)
        {
            pulsaTecla(e);
        }

        private void pulsaTecla(KeyEventArgs e)
        {
            if (searching)
            {
                if (!e.Shift && !e.Alt && !e.Control)
                {
                    int k = (int)e.KeyCode;
                    if ((int)e.KeyCode >= 65 && (int)e.KeyCode <= 90)
                        cargarListado(false);
                }
            }
        }

Comprobamos si estamos en modo Búsqueda para que no lance las consultas mientras editamos o añadimos un registro nuevo, y nos apoyamos en la función auxiliar "pulsaTecla", que sólo lanza la búsqueda si se ha pulsado un caracter [a-z].

Finalmente, modificamos el método de listado() para que haga la llamada a la nueva función de búsqueda, y pasarle los campos de texto del Interfaz:

        private void cargarListado(bool paginando)
        {
            this.Cursor = Cursors.WaitCursor;
            dTOPermisoBindingSource.Clear();
            try
            {
                ArrayList permisos = null;
                if (searching)
                {
                    if (!paginando)
                    {
                        resultsBusqueda = CPPermiso.numRecordsBusqueda(permisoTextBox.Text, descripcionTextBox.Text);
                        ctrlPaginacion.paginacion.refresh(resultsBusqueda);
                        ctrlPaginacion.actualizaPaginacion();
                    }
                    permisos = CPPermiso.listado(ctrlPaginacion.paginacion.PageSize, ctrlPaginacion.paginacion.Skip, permisoTextBox.Text, descripcionTextBox.Text);
                }
                else
                {
                    if (results == -1)
                    {
                        results = CPPermiso.numRecords();
                        ctrlPaginacion.paginacion.refresh(results);
                        ctrlPaginacion.actualizaPaginacion();
                    }
                    permisos = CPPermiso.listado(ctrlPaginacion.paginacion.PageSize, ctrlPaginacion.paginacion.Skip);
                }

                if (permisos != null)
                {
                    foreach (DTOPermiso p in permisos)
                    {
                        dTOPermisoBindingSource.Add(p);
                    }
                }
            }
            catch (Exception ex)
            {
                utils.Error(ex.Message);
            }
            this.Cursor = Cursors.Default;
        }

El parámetro bool paginando nos indica si la llamada a la recarga de datos se ha realizado en respuesta a un evento de la paginación (true) o por una búsqueda del usuario. La diferencia está en que en la búsqueda del usuario necesito recalcular el número total de registros, mientras que si respondemos a un evento de paginación, el cálculo ya lo tenemos hecho.

Finalmente, anotar que he añadido un botón por cada campo de texto para permitir al usuario limpiar la búsqueda de los términos introducidos.

Powered by Google Project Hosting