martes, 1 de diciembre de 2009

C# – DataGridView – Búsqueda con Linq

 

El objetivo de este ejemplo es demostrar como realizar búsquedas utilizando Linq y una control datagridview

Algo que debe remarcarse es que en el control datagridview se debe permitir la selección múltiple de la celdas, o sea la propiedad

MultiSelect = true

Además se debe especificar la propiedad SelectionMode = CellSelect

La búsqueda tiene dos procesos, el primero determina que las filas cumplen con el filtro especiado, y una ves obtenidas las filas se procede a determinar que celdas especificas aplican al filtro para seleccionarlas.

El objetivo final es demostrar la facilidad que se obtiene con linq para poder realizar búsquedas en el control DataGridView.

 

List<DataGridViewRow>  rows = (from item in dataGridView1.Rows.Cast<DataGridViewRow>()
                                        let clave = Convert.ToString(item.Cells["clave"].Value ?? string.Empty)
                                        let modelo = Convert.ToString(item.Cells["modelo"].Value ?? string.Empty)
                                        let descripcion = Convert.ToString(item.Cells["descripcion"].Value ?? string.Empty)
                                where clave.Contains(busqueda) || 
                                       modelo.Contains(busqueda) || 
                                       descripcion.Contains(busqueda)
                                select item).ToList<DataGridViewRow>();

foreach (DataGridViewRow row in rows)
 {
     List<DataGridViewCell> cells = (from item in row.Cells.Cast<DataGridViewCell>() 
                                            let cell = Convert.ToString(item.Value)
                                     where cell.Contains(busqueda)
                                     select item).ToList<DataGridViewCell>();

     foreach (DataGridViewCell item in cells)
     {
         item.Selected = true;
     }
     
 }

Como se observa en el ejemplo la primer consulta linq permite determinar que Rows cumplen la condición de búsqueda, para ello se evalúa cada celda de la fila.

Un punto ha remarcar es que el habilitar la propiedad AllowUserToAddRows genera una fila adicional en la grilla que será tratada en la consulta linq como si fuera una adicional, el problema con esta fila es que produce un valor null cuando se intenta utilizar la propiedad Value de la celda. Es por ello que en la consulta se observara el uso del operador ?? el cual ante un null devuelve una cadena vacía.

Igualmente el punto anterior podrías haberse evitado el uso de ?? ya que al convertir a string con Convert.ToString() evita el problema con el null.

Si la idea era seleccionar las filas que cumplan la condición con el primera consulta linq ya era suficiente, pero si se quiere ir especialmente a las celdas, es necesario dar un paso mas.

Es por ello que la segunda consulta trabaja a nivel de las celdas, tomando cada fila que cumple con la condición y determinado que celdas especifica también lo hace, para luego marcarla como seleccionada.

 

[C#]
[VB.NET]

44 comentarios:

  1. Hola,

    ¿por qué haces dataGridView1.Rows.Cast()?, es para que item se carge con una colección IEnumerable de DataGridViewRow.
    ¿Por que item no se puede cargar directamente con la colección de DataGridRow de Rows?.

    Muchas gracias y un saludo.

    ResponderEliminar
  2. hola sr leandro tuttini , no se si me puede ayudar tengo un problemita estoy haciendo un formulario donde cargo un archivo de texto en un datagridview esa informacion la quiero almacenar en una base de datos como le hago???

    ResponderEliminar
  3. hola ATP, disculpa la demora en contesta, es que no habia recibido alerta de tu pregunta.

    ¿por qué haces dataGridView1.Rows.Cast()?

    El Cast() permite adaptar la coleccion de filas de la grilla para poder ser consultada por Linq.

    Recuerda que Linq necesitas de ciertas interfaces o tipo de objetos para que sea posible aplicar la consulta, no todas las listas se pueden consultar con linq, con el DataSet sucede lo mismo hay que adaptar estas colecciones para poder ser trabjadas en Linq.

    ¿Por que item no se puede cargar directamente con la colección de DataGridRow de Rows?

    No se puede cargar directamente porque las colecciones o listas que se usan son diferentes.
    El DataGridView esta usando un DataGridViewRowCollection, mientras que la query en Linq no puede usar este tipo de datos, por defecto sino se usa el ToList<>(), estaria devolviendo un
    IEnumerable

    Hay que iterar por cada registro para trabajarlo.

    saludos

    ResponderEliminar
  4. hola julio

    Dos puntos importantes que no has mencionado es que lenguaje usar para programar y que base de datos piensas usar.

    por lo pronto puedo mostrarte un ejmeplo en C# y Sql Server:


    using (SqlConnection conn = new SqlConnection(""))
    {
    conn.Open();

    string query = @"INSERT INTO Tabla (descripcion, stock, precio)
    VALUES (@desc, @stock, @precio)";

    SqlCommand cmd = new SqlCommand(query, conn);

    foreach(DataGridViewRow row in dataGridView1.Rows)
    {
    cmd.Parameters.Clear();
    cmd.Parameters.AddWithValue("@desc", Convert.ToString(row.Cells["Descripcion"].Value));
    cmd.Parameters.AddWithValue("@stock", Convert.ToInt32(row.Cells["Stock_ID"].Value));
    cmd.Parameters.AddWithValue("@precio", Convert.ToDecimal(row.Cells["Precio"].Value));

    cmd.ExecuteNonQuery();
    }
    }


    Como veras se recorrer cada uno de las filas, y en cada vuelta se tomas los datos de las celdas para pasarselas al parametro de la query

    saludos

    ResponderEliminar
  5. Hola.

    Estoy tratando de hacer el ejemplo pero la colección Rows no tiene el miembro Cast.
    Cambié el destino del proyecto (a Framework 3.5), agregué a mano las referencias a Linq dentro del proyecto y dentro del código pero no me sale igualmente.

    Tenés idea lo que puede ser?

    Gracias.

    ResponderEliminar
  6. hola Diego

    Realiza una prueba, crea un proyecto nuevo que use .net 3.5

    Luego intenta armar un ejemplo simple de linq que use una grilla hje intenta acceder a ese metodo que no puedes utilizar, valida que alli puedas.

    Si puedes entonces claramente el otro proyecto al cambia de version de framework algo falto.

    Pero primero descartemos que puedes acceder a este metodo desde un proyecto creado con .net 3.5 desde su comienzo.

    saludos

    ResponderEliminar
  7. Este comentario ha sido eliminado por el autor.

    ResponderEliminar
  8. Hola Leandro, tengo algunas dudas, si realizado la busqueda desde un formulario dependiente del formulario donde se encuentra la grilla no me muestra la celda encontrada, mi codigo en el formulario dependiente es el siguiente:

    //Determinar que filas cumplen un filtro determinado
    List rows = (from item in ((PantallaConsulta)this.Owner).dgvBuscar.Rows.Cast()
    let nom_mod = Convert.ToString(item.Cells["Nom_Mod"].Value ?? string.Empty)
    let descripcion_moda = Convert.ToString(item.Cells["Descripcion_Moda"].Value ?? string.Empty)
    let descripcion_segm = Convert.ToString(item.Cells["Descripcion_Segm"].Value ?? string.Empty)
    where nom_mod.Contains(busqueda) ||
    descripcion_moda.Contains(busqueda) ||
    descripcion_segm.Contains(busqueda)
    select item).ToList();

    //MessageBox.Show(rows.ToString());
    //Obtenidas las filas se procede a determinar que celdas especificas aplican al filtro para seleccionarlas
    foreach (DataGridViewRow row in rows)
    {
    List cells = (from item in row.Cells.Cast()
    let cell = Convert.ToString(item.Value)
    where cell.Contains(busqueda)
    select item).ToList();

    foreach (DataGridViewCell item in cells)
    {
    item.Selected = true;
    }
    }

    ¿Que cambio habria que realizar para que me seleccione la fila en vez de la celda?

    Para que el texto escrito no tenga que coincidir completamente, que sea una subcadena del contenido de la celda ¿tiene solucion?

    Son muchas preguntas, lo siento, responde si puedes y muchas gracias por tu tiempo.

    ResponderEliminar
  9. Hola Leandro
    Primero que nada darte las gracias por la ayuda inestimable.
    La pregunta es, se puede utilizar linq con visual basic 2008?, lo digo por que yo no estoy muy puesto en el c#, ni tampoco en linq, pero me gustaría aprender como va.
    Gracias por adelantado.
    Jota69

    ResponderEliminar
  10. hola elChele

    claro se puede usar Linq con VS.NET, pero recuerda crear el proyecto usando .net 3.5

    Introducción a LINQ en Visual Basic

    saludos

    ResponderEliminar
  11. HOLA LEANDRO PORFAVOR AYUDAME,
    TENGO UN SISTEMA PARA UNA TIENDA DE VIDEOJUEGO TENGO UNA DATAGRID DONDE CARGUE MI BASE DE DATOS, YA PUEDO GUARDAR MAS DATOS PERO NO SE ELIMINARLOS, POR FAVOR AYUDAME A ELIMINAR UNA FILA DE DATOS CON ALGUN CODIGO EN C# PARA VISUAL STUDIO 2010

    ResponderEliminar
  12. hola de diseño

    bien para eliminar un registro podrias usar


    private void btnEliminar_Click()
    {
    DataGridViewRow row = DataGridView1.CurrentRow;

    if(row == null)
    {
    MessageGox.Show("debe seelccionar una fila para eliminar");
    return;
    }

    using (SqlConnection conn = new SqlConnection("connection string")
    {

    string sql = @"DELETE FROM NombreTabla WHERE campo = @param";

    SqlCommand cmd = new SqlCommand(sql, conn);
    cmd.Parameters.AddWithValue("@param", Convert.ToInt32(row.Cells["colnombre"].Value));
    cmd.ExecuteNonQuery();
    }
    }

    por supuesto como no indicaste que en que lenguaje programaste puse el ejemplo en c#
    pero peude convertirlo usando

    convert tool

    saludos

    ResponderEliminar
  13. tengo una datagrid con muchos registros, la consulta al datagrid me funciona perfectamente, pero tengo un problema: si el registro selecionado esta al final de datagridview me toca desplazarme hasta abajo o hasta encontrarlo, como hago para que me lo muestre en pantalla sin necesidad de desplazarme. gracias

    ResponderEliminar
  14. hola dubier

    no has intentado usar la propiedad

    FirstDisplayedScrollingRowIndex

    si le pones el index de la fila seleccionada desplazara el grid a ese lugar

    saludos

    ResponderEliminar
  15. Leandro, me podrias indicar como capturo el index? gracias. o cual es la variable DataGridView1.FirstDisplayedScrollingRowIndex = ??

    ResponderEliminar
  16. hola dubier

    cuando trabajas con el DataGridViewRow obtienes el index de esa row en concreto del datagridview

    imagino a ese DataGridViewRow lo seleccionas con Selected= true, bueno ademas de eso extraes el index y lo asignas a la propiedad.

    DataGridView1.FirstDisplayedScrollingRowIndex = row.Index;

    saludos

    ResponderEliminar
  17. hola leandro, estoy buscando un dato en la columna [producto] y este me busca perfectamente en esa columna. solo en esa, pero al hacer foreach para selecionar los item, estos me selecionan las demas columnas donde cioncidan las palabras de busqueda. me podrias ayudar? ya que solo quiero que se me selecionen solo en [producto]. gracias

    ResponderEliminar
  18. hola dubier

    un efecto algo extraño el que comentas

    Como es que estas aplicando el filtro?

    y como es que recorres los items

    quizas sea dificil poner aqui el codigo, podrias plantear la consulta en el foro y la seguimos alli

    foro msdn

    saludos

    ResponderEliminar
  19. como puedo lograr la búsqueda, sin que afecte como esté escrito al registro: "Silla", "silla", "SILLA". Gracias de antemano

    ResponderEliminar
  20. hola jesusM

    para comparar usas el Contains() ?


    porque podrias hacer:

    campos.ToLower().Contains(texto.ToLower())

    o sea llevas todo a minusculas y comparas, asi ya no afectara


    saludos

    ResponderEliminar
  21. Que tal, creo que no me di a entender, sí uso el Contains, pero en las rejillas de mi DataG al estár relacionada con mi bd no siempre tienen un formato definido, y pues el detalle es que para la búsqueda, usando el parámetro de la caja de texto no coincide con lo que tengo en el Grid, como es llenado por varias persona, a veces escriben silla o SILLA como te explique anteriormente, como puedo solucionar ese dilema?

    ResponderEliminar
  22. hola jesusM

    pero estas realizando una busqueda con linq ?

    porque si es asi al llevarlo a minuscula a ambos texto a minuscula ambos texto coinciden y el Contains dira que son iguales

    sino la otra es usa IndexOf()

    String.IndexOf Method (String, StringComparison)

    o sea en el link usarias en el where

    item.IndexOf(txtBuscar.Text, StringComparison.InvariantCultureIgnoreCase) > 0

    o sea buscarias si devuelve uan posocion, si lo hace es porque lo contiene

    saludos

    ResponderEliminar
  23. hola como va, leandro como puedo hacer para mostrar en un datagridview los datos de tres tablas todo esto con linq to sql la verdad que e estado lidiando por mucho y no logro hacerlo desde ya muchas gracias por tus aportes.

    ResponderEliminar
  24. hola luis

    podrias crear una entidad auziliar con las propiedades que quieras visualizar en el grid, estas propiedades seria de varias de las entidades

    public class EntidadAux{

    public string ProdId {get;set;}
    public string ProdDesc {get;set;}
    public string CatId {get;set;}
    public string CatDesc {get;set;}
    }

    entonces con linq lo que ahces en el select es devolver una lista de EntidadAux, asignando la info tomada de un poco de cada entidad que relacionaste mediante un join

    saludos

    ResponderEliminar
  25. hola leandro ya te había comentado este problema pero te lo vuelvo a explicar, he descargado el ejemplo tuyo en c#, y quiero buscar en el ejemplo tuyo la palabra LI esa palabra esta en Modelo y Descripción. ósea está LI10V OS y LICUADORA DE 10 VEL. OSTER ahora le quito las siguientes líneas solo para que me busque ese dato solo en la columna modelo.
    let descripcion = Convert.ToString(item.Cells["descripcion"].Value ?? string.Empty)

    || descripcion.Contains(busqueda)
    Con esto solo me debería buscar en la columna modelo, pero no todavía me resalta las dos filas. Gracias por la ayuda.

    ResponderEliminar
  26. hola dubier

    pero cuando cometas que tienes LI10V OS y LICUADORA DE 10 VEL. OSTER

    en que columna estan estos dos textos que mencionas ? no estaran en la columna descripcion, no ?

    saludos

    ResponderEliminar
  27. hola Leandro te ago una pregunta
    como se, o de donde saco el valor de
    SelectedCells[] tengo esto.
    txtCuit.Text = selCli.dtgClientes.SelectedCells[4].Value.ToString();
    txtCliente.Text = selCli.dtgClientes.SelectedCells[1].Value.ToString();

    no se de donde tomo el valor cual es la referencia se que se lo asigno al txtbox desde el datagrid pero como se a a que valor corresponde. desde ya muchas gracias

    ResponderEliminar
  28. hola luis

    el SelectedCells deberis usarlo desde un evento CellClick o CellDoubleClick, del datagridview

    en el contexto de estos eventos podras saber cual es la delda seleccionada

    si lo haces desde otros eventos imaginemos un click de un boton, debes tener cuidado porque esa propiedad podria venir en null sino hay nada seleccionado

    saludos

    ResponderEliminar
  29. Leandro un saludo, decir q tus ejemplos me han ayudado mucho, te planteo mi inconveniente, yo cargo mi datagrid con un archivo de excel de al menos unos dos mil registros en 3 columnas, con tu codigo me realiza la busqueda .. pero digamos en el datagridview se muestran registros hasta el número 20 y para ver el resto uso el scroll o la barra lateral izq, digamos doy a buscar el registro en la fila numero 300 y me lo encuentra pero no me desplaza el datagridview hasta alla tengo q usar la barra lateral izq. ¿como haría para que me desplace a la celda cuando encuentra el dato?

    ResponderEliminar
  30. hola Nelson

    podrias usar el

    FirstDisplayedScrollingRowIndex


    asignando el index de la row que has encontrado desplaza directo el scroll a esa posicion

    saludos

    ResponderEliminar
  31. Leandro gracias por tus aportes son de mucha ayuda.

    como adapto este código para que solo me muestre en el datagridview los registro que coinciden con la búsqueda.

    Gracias

    ResponderEliminar
  32. hola Israel

    podrias aplicar linq no sobre el datagridview directamente sino sobre los datos de este tiene asignado

    si usas un datatable el linq lo puedes aplicar sobre este para filtrar los datos y luego asignarlos al grid

    saludos

    ResponderEliminar
  33. Buenas noches, Se puede ingresar en un DataGridView --> "Persona.tipoPersona.Descripcion" no se la forma de hacerlo porfavor si podria ayudarme. Si pude con Persona.nombre Persona.direccion .....

    ResponderEliminar
  34. Hola Leandro !!!
    Buen día ... Apliqué el ejemplo para filtrar filas en el datagridview... Yo quisiera que al filtrar, las filas que no coinciden no se vean... Es posible hacerlo sin usar datatable? O sea con los datos que estan cargados en el datagrid?
    Salu2! y gracias por tus aportes que nos salvan dia a dia. :)

    ResponderEliminar
  35. hola Migue

    no veo como podrias filtrar directo en el grid sin tener una lista

    podrias usar un datatable o crear una lista generica con uan clase que tu definas

    de esta forma aplcias linq de forms simple y luego el resultado lo muestras en el grid
    pero podrias tener en una variable la lista original asi no perder los datos y filtrar siempre sobre estos

    saludos

    ResponderEliminar
  36. Sos un groso... No hay más palabras para definirte... Yo no sé como Bill no te lleva a laburar al Norte...

    Salu2! genio...

    ResponderEliminar
  37. Hola Leandro, esto es justo lo que necesitaba, muchas gracias funciona a la perfección, solo un pequeño detalle. ¿Hay alguna función para hacer que si la grilla es demasiado grande desplace la vista hacia el ítem seleccionado? Muchas Gracias !

    ResponderEliminar
  38. Estimados, necesito una sentencia de LINQ para una aplicación en VB.Net.
    Agradezco mucho si alguien me puede ayudar.

    Tengo las siguientes clases
    Clase AcumuladosRegion
    CodSuc
    TotalVenta

    Clase Empresa
    CodEmp
    Nombre
    Pais
    Acumulados de tipo collection (colección de la clase AcumuladosRegion )
    En un formulario tengo una Lista de Empresas LisEmpresas de tipo List (of Empresa)
    A modo de ejemplo, los datos son
    CodEmp Pais Nombre Acumulados
    1 ARG Empresa1 RegionNorte=35 RegionSur=20 RegionEste=45 RegionOeste=15
    2 ARG Empresa2 RegionNorte=10 RegionSur=10 RegionEste=65 RegionOeste=10
    3 PERU Empresa3 RegionNorte=15 RegionSur=20 RegionEste=20 RegionOeste=10
    4 PERU Empresa4 RegionNorte=10 RegionSur=25 RegionEste=30 RegionOeste=30
    5 PERU Empresa5 RegionNorte=15 RegionSur=10 RegionEste=30 RegionOeste=10

    Mediante LinQ necesito obtener el siguiente resultado
    Pais TotalPorRegion
    ARG RegionNorte=45
    ARG RegionSur=30
    ARG RegionEste=110
    ARG RegionOeste=25
    PERU RegionNorte=40
    PERU RegionSur=55
    PERU RegionEste=80
    PERU RegionOeste=50

    Es decir, una SENTENCIA LINQ que tome de LisEmpresas, agrupe por país y genere total de cada región

    ResponderEliminar
  39. hola Horacio

    demasiado grande con respecto al form ?

    no entendi lo de la vista y el item

    saludos

    ResponderEliminar
  40. hola Andres

    podrias usar el grouo by de linq
    algo como ser

    Dim result = From emp In LisEmpresas
    Group By Key = emp.Pais Into Group
    Select New With {
    .Pais = Key,
    .RegionNorte = Group.Sum(Function(x) x.RegionNorte),
    .RegionSur = Group.Sum(Function(x) x.RegionSur),
    .RegionEste = Group.Sum(Function(x) x. RegionEste),
    .RegionOeste = Group.Sum(Function(x) x. RegionOeste),
    }


    Group By Clause (Visual Basic)

    saludos

    ResponderEliminar
  41. hola leandro intente descargar los ejemplos completos y los anlaces estan caidos, sera que puedes resubirlos :) gracias de antemano

    ResponderEliminar
  42. hola Leandro!! te queria comentar que probe el codigo que subiste para filtrar datos de un datagridview pero me marca un error. En el datagriview me sale lo siguiente
    contextMenuStr DefaultCellStyle DividerHeigth ErrorText Heigth ReadOnly osea todas esas columnas y otras mas en blanco

    ResponderEliminar