none
Modificar una celda o eliminarla y que se guarde el cambio en la BD RRS feed

  • Pregunta

  • Buenas, tengo una pregunta, yo tengo una datagridView, y al hacerle doble click a una celda, podes modificarla y ponerle otro contenido. O bien al apretar la tecla SUPR se borra 1 fila, pero mi pregunta es como puedo hacer yo para guardar esos cambios en mi base de datos.

    O sea yo ya tengo una clase que la llamo y lo guarda en la base de datos, lo que quiero saber es como/donde llamar a esa clase/metodo cuando termino de modificar una celda, o bien suprimir una fila entera.

    Para que quede mas claro, yo tengo el metodo Producto.Modificar y Producto.Eliminar. Donde coloco esos metodos, para que cuando elimine una fila de la grilla o modifique una CELDA de la grilla se realicen.

    Gracias, saludos


    .3




    • Editado ShiraGG viernes, 16 de noviembre de 2012 3:10
    viernes, 16 de noviembre de 2012 2:48

Todas las respuestas

  • El asunto puede abordarse de un par de maneras y supongo que depende de la preferencia.

    Primero que nada voy a aclarar la situación:

    1. Asumo que hay un modelo de negocio que incluye la clase Producto, donde hay instancias de esta clase representando los registros en base de datos.
    2. Asumo que la clase Producto tiene un constructor público sin parámetros.
    3. Asumo que existe en la clase Producto una propiedad llamada ID que es el identificador único de producto, y que es de sólo lectura (no hay set público).
    4. Asumo que la capa de negocio provee una colección accedida por posición, como List<Producto>, o bien que se crea una.
    5. Asumo que esta colección se usa como DataSource de un objeto BindingSource, y este objeto BindingSource, es a su vez el DataSource del DGV.

    Bajo las suposiciones anteriores, la inserción de nuevas filas en el DGV automáticamente crea nuevos objetos Producto con ID cero (si es que es numérico el ID) que son agregados a la lista que es el DataSource del componente BindingSource, y automáticamente elimina dichos registros de la misma lista cuando se borran filas.  Esto es bonito porque es automático, pero usted necesita meter la nariz un poco para transferir la acción a la base de datos.

    La forma más directa que veo es usar el evento UserDeletingRow del DGV para informarse cuando una fila es eliminada.  El parámetro e tendrá una propiedad de tipo DataGridViewRow con el índice de fila borrado.  En este momento la lista no ha sido modificada, así que puede aprovechar este momento para obtener el objeto Producto (ya que la lista que se obtuvo puede devolver objetos por índice de posición).  Podrá entonces llamar a su método Producto.Eliminar().  Eso resuelve la parte de eliminación.

    Para resolver la otra parte el asunto es más complejo.  Hay un evento en el DGV llamado CellEndEdit.  Este le informará cuando el usuario termina de editar una celda.  ¿Pero eso quiere decir que ya terminó de editar el registro completo?  La respuesta es que no se sabe a ciencia cierta.  Sería entonces imprudente actualizar el registro cada vez que el usuario termina de editar una celda.  Le recomiendo entonces hacer un botón de Guardar cambios que guardaría los registros cambiados y los registros nuevos.  Así las sentencias al servidor SQL se minimizan.

    ¿Cómo almacenar la información necesaria para el botón de guardar?  Sencillo en principio:  Cree un Dictionary<int, Producto> (asumiendo que el tipo de ID es int) a nivel de formulario (nivel de clase) que vaya acumulando los objetos Producto cuyas propiedades han sido modificadas.  Puede detectar estos cambios de varias maneras, siendo una de ellas implementar INotifyPropertyChanged en su clase Producto.  Pero bueno, si no, el componente BindingSource tiene el event ListChanged.  Su parámetro e le dirá qué tipo de cambio sufrió la lista y el índice.  En el caso de un usuario modificando un valor, el tipo de cambio será ItemChanged y e.NewIndex será el índice del item modificado.  Use este índice para obtener el objeto Producto y adicionarlo al diccionario (si es que aún no ha sido agregado).

    Esa sería la idea básica.  Sin embargo, esto se complica cuando se insertan y borran registros (filas) nuevos sin guardar los cambios en base de datos pues todos los nuevos registros tienen un ID de cero cuando no han sido guardados a base de datos, lo que hace que el diccionario no pueda contener más que un item nuevo a la vez.  También está el asunto de determinar correctamente dentro de la colección auxiliar el elemento que corresponde a un elemento borrado para no guardar sus cambios.

    En fin, el proceso se complica, y es justamente lo que nos lleva a la otra forma de hacerlo, que en mi opinión es la mejor forma pues no padece de ninguno de los problemas descritos, pero toma más tiempo en "prepararla".

    Lo ideal es que cada objeto Producto sepa si ha sido modificado.  Normalmente los objetos entidades tienen una propiedad que indica si han sido cambiados.  En inglés suele llamarse IsDirty.  Si IsDirty == true, entonces  el objeto sufrió cambios.  ¿Cómo se sabe cuando pasa esto?  Cuando uno o más propiedades cambian de valor.  Esto es fácil de implementar con INotifyPropertyChanged.  Por lo tanto:  Implemente INotifyPropertyChanged y cuando cambie una propiedad que se persiste en base de datos, cambie el valor de IsDirty a true.

    Con INotifyPropertyChanged + IsDirty, el botón de guardar cambios no necesita de una colección auxiliar.  Simplemente deberá recorrer la lista completa y determinar qué registros tienen IsDirty en true.  Todos aquellos que la tengan se usan con Producto.Modificar() (si es que tienen un ID distinto de cero), o bien algo como Producto.Nuevo() si es que son nuevos.  En lo personal tendría un único método llamado Producto.Grabar() y sería inteligente:  Haría un UPDATE si el ID es distinto de cero o un INSERT si el ID es cero.


    Jose R. MCP
    Code Samples

    viernes, 16 de noviembre de 2012 5:19
    Moderador
  • webJose cuando llego a mi casa lo pruebo

    Gracias, excelente explicacion.


    .3

    viernes, 16 de noviembre de 2012 11:20
  • Bueno llegue a casa e intente entender la logica de lo que vos me decis, que si bien esta muy bien explicado, yo soy novato en esto de la programaciòn y me cuesta llevar adelante esa explicacion a la hora de programar, por lo cual te pido si no me podes ayudar un poco vos, por lo menos tirarme algo del codigo.

    Todos los puntos que mencionas del 1 al 5 estàn bien asumidos, por que es asì.

    Saludos, mil gracias.


    .3


    • Editado ShiraGG viernes, 16 de noviembre de 2012 21:31
    viernes, 16 de noviembre de 2012 21:31
  • Pues existen muchas implementaciones de INotifyPropertyChanged en la web, incluyendo una en el enlace que se muestra en mi firma (Code Samples).  Pero bueno, esa en particular es compleja y probablemente no es para novatos.  Trataré entonces de explicar con un ejemplo minimalista.

    Actualmente imagino que su clase Producto luce así:

    public class Producto
    {
        public int ID { get; set; }
        public string Nombre { get; set; }
        //Tal vez otras propiedades.  Abrevio aquí.
    }

    Una clase POCO típica.  Hay que transformarla para que utilice INotifyPropertyChanged y que tenga una propiedad que nos indique si está "sucia", o sea, si ha sido modificada.  Como seguramente usted tiene otras clases que se beneficiarían de lo mismo, haré una clase base Entidad:

    public class Entidad : INotifyPropertyChanged
    {
        public event PropertyChangedEventHandler PropertyChanged;
        //Este método debe ser protected para que las clases especializadas puedan invocarlo.
        protected void RaisePropertyChanged(string propertyName)
        {
            PropertyChangedEventHandler ev = PropertyChanged;
            if (ev != null)
            {
                ev(this, new PropertyChangedEventArgs(propertyName));
            }
        }
    
        //Esta es la propiedad adicional que le menciono:
        private bool m_IsDirty;
        public bool IsDirty
        {
            get
            {
                return m_IsDirty;
            }
            protected set
            {
                m_IsDirty = value;
                RaisePropertyChanged("IsDirty");
            }
        }
    }

    Como verá, tenemos una clase base que implementa INotifyPropertyChanged y además nos provee de una propiedad llamada IsDirty cuyo set es protected para que no sea posible alterar su valor desde fuera de la clase.

    Reescribimos Producto como:

    public class Producto : Entidad
    {
        private int m_ID;
        public int ID
        {
            get
            {
                return m_ID;
            }
            protected set
            {
                m_ID = value;
                RaisePropertyChanged("ID");
                IsDirty = true;
            }
        }
        private string m_Nombre;
        public string Nombre
        {
            get
            {
                return m_Nombre;
            }
            set
            {
                m_Nombre = value;
                RaisePropertyChanged("Nombre");
                IsDirty = true;
            }
        }
    }

    De esta manera tenemos el mecanismo base listo.

    Un botón de Windows Forms de Guardar cambios tendría entonces un código similar a este:

    protected void btnGuardar_Click(object sender, EventArgs e)
    {
        List<Producto> prods = bindingSource1.DataSource as List<Producto>;
        foreach (Producto p in prods)
        {
            Producto.Guardar(p);
        }
    }

    El método Producto.Guardar sería estático y sería algo así:

    public static bool Guardar(Producto p)
    {
        //Si no ha sufrido cambios, no lo guardamos.
        if (!p.IsDirty) return true;
        if (p.ID == 0)
        {
            //El producto es nuevo.  Hacer un INSERT.
            ...
        }
        else
        {
            //El producto ya se encuentra en base de datos.  Hacer un UPDATE.
            ...
        }
        return true;
    }

    Nótese cómo se devuelve un valor booleano del método Guardar().  Nos sirve para saber si un item ha sido guardado exitosamente.  Nos sirve especialmente para eliminar el item de la lista asignada al DGV en caso de ser un item nuevo.  Así el usuario se dará cuenta que el registro no fue grabado.  Modificamos entonces el evento Click del botón:

    protected void btnGuardar_Click(object sender, EventArgs e)
    {
        List<Producto> prods = bindingSource1.DataSource as List<Producto>;
        List<Producto> rechazos = new List<Producto>();
        foreach (Producto p in prods)
        {
            if (!Producto.Guardar(p))
            {
                //El producto no pudo guardarse.  Informe al usuario:
                MessageBox.Show("Error: ...", ...);
                //Almacenar en la colección de rechazos.
                rechazos.Add(p);
            }
        }
        //Procesar los rechazos.  En particular, si era un registro nuevo pienso que puede eliminarse de la lista:
        foreach(Producto p in rechazos)
        {
            //Como estamos usando la colección asignada como DataSource,
            //esto hace desaparecer al registro en el DGV automáticamente:
            if (p.ID == 0) prods.Remove(p);
        }
    }

    Y esa sería una buena forma de hacer esto y que además aprovecha las bondades de la infraestructura de vinculación a datos de Windows Forms.

    IMPORTANTE:  No está todo aquí.  Por ejemplo, el método Producto.Guardar debe setear IsDirty a false después de que graba exitosamente el registro.  De otra forma el registro queda "sucio" y se grabará una y otra y otra vez cada vez que se presione el botón de guardar los cambios.

    Otro detalle importante es que cuando se guarda un registro nuevo, el nuevo ID lo dicta la base de datos (típico comportamiento de una columna IDENTITY).  En ese caso durante el proceso de INSERT debe recuperar este valor y asignarlo a la propiedad ID.  Como el objeto está vinculado al DGV, notará placenteramente cómo el DGV automáticamente mostrará el nuevo ID en cuanto la propiedad ID cambia, esto gracias a INotifyPropertyChanged.

    En general, revise esto y cree su propia versión.


    Jose R. MCP
    Code Samples


    viernes, 16 de noviembre de 2012 22:42
    Moderador
  • Pues existen muchas implementaciones de INotifyPropertyChanged en la web, incluyendo una en el enlace que se muestra en mi firma (Code Samples).  Pero bueno, esa en particular es compleja y probablemente no es para novatos.  Trataré entonces de explicar con un ejemplo minimalista.

    Actualmente imagino que su clase Producto luce así:

    public class Producto
    {
        public int ID { get; set; }
        public string Nombre { get; set; }
        //Tal vez otras propiedades.  Abrevio aquí.
    }

    Una clase POCO típica.  Hay que transformarla para que utilice INotifyPropertyChanged y que tenga una propiedad que nos indique si está "sucia", o sea, si ha sido modificada.  Como seguramente usted tiene otras clases que se beneficiarían de lo mismo, haré una clase base Entidad:

    public class Entidad : INotifyPropertyChanged
    {
        public event PropertyChangedEventHandler PropertyChanged;
        //Este método debe ser protected para que las clases especializadas puedan invocarlo.
        protected void RaisePropertyChanged(string propertyName)
        {
            PropertyChangedEventHandler ev = PropertyChanged;
            if (ev != null)
            {
                ev(this, new PropertyChangedEventArgs(propertyName));
            }
        }
    
        //Esta es la propiedad adicional que le menciono:
        private bool m_IsDirty;
        public bool IsDirty
        {
            get
            {
                return m_IsDirty;
            }
            protected set
            {
                m_IsDirty = value;
                RaisePropertyChanged("IsDirty");
            }
        }
    }

    Como verá, tenemos una clase base que implementa INotifyPropertyChanged y además nos provee de una propiedad llamada IsDirty cuyo set es protected para que no sea posible alterar su valor desde fuera de la clase.

    Reescribimos Producto como:

    public class Producto : Entidad
    {
        private int m_ID;
        public int ID
        {
            get
            {
                return m_ID;
            }
            protected set
            {
                m_ID = value;
                RaisePropertyChanged("ID");
                IsDirty = true;
            }
        }
        private string m_Nombre;
        public string Nombre
        {
            get
            {
                return m_Nombre;
            }
            set
            {
                m_Nombre = value;
                RaisePropertyChanged("Nombre");
                IsDirty = true;
            }
        }
    }

    De esta manera tenemos el mecanismo base listo.

    Un botón de Windows Forms de Guardar cambios tendría entonces un código similar a este:

    protected void btnGuardar_Click(object sender, EventArgs e)
    {
        List<Producto> prods = bindingSource1.DataSource as List<Producto>;
        foreach (Producto p in prods)
        {
            Producto.Guardar(p);
        }
    }

    El método Producto.Guardar sería estático y sería algo así:

    public static bool Guardar(Producto p)
    {
        //Si no ha sufrido cambios, no lo guardamos.
        if (!p.IsDirty) return true;
        if (p.ID == 0)
        {
            //El producto es nuevo.  Hacer un INSERT.
            ...
        }
        else
        {
            //El producto ya se encuentra en base de datos.  Hacer un UPDATE.
            ...
        }
        return true;
    }

    Nótese cómo se devuelve un valor booleano del método Guardar().  Nos sirve para saber si un item ha sido guardado exitosamente.  Nos sirve especialmente para eliminar el item de la lista asignada al DGV en caso de ser un item nuevo.  Así el usuario se dará cuenta que el registro no fue grabado.  Modificamos entonces el evento Click del botón:

    protected void btnGuardar_Click(object sender, EventArgs e)
    {
        List<Producto> prods = bindingSource1.DataSource as List<Producto>;
        List<Producto> rechazos = new List<Producto>();
        foreach (Producto p in prods)
        {
            if (!Producto.Guardar(p))
            {
                //El producto no pudo guardarse.  Informe al usuario:
                MessageBox.Show("Error: ...", ...);
                //Almacenar en la colección de rechazos.
                rechazos.Add(p);
            }
        }
        //Procesar los rechazos.  En particular, si era un registro nuevo pienso que puede eliminarse de la lista:
        foreach(Producto p in rechazos)
        {
            //Como estamos usando la colección asignada como DataSource,
            //esto hace desaparecer al registro en el DGV automáticamente:
            if (p.ID == 0) prods.Remove(p);
        }
    }

    Y esa sería una buena forma de hacer esto y que además aprovecha las bondades de la infraestructura de vinculación a datos de Windows Forms.

    IMPORTANTE:  No está todo aquí.  Por ejemplo, el método Producto.Guardar debe setear IsDirty a false después de que graba exitosamente el registro.  De otra forma el registro queda "sucio" y se grabará una y otra y otra vez cada vez que se presione el botón de guardar los cambios.

    Otro detalle importante es que cuando se guarda un registro nuevo, el nuevo ID lo dicta la base de datos (típico comportamiento de una columna IDENTITY).  En ese caso durante el proceso de INSERT debe recuperar este valor y asignarlo a la propiedad ID.  Como el objeto está vinculado al DGV, notará placenteramente cómo el DGV automáticamente mostrará el nuevo ID en cuanto la propiedad ID cambia, esto gracias a INotifyPropertyChanged.

    En general, revise esto y cree su propia versión.


    Jose R. MCP
    Code Samples


    Muchisimas gracias viejo, ahora si que entendi bien la lògica.

    Se agradece de encerio! Cualquier otra duda consulto, ya mismo me pongo a crear mi version.


    .3


    • Editado ShiraGG viernes, 16 de noviembre de 2012 23:12
    viernes, 16 de noviembre de 2012 23:12