sábado, 12 de diciembre de 2009

Visual Studio – Referencia Custom Control en los Proyectos

 

El siguiente video tiene por objetivo mostrar los pasos para poder hacer uso de un control desarrollado desde otro proyecto.

En este caso solo se contara con la dll, la cual será incluida a una carpeta dentro de la estructura de la solución y será desde allí que se referenciara tanto desde el proyecto como desde la toolbox del Visual Studio para poder luego ser arrastrada al formulario y tenerla disponible en modo visual.

Este video mostrara los pasos para poder hacer uso de la dll y al mismo tiempo conservarla dentro de la estructura de desarrollo, para que ante una eventual necesidad de mover de carpeta el desarrollo no se pierdan las referencias.


El video se encuentra en formato flash y podrá ser reproducido ejecutando el archivo .html que se encuentra dentro del zip.

 

[Crystal Reports] Usar DataSet Tipado con dos DataTable

 

Introducción 

La finalidad de este articulo es muy puntual e intentara demostrar como hacer uso en un reporte de Crystal un DataSet Tipado que contiene dos DataTable en su interior.

En este caso se necesita listar los contactos de una empresa, pero para ello no solo se debe contar con los datos de los contactos, sino que además se desea enviar en el mismo origen de datos la información de la empresa.

Para esta operación es que se hace uso de un mismo DataSet Tipado, pero este contendrá dos tablas, las cuales serán cargadas de forma independiente, pero todo el conjunto será enviado como DataSource al reporte.

Para la operación se carga del dataset es que se ha creado un método que permite bajo la misma conexión cargar ambas tablas

public static dtoContactosEmpresa GetContactosEmpresa()
{
    dtoContactosEmpresa dtContactosEmpresa = new dtoContactosEmpresa();


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

        #region Cargo Datos Empresa

        string sql = @"SELECT IdEmpresa
                          ,RazonSocial
                          ,Telefono
                          ,Direccion
                          ,CUIT
                      FROM Empresas";

        OleDbCommand command = new OleDbCommand(sql, conn);

        OleDbDataAdapter da = new OleDbDataAdapter(command);

        da.Fill(dtContactosEmpresa, "DatosEmpresa");

        #endregion

        #region Cargo Contactos

        sql = @"SELECT Nombre
                  ,Apellido
                  ,FechaNacimiento
                  ,Localidad
                  ,Calle
                  ,Numero
                  ,Mail
              FROM Contacto";


        command = new OleDbCommand(sql, conn);

        da = new OleDbDataAdapter(command);

        da.Fill(dtContactosEmpresa, "Contactos");

        #endregion

    }

    return dtContactosEmpresa;
}

Es importante remarcar como al hacer el Fill() de los datos se especifica el nombre del datatable donde se quiere volcar la información (líneas 22 y 42)

 

[C#]
[VB.NET]

jueves, 10 de diciembre de 2009

[DataGridView] Parte 2 – Edición de celda con form PopUp para la selección de ítem - KeyDown

 

El objetivo del articulo es demostrar como en la edición de la celda se puede lanzar un formulario de búsqueda para la selección de un ítem determinado, y a retorno del mismo completar la información de la fila en edición con la selección del formulario.

Para esta operación se hace uso de propiedades, y clases las cuales permitirán desacoplar ambas grilla, tanto la que originalmente mantiene la lista de productos seleccionados, como la que se despliega y permite la elección de un ítem determinado.

En el formulario cuyo objetico será llevar la lista de ítems elegidos por el usuario se dispone de siguiente código.

private void dataGridView1_EditingControlShowing(object sender, DataGridViewEditingControlShowingEventArgs e)
{
    DataGridViewTextBoxEditingControl dText = (DataGridViewTextBoxEditingControl)e.Control;

    dText.KeyDown -= new KeyEventHandler(dText_KeyDown);
    dText.KeyDown += new KeyEventHandler(dText_KeyDown);
}

void dText_KeyDown(object sender, KeyEventArgs e)
{
    int rowIndex = ((System.Windows.Forms.DataGridViewTextBoxEditingControl)(sender)).EditingControlRowIndex;

    if (e.Alt && e.KeyCode == Keys.D)
    {
        frmSeleccionarProducto form = new frmSeleccionarProducto();

        form.ShowDialog();

        Producto productoSeleccionado = form.ProductSelected;

        DataGridViewRow row = dataGridView1.Rows[rowIndex];
        row.Cells["IdProducto"].Value = productoSeleccionado.IdProducto;
        row.Cells["Descripcion"].Value = productoSeleccionado.Descripcion;
        row.Cells["precio"].Value = productoSeleccionado.Precio;

        dataGridView1.EndEdit();
    }  
}

Como se observa mucha de la funcionalidad tiene que ver con la asignación de los eventos que permitirán capturar la pulsación de la teclas definidas para que se visualice el formulario de PopUp.

En este caso si bien es posible utilizar cualquier tecla, se decidió que la combinación de Alt + D seria la adecuada, pero podría seleccionar la que sea necesario.

Como se observa si se detecta la pulsación de la tecla, se crea una instancia del formulario y se muestra en formulario modal. Al cierre del mismo se captura la propiedad con el ítem elegido y se carga en la grilla.

El formulario que se desplegara tiene un código muy simple en donde simplemente al hacer dobleclick en una de sus filas, la marcara como seleccionada y cerrar el formulario.

private void dataGridView1_CellDoubleClick(object sender, DataGridViewCellEventArgs e)
{
    DataGridViewRow row = dataGridView1.SelectedRows[0];

    this.ProductSelected = new Producto()
    {
        IdProducto = Convert.ToInt32(row.Cells["IdProducto"].Value),
        Descripcion = Convert.ToString(row.Cells["Descripcion"].Value),
        Precio = Convert.ToDecimal(row.Cells["precio"].Value),
    };

    this.Close();
}
[C#]
[VB.NET]

[DataGridView] Parte 1 – Valor de celda Condicional usando CellFormatting

 

El ejemplo intenta demostrar como una vez que se bindean los datos estos pueden ser acomodados en las celda según cierta condición.

En este ejemplo se toma un origen de datos con tres columnas pero solo dos serán representadas como datos en la grilla, ya que una condición indicara si debe

 

private void dataGridView1_CellFormatting(object sender, DataGridViewCellFormattingEventArgs e)
{
    DataGridViewColumn currentColumn = dataGridView1.Columns[e.ColumnIndex];

    if (currentColumn.Name == "Fracciona")
    {
        DataGridViewRow currentRow = dataGridView1.Rows[e.RowIndex];
        DataRowView data = currentRow.DataBoundItem as DataRowView;

        if (data == null)
            return;

        if(Convert.ToBoolean(data["fracc"]))
            currentRow.Cells["tipoventa"].Value = Convert.ToString(data["FORM_VENT"]);
        else
            currentRow.Cells["tipoventa"].Value = Convert.ToString(data["PRESENTACI"]);
    
    }
}

En este caso la lógica principal se encuentra en el evento CellFormatting, pues este será el encargado de realiza la lógica necesaria para decidir que campo de los datos bindeados deben representarse en la celda.

Se podrá visualizar que un campo que si se ha enlazado a la grilla mediante la propiedad DataPropertyName pero el segunda columnas de la grilla no se le ha asignado este valor ya que será por medio de código quien decida que valor mostrar.

Una de las líneas mas importantes es la 7, ya que esta permite recuperar los datos originales que se están bindeando a esa fila en particular, y por lo tanto trabajar con ellos.

Los datos utilizados para el ejemplo son los siguiente

private DataTable GetDataTable()
{
    DataTable dt = new DataTable();

    dt.Columns.Add("fracc", typeof(bool));
    dt.Columns.Add("PRESENTACI");
    dt.Columns.Add("FORM_VENT");


    DataRow row = dt.NewRow();
    row["fracc"] = true;
    row["PRESENTACI"] = "PRESENTACI 1";
    row["FORM_VENT"] = "FORM_VENT 1";
    dt.Rows.Add(row);

    row = dt.NewRow();
    row["fracc"] = false;
    row["PRESENTACI"] = "PRESENTACI 2";
    row["FORM_VENT"] = "FORM_VENT 2";
    dt.Rows.Add(row);

    row = dt.NewRow();
    row["fracc"] = false;
    row["PRESENTACI"] = "PRESENTACI 3";
    row["FORM_VENT"] = "FORM_VENT 3";
    dt.Rows.Add(row);

    row = dt.NewRow();
    row["fracc"] = true;
    row["PRESENTACI"] = "PRESENTACI 4";
    row["FORM_VENT"] = "FORM_VENT 4";
    dt.Rows.Add(row);

    return dt;

}

esto es importante para poder conocer como se accede a la información bideada en la grilla.

Como funcionalidad adicional se permite que el usuario al marcar o desmarcar una celda esta cambie el contenido con respecto al valor que se obtuvo al momento de bindear la fila

private void dataGridView1_CellContentClick(object sender, DataGridViewCellEventArgs e)
{

    DataGridViewColumn currentColumn = dataGridView1.Columns[e.ColumnIndex];

    if (currentColumn.Name == "Fracciona")
    {
        DataGridViewCheckBoxCell currentCell = dataGridView1.Rows[e.RowIndex].Cells[e.ColumnIndex] as DataGridViewCheckBoxCell;

        DataGridViewRow currentRow = dataGridView1.Rows[e.RowIndex];
        DataRowView data = currentRow.DataBoundItem as DataRowView;

        if (data == null)
            return;

        if (Convert.ToBoolean(currentCell.Value))
            currentRow.Cells["tipoventa"].Value = Convert.ToString(data["FORM_VENT"]);
        else
            currentRow.Cells["tipoventa"].Value = Convert.ToString(data["PRESENTACI"]);

        dataGridView1.EndEdit();
    }
}
[C#]
[VB.NET]

martes, 8 de diciembre de 2009

N-Tier Application – Parte 1

 

El objetivo de este articulo es demostrar como una aplicación web puede trabajar en capas, mejorando así la mantención del código, y mejorando el desarrollo.

 

[C#]
 

miércoles, 2 de diciembre de 2009

[ADO.NET] Excel y Linq (Union)

 

Introducción

El objetivo del articulo es demostrar como poder procesar distintos documentos Excel mediante la utilización del provider para Excel de ADO.NET, a su vez las limitaciones de esta tecnología es superada por la potencia de Linq, el cual otorga poder de procesamiento una vez que se tiene la información recuperada de los documentos y se necesita trabajar con los datos.

La idea del ejemplo es poder mostrar una lista de pedidos, informando el numero y cantidad de ítems solicitados. Pero se debe consolidar la lista agrupando y sumando aquellos numero de pedidos se aparezcan en ambos listados.

Lectura de los documentos Excel
DataTable dtExcel1 = new DataTable();
DataTable dtExcel2 = new DataTable();

using (OleDbConnection cnn = new OleDbConnection(ConfigurationManager.ConnectionStrings["Excel1"].ToString()))
{
    string sql = @"SELECT pedido, 
                          cantidad 
                    FROM [Sheet1$]";

    OleDbCommand command = new OleDbCommand(sql, cnn);

    OleDbDataAdapter da = new OleDbDataAdapter(command);

    
    da.Fill(dtExcel1);

}

using (OleDbConnection cnn = new OleDbConnection(ConfigurationManager.ConnectionStrings["Excel2"].ToString()))
{
    string sql = @"SELECT pedido, 
                          cantidad 
                    FROM [Sheet1$]";

    OleDbCommand command = new OleDbCommand(sql, cnn);

    OleDbDataAdapter da = new OleDbDataAdapter(command);


    da.Fill(dtExcel2);

}

Si se analiza el Excel utilizado en le procesamiento se vera que este cuanta con dos columnas pero en el primer registro de cada una se ha escrito el nombre que se asigna a cada una de ellas, estos nombres coinciden con los utilizados en la consulta sql que será ejecutada.

Para este trabajo se hacen uso de dos dataset los cuales contendrán la información proveniente de cada Excel por separado, que en el próximo paso será usada para trabarla y poder adecuarla a la información que se necesita visualizar.

Uso de Linq para unir la información
var query = from item in
                 (from item in dtExcel1.AsEnumerable()
                  select item).Union(from item in dtExcel2.AsEnumerable()
                                     select item)
             group item by Convert.ToInt32(item["pedido"]) into g
             select new
             {
                 Pedido = g.Key,
                 Cantidad = g.Sum(x=>Convert.ToInt32(x["cantidad"]))
             };

La consulta linq realiza varias operaciones para poder adecuar la información proveniente del Excel y que en este punto se encuentra contendida en cada dataset.

El primer paso que realiza es la unión de los dos dataset, o sea la información de los Excel, unificando en un solo grupo de datos los pedidos de ambas listas.

El segundo paso es la realización del Group by, el cual permitirá unir números de pedidos que se encuentren repetidos en ambos listados.

Por ultimo crea un nueva entidad anónima, es por ellos que utiliza el “select new”, en donde define de forma dinámica las propiedades y si contenido.

En este caso solo son dos las propiedades necesarias, el valor Key del objeto que actuó como agrupador y el cual contiene la lista de ítems sin repetir del listado. Y la suma de la cantidad reportada de cada ítem agrupado.

 

[C#]
 

[DataGridView] KeyPress detectar ENTER y búsqueda

 

El objetivo del artículo es demostrar como mediante la edición de una celda se puede detectar la presión de la tecla ENTER, la cual será capturada para realizar una búsqueda con la información ingresada en la celda

Se debe aclarar que las columnas que no se requiere búsqueda deberá asignarse la propiedad readonly para evitar problemas, pues por defecto también entraran en la lógica que controla la pulsación del enter, salvo que se agregue validaciones para que no actué sobre estas celdas.

 

private void dataGridView1_CellEnter(object sender, DataGridViewCellEventArgs e)
{
    dataGridView1.BeginEdit(false);
}

private void dataGridView1_EditingControlShowing(object sender, DataGridViewEditingControlShowingEventArgs e)
{
    DataGridViewTextBoxEditingControl dText = (DataGridViewTextBoxEditingControl)e.Control;
    dText.KeyUp -= new KeyEventHandler(text_KeyUp);
    dText.KeyUp += new KeyEventHandler(text_KeyUp);
}

void text_KeyUp(object sender, KeyEventArgs e)
{
    int rowIndex = ((System.Windows.Forms.DataGridViewTextBoxEditingControl)(sender)).EditingControlRowIndex;
    
    if (e.KeyCode == Keys.Enter)
    {
        int valueEntered = Convert.ToInt32(dataGridView1.Rows[rowIndex - 1].Cells["cuenta"].Value);

        dataGridView1.Rows[rowIndex - 1].Cells["descripcion"].Value = this.Search(valueEntered);
    }
}

Como se observa hay varios eventos asociados al DataGridView necesarios para poder controlar la pulsación del enter en las celdas.

Al editar la calda esta asocia el evento una clase de nombre DataGridViewTextBoxEditingControl, la cual representa un TextBox control asociado a una DataGridViewTextBoxCell.

Se vera además que se realiza una operación de búsqueda, en este caso para simplificar el ejemplo se realiza sobre un datatable cargado previamente, para lo cual se ha utilizado LinQ

 

 

private DataTable GetDataTable()
{
    DataTable dt = new DataTable();

    dt.Columns.Add("cuenta");
    dt.Columns.Add("descripcion");
    

    DataRow row = dt.NewRow();
    row["cuenta"] = "1001";
    row["descripcion"] = "cuenta 1001";
    dt.Rows.Add(row);

    row = dt.NewRow();
    row["cuenta"] = "1002";
    row["descripcion"] = "cuenta 1002";
    dt.Rows.Add(row);

    row = dt.NewRow();
    row["cuenta"] = "1003";
    row["descripcion"] = "cuenta 1003";
    dt.Rows.Add(row);

    return dt;


}

private string Search(int cuenta)
{
    string descripcion = (from item in this.GetDataTable().AsEnumerable()
                         where Convert.ToInt32(item["cuenta"]) == cuenta
                         select item["descripcion"].ToString()).FirstOrDefault<string>();

    return descripcion;
}

[C#]
 

[WinForms] MDI – Formularios modales

 

En un ambiente MDI es sabido que los formulario hijos no podrán ser desplegados mediante el método ShowDialog() para que estos se comportan de forma modal, pero a veces es necesario que este formulario este en primer plano hasta que este completo para después si poder cerrarlo y capturar la información ingresada.

La solución a este problema se logra mediante el uso de una función del  API de Windows

<System.Runtime.InteropServices.DllImport("user32.DLL")> _
Public Shared Function SetParent(ByVal hWndChild As Integer, ByVal hWndNewParent As Integer) As Integer
End Function


Private Sub btnshowmodal_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btnshowmodal.Click

    Dim frm As New Form3
    frm.Show()

    SetParent(CInt(frm.Handle), CInt(Me.MdiParent.Handle))

End Sub

Como se vera en ele ejemplo se sigue utilizando el método Show() el cual se usa en un ambiente MDI, solo que ahora se realiza un paso adicional al utilizar la funcionalidad del Sistema Operativo para establecer las relaciones entre ventanas.

[VB.NET] 
 

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]

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] 

sábado, 17 de octubre de 2009

[ASP.NET] Pasar información entre User Control

 

Introducción


El desarrollo modular de aplicaciones web requiere la confección de componentes que permitan encapsular ciertos compartimientos que serán reutilizados, o que necesitan ser tratados de una forma unificada para facilitar la mantenibilidad del desarrollo.

Es por eso que en ciertas ocasiones el uso de User Controls es una buena práctica, aunque un aspecto no menor es la comunicación entre estos componentes encapsulados con el resto de la aplicación, o con otros componentes.

En este texto explicare como lograr la comunicación entre dos User Control, pasando datos de uno a otro de una forma desacoplada.

 

Diseño


El planteo se basará en la utilización de dos users control muy simples que estarán contenidos en una misma página web.

El primer user control encapsulará una grilla la cual cargara un listado de empleados.

El segundo contiene una serie de Textbox que serán utilizados para visualizar el registro seleccionado del primer user control.

 

User Control – Listado


Este estará formado por un control GridView.

using System;
using System.Collections.Generic;
using System.Web;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.Data;

namespace WebUserControlsCommunicated
{
    public partial class ucGrid : System.Web.UI.UserControl
    {
        public delegate void GridSelectorCommandEventHandler(GridSelectorCommandEventArgs e);
        public event GridSelectorCommandEventHandler GridSelectorChanged;

        public class GridSelectorCommandEventArgs
        {
            public int Id { get; protected set; }
            public string Nombre { get; protected set; }
            public string Cargo { get; protected set; }

            public GridSelectorCommandEventArgs(int id, string nombre, string cargo)
            {
                this.Id = id;
                this.Nombre = nombre;
                this.Cargo = cargo;
            }
        }


        public DataTable DataSource { get; set; }


        protected void Page_Load(object sender, EventArgs e)
        {
            if (!IsPostBack)
            {
                GridView1.DataSource = this.DataSource;
                GridView1.DataBind();
            }
        }

        protected void GridView1_SelectedIndexChanged(object sender, EventArgs e)
        {
            int Id = Convert.ToInt32(GridView1.SelectedRow.Cells[1].Text);
            string nombre = Convert.ToString(GridView1.SelectedRow.Cells[2].Text);
            string cargo = Convert.ToString(GridView1.SelectedRow.Cells[3].Text);


            if (GridSelectorChanged != null)
                GridSelectorChanged(new GridSelectorCommandEventArgs(Id, nombre, cargo));

            
        }
    }
}

Los puntos importantes a destacar del código serian:

- línea 30, define la propiedad que permitirá asignar el origen de datos que se usará para cargar el GridView. Esta propiedad será utilizada en el evento Page_Load del user control, asignando el contenido.

- líneas 12 y 13, en las mismas se define el handler y evento que permitirán que la información  de los items seleccionados del GridView viajen desde el interior del user control hacia la página.

- líneas 15-27, definen el argumento del evento que será usado para realizar el pasaje de los datos seleccionados en la grilla. Es por medio de este argumento que se podrá recuperar los valores elegidos, ya que desde fuera del user control no se tendra acceso directo al GridView.

Habría una alternativas a esta opción, la cual consiste en dejar disponible en una propiedad los valores seleccionados, imitando un poco a las propiedades como ser el SelectedRow, o SelectedItems que poseen otro controles.

- líneas 42-53, estas defines el evento local al user control que se ejecutará al utilizar el botón de selección del GridView, básicamente será una especie de conversión de eventos, en donde un evento atrapado localmente, es transformado en un evento exterior, el cual es lanzado en la línea 50.

Como ser observara las primeras acciones son las de recuperar los valores de la columnas de la fila seleccionada, para poderlos usar como parámetros del argumento del evento que se lanzara.

Es muy importante cuando se va a lanzar un evento validar que este tenga algún método adjunto, ya que de no tener ninguno y ejecutarse este producirá un fallo, la validación por distinto de null evita este problema.

 

User Control – Listado de TextBox


Este user control contendrá una serie de TextBox, destinado a la visualización de la información seleccionada en el user control que contiene el GridView

using System;
using System.Collections.Generic;
using System.Web;
using System.Web.UI;
using System.Web.UI.WebControls;

namespace WebUserControlsCommunicated
{
    public partial class ucTextView : System.Web.UI.UserControl
    {

        public int Id { get; set; }
        public string Nombre { get; set; }
        public string Cargo { get; set; }

        protected void Page_Load(object sender, EventArgs e)
        {

        }

        public void Refresh(int id, string nombre, string cargo)
        {
            this.Id = Id;
            this.Nombre = nombre;
            this.Cargo = cargo;

            this.Refresh();
        }

        public void Refresh()
        {
            txtId.Text = Convert.ToString(this.Id);
            txtNombre.Text = this.Nombre;
            txtCargo.Text = this.Cargo;
        }
    }
}

Este control tiene bastante menos lógica que implementar ya que su funcionalidad se reduce a recibir los valores y asignarlos a los TextBox que correspondan.

Como se observa cuenta con una serie de propiedades que representan cada atributo de la entidad.

Y un método Refresh() que será el encargado de realizar en concreto la asignación de cada propiedad con su respectivo control.

 

Integración – Default.aspx


Bien llego el momento de poner todo en conjunto a funcionar.

Para ello se hará uso del formulario web, Default.aspx, es allí donde se arrastrara cada user control en el diseñador de la pagina, haciendo uso de una tabla para dar algo de formato y ubicación.

using System;
using System.Collections.Generic;
using System.Web;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.Data;

namespace WebUserControlsCommunicated
{
    public partial class _Default : System.Web.UI.Page
    {
        protected void Page_Load(object sender, EventArgs e)
        {
            ucGrid1.GridSelectorChanged += new ucGrid.GridSelectorCommandEventHandler(ucGrid1_GridSelectorChanged);

            if (!IsPostBack)
            {
                ucGrid1.DataSource = CargarTabla();
            }
        }

        void ucGrid1_GridSelectorChanged(ucGrid.GridSelectorCommandEventArgs e)
        {
            ucTextView1.Id = e.Id;
            ucTextView1.Nombre = e.Nombre;
            ucTextView1.Cargo = e.Cargo;

            ucTextView1.Refresh(); 
        }



        private DataTable CargarTabla()
        {
            DataTable dt = new DataTable();
            dt.Columns.Add("Id");
            dt.Columns.Add("Nombre");
            dt.Columns.Add("Cargo");

            DataRow row = dt.NewRow();
            row["Id"] = 1;
            row["Nombre"] = "Andres";
            row["Cargo"] = "Developer";
            dt.Rows.Add(row);

            row = dt.NewRow();
            row["Id"] = 2;
            row["Nombre"] = "Federico";
            row["Cargo"] = "PM";
            dt.Rows.Add(row);

            row = dt.NewRow();
            row["Id"] = 3;
            row["Nombre"] = "Leonardo";
            row["Cargo"] = "Developer";
            dt.Rows.Add(row);

            return dt;

        }

    }
}

En la línea 14 es donde se define la utilización del evento expuesto por el user control que contiene la grilla, la asignación del handler requiere de un método declarado en las líneas 22-29.

Es en este método ucGrid1_GridSelectorChanged donde se hará uso de los parámetro del argumento del evento GridSelectorCommandEventArgs, para especificar los datos al segundo user control.

Como se habrá notado en este caso se asignan las propiedades directamente al segundo user control, para llamar por ultimo el método Refresh() que realizara la asignación de estos valores dentro del control; pero también se podría haber utilizado para tal fin el método Refresh() sobrecargado con los valores de cada propiedad necesaria para desplegar la información.

[C#]