domingo, 29 de noviembre de 2009

C# – ADO.NET – Parte 3 – Actualización Información Sql Server

 

Introducción

En esta oportunidad avanzaremos un poco mas allá en el uso de ADO.NET para el trabajo con la información, es por eso que no solo recuperaremos datos, sino que también agregaremos nuevos registros, actualizaremos los existentes y eliminaremos aquellos que ya no sean necesarios

Este post es la continuación de un post anterior:

C# – ADO.NET - Parte 1 - Recuperar Información Sql Server

es por ello que ciertos aspectos serán pasados por alto, pues fueron explicados con mas detalle en la primer parte.

1 – Creación nueva entidad (Insert)

Para esta operación se ha creando en la clase ContactosDAL, varias funciones que encapsulan la funcionalidad.

Encontraran un método de nombre Save(), el cual es el único visibilidad publica en la clase que permite la actualziacion de la entidad, y es por este que se decide si la operación a llevar a cabo será en definitiva un Update() o un Insert()

public static ContactoEntity Save(ContactoEntity contacto)
{

    if (string.IsNullOrEmpty(contacto.Nombre))
        throw new BusinessException(Messages.MSG1002);

    if (string.IsNullOrEmpty(contacto.Apellido))
        throw new BusinessException(Messages.MSG1003);

    if (Exists(contacto))
        return Update(contacto);
    else
        return Insert(contacto);
}

 

El método Save() es bastante simple, hace uso de una función interna de nombre Exists(), para decidir si el contacto como entidad enviado desde la presentación es uno nuevo, o si existe y hay que actualizarlo

private static bool Exists(ContactoEntity contacto)
{
    if (contacto == null)
        throw new BusinessException(Messages.ERR1001); 

    return Exists(contacto.IdContacto);
}

private static bool Exists(int Id)
{
    string sql = @"SELECT COUNT(*)
                      FROM Contacto
                      WHERE IdContacto = @Id";


    using (SqlConnection conn = new SqlConnection(ConfigurationManager.ConnectionStrings["default"].ToString()))
    {

        SqlCommand command = new SqlCommand(sql, conn);
        command.Parameters.AddWithValue("Id", Id);

        conn.Open();

        int count = Convert.ToInt32(command.ExecuteScalar());

        if (count == 0)
            return false;
        else
            return true;
        
    }
}

La estructura de conexión y creación de objetos se respeta al igual que se comento en el primer articulo escrito, la diferencia en este es que se hace uso del  ExecuteScalar del objeto SqlCommand.

Como se observara este método ideal para estos casos, ya que es utilizado cuando se necesita recuperar un solo valor de la base de datos.

El método ExecuteScalar:

- Ejecuta la consulta y devuelve la primera columna de la primera fila del conjunto de resultados que devuelve la consulta. Se omiten todas las demás columnas y filas.
- Utilice el método ExecuteScalar para recuperar un único valor (por ejemplo, un valor agregado) de una base de datos.

En este caso se esta preguntando cuantos registro hay con el id de contacto que le pasamos como parámetro, si devuelve cero quiere decir que no existe el contacto, en cambio un valor de uno indica que existe por lo tanto debe proceder con la actualización.

Como se notara el método Exists() esta sobrecargado, o sea tiene dos definiciones del mismo pero con distintos parámetros, uno recibe la instancia de un contacto, mientras que al segundo solo el id, es mas el primero termina haciendo uso de siguiente para reutilizar la funcionalidad, y solo codificarla en uno de ellos.

Una vez determinado si existe o no el registro se proceder con las operaciones, por ejemplo el Insert()

private static ContactoEntity Insert(ContactoEntity contacto)
{
    string sql = @"INSERT INTO Contacto ([IdContacto]
                          ,[Nombre]
                          ,[Apellido]
                          ,[FechaNacimiento]
                          ,[Localidad]
                          ,[Calle]
                          ,[Numero])
                      VALUES (@Id, 
                            @Nombre, 
                            @Apellido, 
                            @FechaNacimiento, 
                            @Localidad, 
                            @Calle, 
                            @Numero)";


    using (SqlConnection conn = new SqlConnection(ConfigurationManager.ConnectionStrings["default"].ToString()))
    {
        int NextId = MaxId() + 1;

        SqlCommand command = new SqlCommand(sql, conn);
        command.Parameters.AddWithValue("Id", NextId);
        command.Parameters.AddWithValue("Nombre", contacto.Nombre);
        command.Parameters.AddWithValue("Apellido", contacto.Apellido);
        command.Parameters.AddWithValue("FechaNacimiento", contacto.FechaNacimiento);
        command.Parameters.AddWithValue("Localidad", string.IsNullOrEmpty(contacto.Localidad) ? (object)DBNull.Value : contacto.Localidad);
        command.Parameters.AddWithValue("Calle", string.IsNullOrEmpty(contacto.Calle) ? (object)DBNull.Value : contacto.Calle);
        command.Parameters.AddWithValue("Numero", contacto.Numero.HasValue ? contacto.Numero : (object)DBNull.Value );

        conn.Open();

        command.ExecuteNonQuery();

        contacto.IdContacto = NextId;

        return contacto;

    }
}

Durante la creación del registro hay una operación previa que debe realizarse, y esta consiste en obtener el id siguiente de la clave de la entidad a insertar.

Es por ello que se creo el método MaxId()

private static int MaxId()
{
    string sql = @"SELECT MAX(IdContacto)
                      FROM Contacto";


    using (SqlConnection conn = new SqlConnection(ConfigurationManager.ConnectionStrings["default"].ToString()))
    {

        SqlCommand command = new SqlCommand(sql, conn);

        conn.Open();

        return Convert.ToInt32(command.ExecuteScalar());

    }
}

Este es un método muy simple que también hace uso del ExecuteScalar() para obtener el único valor devuelto para la función de agregación que se ejecuta en la consulta sql, en este caso se utiliza la función Max, para recuperar el ultimo id de la tabla utilizada en la consulta.

Además de obtener el próximo id, durante el proceso de insertar se validan ciertos campos, para verificar si estos contiene información asignada, pero en caso de no tenerla se envía a la db el valor DBNull.Value, esto se puede hacer ya que los campos donde se realiza la operación permiten valores nulos como contenido.

Como este es un proceso de insert y la clave es generada de forma interna a la aplicación, es por esta razón que el final se actualiza la entidad pasada por parámetro y es devuelta para que el sistema pueda informarlo al usuario, ya sea en un mensaje de texto, o para registrar la operación en algún sistema de log, si fuera necesario.

2 – Actualización de la entidad (Update)

El proceso de actualización es bastante similar al de creación de la entidad, solo varia en que este requiere que la entidad exista, se tenga el identificador o código para poder identificarlo

private static ContactoEntity Update(ContactoEntity contacto)
{

    string sql = @"UPDATE Contacto SET 
                          [Nombre] = @Nombre
                          ,[Apellido] = @Apellido
                          ,[FechaNacimiento] = @FechaNacimiento
                          ,[Localidad] =  @Localidad
                          ,[Calle] =  @Calle
                          ,[Numero] = @Numero
                    WHERE [IdContacto] = @Id";


    using (SqlConnection conn = new SqlConnection(ConfigurationManager.ConnectionStrings["default"].ToString()))
    {

        SqlCommand command = new SqlCommand(sql, conn);
        command.Parameters.AddWithValue("Id", contacto.IdContacto);
        command.Parameters.AddWithValue("Nombre", contacto.Nombre);
        command.Parameters.AddWithValue("Apellido", contacto.Apellido);
        command.Parameters.AddWithValue("FechaNacimiento", contacto.FechaNacimiento);
        command.Parameters.AddWithValue("Localidad", string.IsNullOrEmpty(contacto.Localidad) ? (object)DBNull.Value : contacto.Localidad);
        command.Parameters.AddWithValue("Calle", string.IsNullOrEmpty(contacto.Calle) ? (object)DBNull.Value : contacto.Calle);
        command.Parameters.AddWithValue("Numero", contacto.Numero.HasValue ? contacto.Numero : (object)DBNull.Value);

        conn.Open();

        command.ExecuteNonQuery();

        return contacto;

    }
}

A diferencia del proceso de creación de la entidad en esta oportunidad no es creado un nuevo Id, sino que este debe enviarse para que la actualización pueda realizarse correctamente. Es mas esta Id es utilizado en la sección WHERE de la consulta para poder identificar el registro que se quiere actualizar.

3 – Eliminación de la entidad (Delete)

Para este proceso una vez que se tiene la entidad simplemente se envía el id y eso es todo.

public static void Delete(ContactoEntity contacto)
{
    if (contacto == null)
        throw new BusinessException(Messages.ERR1001); 

    Delete(contacto.IdContacto);
}

public static void Delete(int Id)
{
    string sql = @"DELETE FROM Contacto 
                   WHERE [IdContacto] = @Id";


    using (SqlConnection conn = new SqlConnection(ConfigurationManager.ConnectionStrings["default"].ToString()))
    {

        SqlCommand command = new SqlCommand(sql, conn);
        command.Parameters.AddWithValue("Id", Id);

        conn.Open();

        command.ExecuteNonQuery();

    }
}

Consideración sobre el ejemplo

Algo que se notara cuando se analice la aplicación es que fue eliminado el DataSet del ejemplo previo en el post anterior, dejando únicamente  el uso de la entidad como clase, esto se debió a que era necesario uniformizar la aplicación para tratar con solo un tipo de entidad, y poder así conservar esta cuando se esta en modo edición.

Se vera a nivel de presentación una propiedad que representa la entidad seleccionada, el tener o no un objeto que representa al contacto marca el modo en que se encuentra la aplicación, ya que al hacer dobleclick en una entidad de la grilla esta carga el contenido y asigna un valor en este propiedad interna del formulario

Los ejemplos requieren al menos Sql Server 2008 Express instalado localmente

[C#]
[VB.NET]

13 comentarios:

  1. hola estimado quisiera que me ayudes con un problema que tengo quiciera realizar un abm por capas y sin utilizar un datagridview pero utilizando un listbox telo agradeceria mucho que dios te bendiga

    ResponderEliminar
  2. hola omar

    Armar un ejemplo de este estilo llevaria un poco de tiempo.

    Estoy empezando el armado de unos articulos que trataran el tema de desarollo en capas, pero no habia pensado usar un listbox, ya que en realidad en el desarollo por capas lo que interesa es la forma de razonar la separacion de responsabilidades ente cada capa, y no el control especifico que se use.

    No se si en el siguiente articulo incluiya el listbox, pero por ahi si te das una vuelva el que publique te sirva de base para el desarrollo, al menos para que puedas aprender los conceptos, despues el resto es solo bindear controles a datos, no mucho mas.


    saludos

    ResponderEliminar
  3. A mi no me parece mucho lo de la clase ContactoDAL ya que deberia utilizarse una general. Porque o sea, si tenemos más entidades: producto, factura, almancen, etc... cada una tendria su propia DAL sin embargo, podriamos programar un sola DAL que sirva para todas las entidades.

    o al menos eso entendí en tu ejemplo... como es?
    .
    Slds
    Lizzy

    ResponderEliminar
  4. hola Lizzy

    Si has entendi bien se crea una clase DAL por cada entidad del dominio de la aplicacion.

    Esto esta mas orientado cuando se programa en capas, o con una arquitectura que modele el dominio y en donde la idea es reutilizar la funcionalidad.

    No estoy en contra de armar algo generico para la persitencia, pero ojo porque si se hace mal puede ser contraproducente. He tenido experincia de desarrollos que tendian a lo generico y al final termina ciendo un caos de parches por todos lados para atajar los imprevistos del modelo.

    Cuando diseñas hay entidades que escapan a lo normal y requieren de un tratamiento especial para trabajarlas, es alli donde lo generico no cuadra y se encuentran los problemas.

    Yo he programado aplicacion muy grandes con una tecnica similar a esta, por supuesto adaptada a desarrollo en capas, y los resultados son muy buenos.

    Si hay que reconocer que con esta tecnica se tipear bastante mas, pero si te animas a usar un generador de codigo, sale toda la persitencia en dos clicks, por supuesto una vez que hayas definido los templates del generador de codigo.

    Tambien es valido armar la persistencia usando algun ORM, como ser NHibernate, o Entity Framework, estos te ayudarian en el armado de la prsistencia sin tener que codificar tanto.

    saludos

    ResponderEliminar
  5. Excelente material Leandro, no cualquier emplea su tiempo para crear articulos como este para ayudar a los demas...

    ResponderEliminar
  6. wow mi estimado me hiciste un parote con este tuto andaba medio perdido pero ya quedo funcioannado perfectamente....
    dejo el codigo por si a alguien le sirve



    Sub Guardar()


    Dim comp As New SqlClient.SqlCommand("SELECT COUNT (*) FROM Equipo_computo WHERE IDAF='" & TextBox1.Text & " '", cs)
    cs.Open()
    Dim count As Integer = Convert.ToInt32(comp.ExecuteScalar)
    cs.Close()
    If count = 0 Then
    Dim cmd As New SqlClient.SqlCommand("INSERT INTO Equipo_computo VALUES ('" & TextBox1.Text & "','" & TextBox4.Text & "','" & TextBox3.Text & "','" & ComboBox1.Text & "','" & TextBox2.Text & "', '" & TextBox6.Text & "','" & TextBox7.Text & "','" & TextBox8.Text & "','" & TextBox5.Text & "','" & ComboBox2.Text & "' )", cs)
    cs.Open()
    cmd.ExecuteNonQuery()
    TextBox1.Text = ""
    TextBox3.Text = ""
    TextBox4.Text = ""
    TextBox5.Text = ""
    ComboBox1.Text = ""
    TextBox2.Text = ""
    TextBox6.Text = ""
    TextBox7.Text = ""
    TextBox8.Text = ""
    ComboBox2.Text = ""
    TextBox2.Text = ""
    cs.Close()
    Else
    MsgBox("El registro ya existe", MsgBoxStyle.Information, AcceptButton)
    limpiar()
    End If

    End Sub

    ResponderEliminar
  7. hola jesus

    Me alegro que el articulo haya sido de utilidad, pero me pregunto porque en el codigo que dejas no has usado parametros ?

    Este punto es uno de los que mas insistencian doy, la idea es evitar unir una cadena de string para armar la query.

    Sino lo has realizado de esta forma, revisa nuevamente el codigo del articulo y usa la coleccion Parameters, para pasarle los valores por medio del AddWithValue(), es la forma correcta de hacerlo.

    Ademas recuerda usar el Using para englobar la conexion, con esta evitaras que la conexion pueda quedar abierta, es mas con el using no requieres especificar en el codigo el Close()

    saludos

    ResponderEliminar
  8. Hola leandro nesecito que me ayudes con un problema que tengo en una aplicacion es un proyecto de mi universidad se trata de un inventario de un restauran el problema que tengo esque quiero mostrar todos los articulos con sus imagenes en un listview pero no encuntro como extraer las imagenes desde mysql eespero que me puedas ayudar

    ResponderEliminar
  9. hola Ali

    extraer las imagene de mysql es identico a realziarlo en sql server

    recuperas el array de byte del campo

    byte[] imagen = (byte[])row["campoimagen"];

    por supuesto en el SELECT defines el campo de imagen

    saludos

    ResponderEliminar
  10. una pregunta estimado amigo como aces para sacar ese menu en el datagred view .. osea el menu eliminar

    ResponderEliminar
  11. hola Josue

    no se si entendi la pregunta, el datagridview hasta donde se no tiene ningun menu, pero quizas apuntes a un menu contextual

    Creating a Context Menu on a DataGridView Mouse Click


    lo que haces es definir un ContextMenu con los items y al accionar sobre el grid lo muestras

    saludos

    ResponderEliminar
  12. Hola soy nueva en esto y tengo una duda, estoy haciendo un update de un registro de la bd desde c# visual studio y cuando realizo la gestion me indica :"Error al convertir el tipo de datos varchar a float." he de decir que solo me pasa cuando estoy intentado modificar un valor float, los campos de la bd son float y los que recojo en el programa son double, a continuación os muestro el codigo:

    SqlCommand comando = new SqlCommand("Update Depositos SET [Id Tipo Deposito]= @IdTipoDep, [Id Tipo Combustible] = @IdTpCom , Nombre = @nomDep , Ubicacion = @ubicacion , [Cantidad Actual] = @cantActua , Capacidad = @capacidad , Remanente = @remanente where Id = @Id", conexion);

    comando.Parameters.AddWithValue("@Id", VarGlobales.idDeposModificar);
    comando.Parameters.AddWithValue("@IdTipoDep", miDeposito.idTipoDepos);
    comando.Parameters.AddWithValue("@IdTpCom", miDeposito.idTipoComb);
    comando.Parameters.AddWithValue("@nomDep", miDeposito.nomDepos);
    comando.Parameters.AddWithValue("@ubicacion", miDeposito.ubicaDepos);
    comando.Parameters.AddWithValue("@cantActua", miDeposito.cantidadActual);
    comando.Parameters.AddWithValue("@capacidad", miDeposito.capacidadDepos);
    comando.Parameters.AddWithValue("@remanente", miDeposito.remanenteDepos);

    Agradeceria que me echaran una mano para poder solucionarlo. Muchas gracias.

    ResponderEliminar
  13. ya lo he solucionado, esque tengo un trigger para generar un histórico de las modificaciones, y era alli donde me daba el error y lo solucioné con un cast(x), aun asi gracias por tus aportes.

    ResponderEliminar