domingo, 29 de noviembre de 2009

C# – ADO.NET – Parte 4 – Actualización Información Ms Access

 

Introducción

Este articulo intenta brindar un ejemplo practico de como hacer uso de los objetos de ADO.NET para poder manipular la información de la base de datos, en este caso será de Ms Access.

Gran parte de la explicación se ha realizado en artículos previos

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

que si bien tratan otra base de datos, se vera que salvo algunos pequeños detalles es prácticamente idéntico.

Es por eso que a medida que se vaya analizando el código del articulo es recomendable darle un vistazo al link de la Parte 3 del articulo, este contendrá detalles mas precisos sobre algunos aspectos utilizados.

 

1 – Creación nueva entidad (Insert)

En este código encontraran también el método Save() el cual será el encargado de determinar si es necesario insertar el registro o simplemente actualizarlos

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);
}

Si se analiza el código veras que la funcionalidad que determina si existe la entidad es idéntica a como se programaría cuando se hace uso de Sql Server, el único cambio radica en que se utiliza los objeto de OleDb

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 (OleDbConnection conn = new OleDbConnection(ConfigurationManager.ConnectionStrings["default"].ToString()))
    {

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

        conn.Open();

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

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

al igual que el ejemplo del link relacionado, se hace uso de ExecuteScalar, el cual devolverá simplemente la primer columna de la primer fila de la consulta.

LA funcionalidad que inserta tiene un pequeño detalles que hay que aclararlo ya que puede presentarse problemas en otras circunstancias.

Este se presenta al declarar el parámetro de fecha de nacimiento (líneas 28-30), se vera que se crea una variable del tipo OleDbParameter en donde se define el tipo de dato puntualmente.

Esto debe ser así ya que el método AddWithValue() no puede determinar para este tipo de dato cual es el correcto, cuando se le pasarle un objeto del tipo DataTime de .net, es por eso que hay que definirlo OleDbType.Date, y de esta forma funciona perfectamente.

 

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 (OleDbConnection conn = new OleDbConnection(ConfigurationManager.ConnectionStrings["default"].ToString()))
    {
        int NextId = MaxId() + 1;

        OleDbCommand command = new OleDbCommand(sql, conn);
        command.Parameters.AddWithValue("Id", NextId);
        command.Parameters.AddWithValue("Nombre", contacto.Nombre);
        command.Parameters.AddWithValue("Apellido", contacto.Apellido);
        
        OleDbParameter param = new OleDbParameter("FechaNacimiento", OleDbType.Date);
        param.Value = contacto.FechaNacimiento;
        command.Parameters.Add(param);
        
        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;

    }
}

También se veras que la función MaxId() que permite recuperar el ultimo id ingresado no sufre cambio alguno con respecto a utilizado en una base de datos Sql Server

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


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

        OleDbCommand command = new OleDbCommand(sql, conn);

        conn.Open();

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

    }
}

2 – Actualización de la entidad (Update)

En la operación de actualización hay que remarcar dos puntos que son importantes y generan cambio con respecto a su equivalente en Sql Server

Uno al igual que como sucedió en la funcionalidad de Insert, es necesario definir el tipo de dato explicito para la fecha de nacimiento.

El segundo aspecto se refiere a que si bien hasta ahora pensábamos que los nombres de los parámetros guardaban una conexión, bajo la operación del actualización nos damos cuenta que no es tan así.

Es por ello que se notara que la creación de parámetros en este caso el “Id” ha sido declarado al final del resto, mientras que en si equivalente para Sql Server podría haber sido declarado en cualquier posición.

Esto marca un punto importante durante la actualización y es que el nombre sirve para tener una referencia a que parámetro estamos asignando el valor, pero para el provider de base de datos OleDb, lo que importa es la posición del parámetro en colección de Parameters.

Si en este código se declarar el parámetro “Id” en primer lugar la actualización no se realizaría adecuadamente.

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 (OleDbConnection conn = new OleDbConnection(ConfigurationManager.ConnectionStrings["default"].ToString()))
    {

        OleDbCommand command = new OleDbCommand(sql, conn);
        
        command.Parameters.AddWithValue("Nombre", contacto.Nombre);
        command.Parameters.AddWithValue("Apellido", contacto.Apellido);

        OleDbParameter param = new OleDbParameter("FechaNacimiento", OleDbType.Date);
        param.Value = contacto.FechaNacimiento;
        command.Parameters.Add(param);

        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);
        
        command.Parameters.AddWithValue("Id", contacto.IdContacto);

        conn.Open();

        int rows = command.ExecuteNonQuery();

        return contacto;

    }
}

3 – Eliminación de la entidad (Delete)

Esta operación no sufre cambio alguno con respecto a su equivalente en Sql Server, salvo los objetos de ado.net utilizados.

 

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


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

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

        conn.Open();

        command.ExecuteNonQuery();

    }
}

 

[C#]
[VB.NET]

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]

domingo, 22 de noviembre de 2009

[ADO.NET] Recuperar Información MS Access

 

Introducción


Con este ejemplo intento demostrar como con algunos simples cambios se puede utilizar ado.net de una forma similar tanto si se accede a sql server como si es Access la base de datos utilizada

Este post esta adaptado de la primera parte

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

por lo tanto muchas de las explicaciones serán obviadas ya que los pasos son idénticos para ambos métodos.

Ante cualquier duda en los paso necesario para utilizar ado.net se podrá recurrir al primer articulo ya que la forma de codificar es idéntica para Sql Server y Access

En los ejemplos se observara que la técnica puede aplicarse exactamente de la misma forma, solo se ha cambiado los nombre de los objetos utilizados, por ejemplo en lugar de ser SqlConnection se utilizaría el OleDbConnection, y así con el resto de los objetos a utilizar.

 

1 - Recuperar un conjunto de datos (DataReader)


En este se recorre mediante el objeto OleDbDataReader los datos recuperados mediante la ejecución de la consulta.

public static List<ContactoEntity> GetAll()
{
    string sql = @"SELECT [IdContacto]
                          ,[Nombre]
                          ,[Apellido]
                          ,[FechaNacimiento]
                          ,[Localidad]
                          ,[Calle]
                          ,[Numero]
                      FROM Contacto";

    List<ContactoEntity> list = new List<ContactoEntity>();

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

        OleDbCommand command = new OleDbCommand(sql, conn);

        conn.Open();

        OleDbDataReader reader = command.ExecuteReader();

        while (reader.Read())
        {
            list.Add(LoadContacto(reader));
        }

        return list;
    }


}

Como se observa el método utilizado para crear la entidad se mantiene exactamente igual, ya que al usarse una interfaz del tipo IDataReader, esta mantiene compatibilidad tanto si proviene de Sql server, como Access

private static ContactoEntity LoadContacto(IDataReader reader)
{
    ContactoEntity contacto = new ContactoEntity();

    contacto.IdContacto = Convert.ToInt32(reader["IdContacto"]);

    contacto.Nombre = Convert.ToString(reader["Nombre"]);
    contacto.Apellido = Convert.ToString(reader["Apellido"]);

    contacto.FechaNacimiento = Convert.ToDateTime(reader["FechaNacimiento"]);

    contacto.Localidad = Convert.ToString(reader["Localidad"]);
    contacto.Calle = Convert.ToString(reader["Calle"]);
    contacto.Numero = Convert.ToInt16(reader["Numero"]);


    return contacto;
}

2- Recuperar un conjunto de datos (DataSet)


Al igual que el ejemplo planteado en Sql Server, este utiliza un DataSet Tipado es mas, como el DataSet es independiente de la tecnología utilizada en el acceso a los datos se pudo reutilizar el del ejemplo anterior

public static dtoContacto GetAllFromDataSet()
{
    string sql = @"SELECT [IdContacto]
                          ,[Nombre]
                          ,[Apellido]
                          ,[FechaNacimiento]
                          ,[Localidad]
                          ,[Calle]
                          ,[Numero]
                      FROM Contacto";

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

        OleDbCommand command = new OleDbCommand(sql, conn);

        OleDbDataAdapter da = new OleDbDataAdapter(command);

        dtoContacto contactos = new dtoContacto();

        da.Fill(contactos, "Contacto");

        return contactos;
    }


}

3 – Recuperar un solo registro (DataReader)


Debe remarcarse en esta parte del ejemplo que Access también permite el uso de parámetros en sus consultas, en este caso al usarse el proveedor: Microsoft.Jet.OLEDB.4.0, los parámetros siguen teniendo el mismo formato que Sql Server, o sea hacen uso de @param, (en donde param es el nombre del parámetro), pero si se utiliza algún otro proveedor de acceso a datos para OleDb puede que esto cambie, y se necesite hacer uso del signo ?, para especificar la posición del mismo.

public static ContactoEntity GetById(int Id)
{
    string sql = @"SELECT [IdContacto]
                          ,[Nombre]
                          ,[Apellido]
                          ,[FechaNacimiento]
                          ,[Localidad]
                          ,[Calle]
                          ,[Numero]
                      FROM Contacto
                      WHERE IdContacto = @Id";

    ContactoEntity product = null;

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

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

        conn.Open();

        OleDbDataReader reader = command.ExecuteReader();

        if (reader.Read())
        {
            product = LoadContacto(reader);
        }

        return product;
    }
}

4 – Recuperar un solo registro (DataSet)


Aquí hay que resaltar el cambio de  enumerado utilizado para definir el tipo de datos del parámetro, siendo el OleDbType

 

public static dtoContacto.ContactoRow GetByIdFromDataSet(int Id)
{
    string sql = @"SELECT [IdContacto]
                          ,[Nombre]
                          ,[Apellido]
                          ,[FechaNacimiento]
                          ,[Localidad]
                          ,[Calle]
                          ,[Numero]
                      FROM Contacto
                      WHERE IdContacto = @Id";

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

        OleDbCommand command = new OleDbCommand(sql, conn);

        OleDbParameter param = new OleDbParameter("Id", OleDbType.Integer);
        param.Value = Id;
        command.Parameters.Add(param);

        OleDbDataAdapter da = new OleDbDataAdapter(command);

        dtoContacto contactos = new dtoContacto();

        da.Fill(contactos, "Contacto");

        if (contactos.Contacto.Rows.Count > 0)
            return contactos.Contacto.Rows[0] as dtoContacto.ContactoRow;
        else
            return null;
    }
}

Conclusión

Una vez se comprenda el concepto general de los objetos implicados en las consultas a las base de datos, las diferencias son mínimas, conociendo los objetos de ADO.NET básicos se puede consultar cualquier base de datos con pequeños cambios y lo aprendido para trabajar con una base de datos especifica pueden aplicarse al resto.

 

[C#] 
[VB.NET] 

domingo, 15 de noviembre de 2009

[ADO.NET] Parte 1 - Recuperar Información Sql Server

 

Introducción


He notado que a veces faltan ejemplo integradores sobre el uso de ciertos aspectos que a primera vista parecen simple, pero al no encontrase en uso de forma integradora pueden resultar algo difíciles de captar las ideas, especialmente cuando se comienza en el aprendizaje.

La objetivo de este articulo es mostrar como hacer so de ado.net paso a paso en un ejemplo concreto utilizando varios métodos de consultas y trabajo de la información obtenida

 

Algunas aclaraciones antes de comenzar


Si bien la explicación del ejemplo se centrara en el uso de ado.net bajo lenguaje c#, los ejemplos podrán descargarse también en vb.net

Las explicación del articulo aplica a ambos lenguajes sin problemas.

Con respecto al diseño del ejemplo por ahí algunas aclaraciones previas podrá facilitar la comprensión.

Se visualizaría que en el ejemplo se han declarado dos clases que por ahí no son familiares, estas llevan el nombre: ContactoDAL y ContactoEntity.

Para el que no conozca el DAL (Data Access Layer) en realidad apunta a separa en capas la aplicación para dividir las responsabilidades y poder así encapsular y abstraer funcionalidad facilitando la reutilización.

En este caso en particular aplique este mismo concepto pero no lo puse en un proyecto nuevo, simplemente para no complicar el ejemplo, es por eso que tanto estas clases como el formulario están en el mismo proyecto.

También verán una clase que termina con la palabra entity, esta básicamente representa la entidad del dominio, y define las propiedades que posee la tabla de contactos, esta entidad será la intermediaria entre la funcionalidad de la DAL y la presentación. Esta clase evitara que a la presentación le lleguen objetos que claramente son de datos.

Aunque vale aclarar que en este ejemplo al estar contenido en un solo proyecto se pierde un poco el objetivo de abstraer a la presentación de componente de datos, pero igualmente esta es una buen técnica para mantener el código prolijo.

 

1 - Recuperar un conjunto de datos (DataReader)


La primera operación que será utilizada será la de recuperar todos los ítems de una tabla y cargar una grilla con ellos.

Para esta operación hay dos formas para acceder a los datos, por medio de:

  • DataReader
  • DataSet

Para los DataReader es que se hace uso de la clase con sufijo “entity” de esta forma se cargan los datos en estas entidades evitando pasar el reader a la presentación, lo cual no es nada aconsejable.

Algo importante para contar acerca de los reader es que estos necesitan mantener la conexión a al db abierta durante el procesamiento (o lectura de los registros que se están recuperando), es por eso que usar clases de entidades ya que se procesa el reader en un tiempo muy corto y luego se cierra. Hay que destacar que lo reader en acceso secuencial de lectura son mas rápido que los dataset.

Para el dataset, se hace uso de un DataSet tipado, en el proyecto lleva el nombre de “dtoContacto”, este permite tener una estructura definida, similar a la que brinda “ContactoEntity”.

En la clase ContactoDAL, se encuentran dos métodos “GetAll” y “GetAllFromDataSet”.

Empezaremos explicando como hacer uso de DataReader.

public static List<ContactoEntity> GetAll()
{
    string sql = @"SELECT [IdContacto]
                          ,[Nombre]
                          ,[Apellido]
                          ,[FechaNacimiento]
                          ,[Localidad]
                          ,[Calle]
                          ,[Numero]
                      FROM Contacto";

    List<ContactoEntity> list = new List<ContactoEntity>();

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

        SqlCommand command = new SqlCommand(sql, conn);

        conn.Open();

        SqlDataReader reader = command.ExecuteReader();

        while (reader.Read())
        {
            list.Add(LoadContacto(reader));
        }

        return list;
    }

}

> Creación del objeto de conexión a la base de datos (línea 14): Este siempre será el primer paso ya que es necesario definir el objeto que permitirá establecer el contacto con la base de datos. Para realizar la tarea se necesitara de un string de conexión, para mas detalles ver “Cadena de conexión”.

Algo interesante que observaran es el uso de la sentencia using, el objetivo de esta es permitir delimitar la sección en que estará disponible un objeto, o sea el ámbito en el cual la instancia será usada, verán como al final cuando se termina de utilizar los objetos no se realiza un cierre de la conexión o un Dispose de los objetos, es justamente el bloque using el que hará esto por nosotros.

> Objeto Command (línea 17): este objeto permitirá unificar la consulta sql de selección que vamos a utilizar con la conexión a la base de datos que establecimos en el paso anterior.

El objeto command posee la funcionalidad para ejecutar la consulta que se ha creado, ya sea esta para insertar o actualizar la información, o como en este caso para recuperarla.

> Ejecución de la consulta (línea 21): el objeto command posee un método de nombre ExecuteReader(), el cual devolverá como resultado un objeto del tipo SqlDataReader.

> Lectura de los datos devueltos (líneas 23-26): al tratarse de un grupo de registros los que se recuperaran de la consulta es necesario realizar un ciclo por cada uno de ellos realizando la transformación de los datos para adecuarlos al objeto definido como entidad de negocio de la aplicación, en este caso “ContactoEntity”

El método Read() del objeto SqlDataReader tiene dos funcionalidades básicas, devuelve un true o false según se encuentren registros para leer, y además posiciona el cursor el el siguiente registro habilitado, es por eso que el while cumple la función ideal para recorrer cada ítem del objeto DataReader.

> Transformación de los datos del reader a la entidad (línea 25): esta operación se ha encapsulado en un método separado ya que es una operación que será reutilizada:

private static ContactoEntity LoadContacto(IDataReader reader)
{
    ContactoEntity contacto = new ContactoEntity();

    contacto.IdContacto = Convert.ToInt32(reader["IdContacto"]);

    contacto.Nombre = Convert.ToString(reader["Nombre"]);
    contacto.Apellido = Convert.ToString(reader["Apellido"]);

    contacto.FechaNacimiento = Convert.ToDateTime(reader["FechaNacimiento"]);

    contacto.Localidad = Convert.ToString(reader["Localidad"]);
    contacto.Calle = Convert.ToString(reader["Calle"]);
    contacto.Numero = Convert.ToInt16(reader["Numero"]);


    return contacto;
}

Como se observara en el bloque de código este método crea una instancia nueva de la entidad que estamos utilizando, luego asigna a cada propiedad la columna que le corresponde del registro que se esta leyendo en ese momento, el cual se ha pasado como parámetro al método, y por ultimo retorna el objeto entity con los datos para ser asignado a la colección de “ContactoEntity”.

 

2- Recuperar un conjunto de datos (DataSet)


Esta operación tendrá mucho en común con la anterior solo diferirá en la utilización algunos objetos distintos ya que se hará uso de un DataSet, (en este caso tipado) para cargar los datos

 

public static dtoContacto GetAllFromDataSet()
{
    string sql = @"SELECT [IdContacto]
                          ,[Nombre]
                          ,[Apellido]
                          ,[FechaNacimiento]
                          ,[Localidad]
                          ,[Calle]
                          ,[Numero]
                      FROM Contacto";

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

        SqlCommand command = new SqlCommand(sql, conn);

        SqlDataAdapter da = new SqlDataAdapter(command);

        dtoContacto contactos = new dtoContacto();

        da.Fill(contactos, "Contacto");

        return contactos;
    }

}

Como se observara en el código los dos primeros pasos son coincidentes con la operación anterior que utilizaba DataReader para la obtención de los datos, se detallara a continuación las diferencias.

> Creación del adaptador (línea 17): Este objeto DataAdapter proporcionara la herramienta principal para poder cargar un objeto del tipo DataSet.

Como ser observara el constructor del objeto SqlDataReader en este caso recibe al objeto Command como parámetro, pero debe comentarse que no es la única alternativa, en este caso se uso el command para mantener una uniformidad en la codificación, pero podría haber pasado como parámetro la consulta sql y el objeto conexión directamente al constructor del DataAdapter, de esta forma ya no se necesitara mas del objeto Command.

> Creación y llenado de los datos (líneas 19-21): La definición del dataset tipado se encuentra en el archivo de nombre “dtoContacto.xsd”, al inspeccionar el archivo se notara que este contiene un DataTable de nombre “Contacto” y la definición de las columnas, las cuales coinciden con las de la tabla de la base de datos.

Nota: cuando se crea un DataSet Tipado lo mas común es que se agregue de forma automática un TablaAdapter, en este caso se removió dejando la entidad lo mas simple posible.

El método Fill() del DataAdapter realizara la ejecución y carga de los datos en el DataSet Tipado, al cual además le indicamos cual es el DataTable que se estará cargando. Debe recordarse que un DataSet puede contener mas de un DataTable, en este caso posee solo uno, pero podría haber mas en otros casos.

Nota: al diferencia del objetos Command el SqlAdapter no requiere la apertura previa de la conexión, es por eso que la línea conn.Open() no esta presente. El DataDapter realiza la apertura, utilización y cierre de la conexión de forma automática

 

3 – Recuperar un solo registro (DataReader)


A diferencia la la operación en donde recuperábamos todo la información de una tabla, en este caso se remarcan dos puntos

- la utilización de parámetros en la consulta

- la no utilización del while para recorrer los datos provenientes de la consulta

 

public static ContactoEntity GetById(int Id)
{
    string sql = @"SELECT [IdContacto]
                          ,[Nombre]
                          ,[Apellido]
                          ,[FechaNacimiento]
                          ,[Localidad]
                          ,[Calle]
                          ,[Numero]
                      FROM Contacto
                      WHERE IdContacto = @Id";

    ContactoEntity product = null;

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

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

        conn.Open();

        SqlDataReader reader = command.ExecuteReader();

        if (reader.Read())
        {
            product = LoadContacto(reader);
        }

        return product;
    }
}

> Creación del parámetro de la consulta (línea 19): como se observa el objeto Command posee una propiedad que permite definir los parámetros en la consulta, es allí donde serán agregados, y hay diferentes formas de hacerlo, la mas practica y directa para crear parámetros simples es utilizando el método AddWithValue().

Pero debe remarcarse un punto importante en el uso de este método, como se observara en ningún momento se define un tipo de dato para el parámetro, lo cual parecería hasta mágico como lo infiere, pero no es así, para que esto funcione correctamente el tipo de la variable que se utiliza debe ser del tipo correcto, en este caso el “Id” es del tipo int, el cual coincide con el tipo de la columna “Id” de la tabla en la base de datos, es por esto que todo es resulto fácilmente.

Por ejemplo, si el parámetro será una fecha la variable usada en el método AddWithValue, debería ser también del tipo fecha, o en su defecto se tendría que convertir (castear) al tipo DataTime. En definitiva el método para agregar parámetros es simple de usar pero requiere ciertos cuidados a tener en cuenta, ya que de otra forma no podrá determinar de forma dinámica el tipo de datos del parámetro.

> Lectura del registro (línea 25-28): a diferencia del proceso en donde se leían un grupo de registros, en este solo nos interesa uno solo, es por eso que al reemplazar el while por un simple if, este cumple la misma funcionalidad, el Read() del DataReader devuelve true si hay registro, y además posiciona el cursor en el mismo para su lectura.

Luego se reutiliza el método que devolverá la entidad completa, y esta es retornada como resultado del método.

 

4 – Recuperar un solo registro (DataSet)


A diferencia el método en donde se recuperaba un conjunto de registros aquí hay variantes

- se utiliza un parámetro en la consulta

- se devuelve un solo registro del DataTable y no el DataSet completo

public static dtoContacto.ContactoRow GetByIdFromDataSet(int Id)
{
    string sql = @"SELECT [IdContacto]
                          ,[Nombre]
                          ,[Apellido]
                          ,[FechaNacimiento]
                          ,[Localidad]
                          ,[Calle]
                          ,[Numero]
                      FROM Contacto
                      WHERE IdContacto = @Id";

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

        SqlCommand command = new SqlCommand(sql, conn);

        SqlParameter param = new SqlParameter("Id", SqlDbType.Int);
        param.Value = Id;
        command.Parameters.Add(param);

        SqlDataAdapter da = new SqlDataAdapter(command);

        dtoContacto contactos = new dtoContacto();

        da.Fill(contactos, "Contacto");

        if (contactos.Contacto.Rows.Count > 0)
            return contactos.Contacto.Rows[0] as dtoContacto.ContactoRow;
        else
            return null;
    }
}

> Creación del parámetro de la consulta (línea 18-20): a modo de ejemplo en este caso se ha utilizado una técnica distinta, en donde se define explícitamente cual es el tipo de datos del parámetro.

Se crea el objeto SqlParameter, pasando en el constructor el nombre y tipo de dato del parámetro, luego a la instancia se le asigna el valor, y por ultimo se agrega a la colección de parámetros del objeto Command

> Determinar si se encontró el registro, y retorno del ContactRow (lineas 28-31): Como en este caso lo que se carga es un Datset completo, en realidad un DataTable si se lo devuelve directamente quien deba manipular los datos deberá agregar validaciones que comprueben si esta o no el registro cargado.

Es por ello que estas validaciones se agrego del lado de los datos, en el if se pregunta si al menos hay un registro cargado, luego se toma el primero de la colección para retornarlo, en caso de no haber ninguno registro para los filtros asignados se devuelve null.

Al retornar el valor se castea a un tipo de dato algo particular “dtoContacto.ContactoRow”, esta es una clase que crea internamente el DataSet Tipado, y representa un registro del DataTable “Contacto” 

 

Cadena de Conexión


Seguramente se habrá notado en el código que al hacer uso del objeto SqlConnection se utiliza una clase que devuelve la cadena de conexión:

ConfigurationManager.ConnectionStrings["default"]

El ConfigurationManager permite el acceso al archivo de configuración situado en el “app.config”, este archivo al editarlo posee un formato xml en donde se podrá especificar la configuración del sistema, en este caso en particular se utiliza para especificar la cadena de conexión a la base de datos, pero podría servir para conservar otro tipo de datos variables para el sistema.

Lo bueno de esta implementación es que al compilar la aplicación el archivo de configuración queda libre como archivo de texto que podrá editarse con el notepad y cambiarse sin necesidad de recompilar la aplicación desarrollada. Esto es muy bueno para realizar cambios una vez que se ha realizado el deploy en la pc del usuario.

Para utilizar el ConfigurationManager es necesario agregar la referencia a la assembly de nombre “System.Configuration”

 

Consideraciones acerca de la aplicación de ejemplo


Los ejemplos de código están usando Sql Server como parte de la solución, por lo tanto se aconseja que localmente al menos se tenga instado el Sql Server 2008 Express con el servicio ejecutándose.

Para editar un item de la grilla una vez que este cargada, se deberá hacer click con el botón derecho del mouse sobre de la grilla, y seleccionar con que operación recuperar los datos. Se esta haciendo uso control ContextMenuStrip para la edición del registro seleccionado.

 

[C#] 
[VB.NET]