domingo, 27 de febrero de 2011

Crystal Reports – Cargar imagen usando una capa de reportes

 

Introducción


En esta oportunidad se profundizara el trabajo de imágenes pero apuntando a Reportes, concretamente con el uso de Crystal Reports.

Para este artículo se continua con el ejemplo de uno previo:

[WinForms] Edición Empleados – Grabar imagen en base de datos

En el anterior se listaba y editaba los datos del empleado, incluida sus fotografías, en cambio en este artículo se vera como listar en un reporte esta misma información.

El resultado del reporte final del reporte seria:

imagen1

Se analizara además como incluir las imágenes provenientes de una base de datos, y también un logo tomado de un archivo de imagen.

 

Capa de Reportes


Esto quizás aplique un poco mejor con una arquitectura en capas, pero en este caso aunque no las haya definido del todo, se puede separa en un proyecto concreto la responsabilidad de crear los reportes.

Es por eso que se observara en la solución un proyecto de nombre ReportsLayer, este será el encargado de:

- Encapsular la diseño del reporte

- Definición y estructura de datos que requieres el reporte, en este caso implementada en dataset tipados

- La carga de la información, conectándose para ello directamente a los datos, esta capa no hará uso del DataAccess, porque al usar dataset la carga de datos se torna particular, por lo tanto su funcionalidad requiere una conexión directa.

 imagen2

La idea con esto es separar funcionalidad y además cubrir un defecto que tiene Crystal Reports, en donde el diseñador solo toma como entidades objetos que estén local al proyecto donde se encuentra el rpt. Muchas veces poner en la Presentación un reporte implicaría además poner allí mismo los dataset tipados, lo cual ensucia el modelo.

 

Definición y carga de datos en el DataSet


Para este reporte se definió un dataset tipado con dos datatable en su interior.

imagen3

Para al carga de los empleados se hará uso de la funcionalidad de la clase EmpleadosDAL definida dentro del propio proyecto de Reportes.

internal static class EmpleadosDAL
    {

        internal static Empleados ObtenerTodos()
        {
            Empleados empleados = new Empleados();

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

                string query = @"SELECT IdEmpleado, Nombre, Apellido, FechaNacimiento, EstadoCivil, Imagen
                                 FROM Empleados";

                SqlCommand cmd = new SqlCommand(query, conn);

                SqlDataAdapter da = new SqlDataAdapter(cmd);
                //es necesario indicar la tabla del dataset que se quiere cargar
                da.Fill(empleados, "Empleados"); 

            }

            return empleados;
        }
    }

Allí se define la consulta en donde las columnas coinciden en nombre con los definidos en timepo de diseño en el dataset tipado, además vale aclarar que la clase ha sido declarada como internal de forma intencional, para que solo la capa de Reportes pueda usar esta funcionalidad, es mas la idea es que se limite al máximo el acceso a funcionalidad que solo esta clase debería utilizar, por eso el dataset tipado también tiene el modificador de acceso asignado a internal.

 imagen4

Un punto adicional es la carga de una imagen externa que representa el logo de la empresa, el mismo no se encuentra en la db sino que es un archivo, es por eso que luego de cargar el reporte se observan las líneas:

Empleados.EmpresaRow row = empleado.Empresa.NewEmpresaRow();
row.Logo = ImageHelper.ImageToByteArray(ImageHelper.ObtenerImagenLogoEmpresa());
empleado.Empresa.Rows.Add(row);

encargadas justamente de crear una row en al datatable con la imagen del logo. En este caso se hace uso de la funcionalidad del Helper de Imágenes creado para tomar la imagen embebida como recurso.

 

Retorno del Reporte


Esta cada de reportes solo debería ser accedida por medio de la clase Reports con su metodo ObtenerReporteEmpleados() este devolverá la instancia del reporte con la información asignada lista para ser mostrada en pantalla, o exportada si es necesario.

public static class Reports
    {

        public static ReporteEmpleados ObtenerReporteEmpleados()
        {
            ReporteEmpleados report = new ReporteEmpleados();

            //
            // Se obtienen los datos de la lista de empleados
            //
            Empleados empleado = EmpleadosDAL.ObtenerTodos();

            //
            // Se agrega el logo de la empresa a la informacion del listado
            //
            Empleados.EmpresaRow row = empleado.Empresa.NewEmpresaRow();
            row.Logo = ImageHelper.ImageToByteArray(ImageHelper.ObtenerImagenLogoEmpresa());
            empleado.Empresa.Rows.Add(row);

            //
            // Se asigna los datos a la instancia del reporte
            //
            report.SetDataSource(empleado);

            return report;
        }

    }

Lanzar el Reporte en al Presentación


El ultimo punto por tratar es como se usara lo anteriormente explicado desde la presentación.

Por un lado contamos con un formulario especialmente creado para desplegar el reporte, el mismo solo cuanta con el CrystalReportViewer y recibe por parámetro la instancia del reporte que debe mostrar.

public partial class Reporte : Form
{
    private ReportClass _report = null;

    public Reporte()
    {
        InitializeComponent();
    }

    public Reporte(ReportClass report)
        :this()
    {
        _report = report;
    }

    private void Reporte_Load(object sender, EventArgs e)
    {
        crystalReportViewer1.ReportSource = _report;
    }
}

Se define un nuevo constructor del formulario para pasar la instancia del reporte al formulario y es en el Load del mismo que se asigna al Viewer, para desplegar el reporte en pantalla.

Por otro lado tenemos un botón en la pantalla de ListaEmpleados, el cual recupera el reporte, con los datos asignados, y se la pasa a la instancia del formulario para que la muestre en pantalla.

private void btnListar_Click(object sender, EventArgs e)
{

    ReporteEmpleados report = Reports.ObtenerReporteEmpleados();

    Reporte frmReporte = new Reporte(report);
    frmReporte.Show();

}

 

Código de ejemplo


La base de datos utilizada en el ejemplo es la Sql Server Express 2008 R2, como ver en la solución el mdf esta integrado al Visual Studio, por lo tanto con solo tener el sql server express instado esta debería funciona adjuntándose sola al servicio.

En la carpeta “script” del proyecto “DataAccess”  se encuentra un archivo .sql con las instrucciones para crear la estructura de tablas y datos que se requieren para este articulo.

 

[C#] 
[VB.NET] 

martes, 22 de febrero de 2011

[ASP.NET] Lockear Edición de Entidades

 

Introducción

La finalidad del articulo será demostrar como implementar lockeos pesimistas usando las características que un entorno web puede brindar.

Seguramente será bien conocido el lockeo por medio de la base de datos, definiendo un campo del tipo TimeSpan y ante una actualización validar si este ha cambiado, si lo hizo se informa al usuario y se evita la operación hasta resolver el conflicto. Este se lo denomina lockeo optimista ya que no evita que cada usuario edite el registro, pero lo valida al momento de realizar la transacción.

Ahora bien como implementar aprovechando las características de un entorno web un lockeo de entidad distinto

Primero mencionaremos porque un entorno web provee ciertas características que hacen posible este tipo de implementación

- se dispone de un ambiente centralizado, todos lo usuario debes consultar el servidor para poder editar las entidades

- distintas sesiones se pueden comunicar y mantener información global a nivel del sitio por medio de la declaración de variables estáticas, usar esto o el objeto Application de asp.net es semejando ya que se comportan de la misma forma

Locker Helper

El núcleo por el cual el lockeo es implementado se desarrolla en una clase muy simple

public static class LockHelper<TEntity> where TEntity : class
{
    private static Dictionary<string, List<LockInfo>> lockList = new Dictionary<string,List<LockInfo>>();

    public static LockResult Lock(int idEntity, string userName)
    {
        LockInfo newLock = null;

        if (!lockList.ContainsKey(typeof(TEntity).Name))
        {
            newLock = new LockInfo()
            {
                UserName = userName,
                IdEntity = idEntity
            };

            lock (lockList)
            {
                lockList.Add(typeof(TEntity).Name, new List<LockInfo>() { newLock });
            }
            
            
            return new LockResult()
                {
                    Result = LockResultEnum.Succesully,
                    LockInfo = newLock
                };
        }

        //
        // Se valida sino estaba previamente lockeado por alguien mas
        //
        LockInfo alreadyLockedResult = lockList[typeof(TEntity).Name]
                                            .FirstOrDefault(o => o.IdEntity == idEntity
                                                                    && o.UserName != userName);

        if (alreadyLockedResult != null)
            return new LockResult()
            {
                Result = LockResultEnum.AlreadyLocked,
                LockInfo = alreadyLockedResult
            };

        //
        // Si se intenta lockear algo que ya estaba previamente lockeado solo se devuelve
        // el LockInfo que se tenia
        //
        LockInfo lockResult = lockList[typeof(TEntity).Name]
                                    .FirstOrDefault(o => o.IdEntity == idEntity 
                                                            && o.UserName == userName);
        if (lockResult != null)
            return new LockResult()
                        {
                            Result = LockResultEnum.Succesully,
                            LockInfo = lockResult
                        }; 


        newLock = new LockInfo()
        {
            UserName = userName,
            IdEntity = idEntity
        };

        lock (lockList)
        {
            lockList[typeof(TEntity).Name].Add(newLock);
        }

        return new LockResult()
                {
                    Result = LockResultEnum.Succesully,
                    LockInfo = newLock
                };
    }

    public static void UnLock(int idEntity)
    {
        lock (lockList)
        {
            lockList[typeof(TEntity).Name].RemoveAll(o => o.IdEntity == idEntity);
        }
    }


}


public class LockInfo
{
    public string UserName { get; set; }
    public int IdEntity { get; set; }
}

public class LockResult
{
    public LockResultEnum Result { get; set; }
    public LockInfo LockInfo { get; set; }
}

public enum LockResultEnum
{
    AlreadyLocked = 0,
    Succesully = 1
}

Esta básicamente esta compuesta por método estáticos y particularmente es la que define el

private static Dictionary<string, List<LockInfo>> lockList

esta linea aunque parece insignificante es donde se contendrá la información de lockeos para todos los usuario que trabajen con la aplicación, el helper básicamente realizara consulta, agregara y quitara elementos en esta lista.

Como comente anteriormente ls lista esta definida con static para que esta actué como si fuera el objeto Application y este disponible a nivel del sitio, o sea pueda ser accedida por todos las sesiones y request que se realicen, seria como un repositorio en memoria.

 

Como usarlo ?

Esta es la gran pregunta, como hacer uso de la funcionalidad implementada.

 

        protected void gvCourse_RowEditing(object sender, GridViewEditEventArgs e)
        {
            lblMessage.Text = string.Empty;

            //
            //Obtengo el id de la entidad que se esta editando
            //
            int id = Convert.ToInt32(gvCourse.DataKeys[e.NewEditIndex].Value);

            LockResult resultLock = LockHelper<Course>.Lock(id, Thread.CurrentPrincipal.Identity.Name);

            if (resultLock.Result == LockResultEnum.Succesully)
            {
                gvCourse.EditIndex = e.NewEditIndex;

                DataBindGridView();
            }
            else
                lblMessage.Text = string.Format("El Curso {0} esta siendo editado por: {1}", id, resultLock.LockInfo.UserName);

        }

        protected void gvCourse_RowCancelingEdit(object sender, GridViewCancelEditEventArgs e)
        {
            int id = Convert.ToInt32(gvCourse.DataKeys[e.RowIndex].Value);

            LockHelper<Course>.UnLock(id);

            gvCourse.EditIndex = -1;
            DataBindGridView();
        }

Bien, como se observa es en la edición de registros de un GridView donde se realiza la operación de lockeo, al metodo Lock() se le proporciona el id de la entidad que se quiere bloquear y el usuario que solicita esta acción.

Si todo va bien se edita el registro, pero puede ocurrir que este tomada la edición por otro usuario, en tal caso el método devolverá en su respuesta el estado que lo indica y es usado para notificar al usuario.

Codigo de Ejemplo

El código de ejemplo cuanta con una base de datos creada en Sql Server 2008, pero en caso de tener problemas en el uso podrían ejecutarse los script que acompañan al ejemplo.

Para autenticarse al ejecutar el ejemplo solo debe ingresar correctamente el nombre del usuario, ya que aplica una validación simple sin verificar el password, en este puede ingresarse cualquier valor

Como nombres de usuario podrías elegir: Alonso, Suarez, Lopez, cualquiera de estos serán validos, pero si se quiere se puede consultar la tabla Person allí esta el resto.

 

[C#]
 

domingo, 13 de febrero de 2011

[WinForms] Edición Empleados – Grabar imagen en base de datos

 

Introducción


El objetivo de este articulo será reflejar los siguiente puntos:

- Grabar una imagen en un campo de una base de datos

- Recuperar la imagen visualizándola en un celda del DataGridView y en un PictureBox

- Comunicar un formulario pasándole información en el constructor para la edición de una entidad

- Detectar el cierre del formulario para actualizar la información de los empleados

 

Dominio de la aplicación


Bien empezaremos definiendo al estructura de datos de la aplicación.

imagen1

 

El dominio consiste en Empleados, con información básica del mismo, mas un campo de imagen el cual es opcional por lo cual esta marcado para permitir nulos.

El atributo Estado Civil tomara los valores de 1 a 4, los cuales fueron definido en un enumerado.

Adicionalmente el empleado podrá tener Estudios realizados, los cuales se asignan marcando de una lista.

El diseño de la interfaz es simple, un formulario principal que lista los usuarios.

imagen2

Y otro que edita el usuario cuando se realiza un doble click en la fila de la lista.

imagen3

La arquitectura de la aplicación es en dos capas, la presentación se comunica con DataAccess pasándole la información que necesita persistir o recuperar, el medio de comunicación son entidades definidas por medio de clases que modelan el dominio de la aplicación.

imagen4

Si bien el esquema de estas entidades son muy parecido al de la base de datos ,se debe apreciar que la entidad “Empleado” posee una relación con la de estudios, lo cual posibilita la navegación de la información asociada.

 

Cargar lista de empleados (con imágenes)


En esta sección analizaremos como cargar las imágenes en el DataGridView, tomando esta información desde la base de datos.

El primer paso será recuperar los registros de la tabla, incluyendo la imagen.

[C#]

public static List<EmpleadoEntity> ObtenerTodos()
{
    List<EmpleadoEntity> lista = new List<EmpleadoEntity>();

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

        string query = @"SELECT IdEmpleado, Nombre, Apellido, FechaNacimiento, EstadoCivil, Imagen
                         FROM Empleados";

        SqlCommand cmd = new SqlCommand(query, conn);

        SqlDataReader reader = cmd.ExecuteReader();

        while (reader.Read())
        {
            lista.Add(ConvertirEmpleado(reader, false));
        }

    }

    return lista;
}

private static EmpleadoEntity ConvertirEmpleado(IDataReader reader, bool cargarRelaciones)
{
    EmpleadoEntity empleado = new EmpleadoEntity();

    empleado.IdEmpleado = Convert.ToInt32(reader["IdEmpleado"]);
    empleado.Nombre = Convert.ToString(reader["Nombre"]);
    empleado.Apellido = Convert.ToString(reader["Apellido"]);
    empleado.FechaNacimiento = Convert.ToDateTime(reader["FechaNacimiento"]);
    empleado.EstadoCivil = Convert.ToInt16(reader["EstadoCivil"]);

    if (reader["Imagen"] != DBNull.Value)
        empleado.Imagen = (byte[])reader["Imagen"];

    if (cargarRelaciones)
    {
        empleado.Estudios = EstudiosDAL.ObtenerAsignadoEmpleado(empleado.IdEmpleado);
    }

    return empleado;
}		

[VB.NET]

Public Shared Function ObtenerTodos() As List(Of EmpleadoEntity)

    Dim lista As New List(Of EmpleadoEntity)()

    Using conn As New SqlConnection(ConfigurationManager.ConnectionStrings("default").ToString())
        conn.Open()

        Dim query As String = "SELECT IdEmpleado, Nombre, Apellido, FechaNacimiento, EstadoCivil, Imagen" & _
                              "FROM Empleados"

        Dim cmd As New SqlCommand(query, conn)

        Dim reader As SqlDataReader = cmd.ExecuteReader()

        While reader.Read()
            lista.Add(ConvertirEmpleado(reader, False))
        End While

    End Using

    Return lista

End Function


Private Shared Function ConvertirEmpleado(ByVal reader As IDataReader, ByVal cargarRelaciones As Boolean) As EmpleadoEntity

    Dim empleado As New EmpleadoEntity()

    empleado.IdEmpleado = Convert.ToInt32(reader("IdEmpleado"))
    empleado.Nombre = Convert.ToString(reader("Nombre"))
    empleado.Apellido = Convert.ToString(reader("Apellido"))
    empleado.FechaNacimiento = Convert.ToDateTime(reader("FechaNacimiento"))
    empleado.EstadoCivil = Convert.ToInt16(reader("EstadoCivil"))

    If reader("Imagen") IsNot DBNull.Value Then
        empleado.Imagen = DirectCast(reader("Imagen"), Byte())
    End If

    If cargarRelaciones Then
        empleado.Estudios = EstudiosDAL.ObtenerAsignadoEmpleado(empleado.IdEmpleado)
    End If

    Return empleado

End Function	

Es preciso notar como se asigna la imagen a la entidad

[C#]

if (reader["Imagen"] != DBNull.Value)
    empleado.Imagen = (byte[])reader["Imagen"];

[VB.NET]

If reader("Imagen") IsNot DBNull.Value Then
    empleado.Imagen = DirectCast(reader("Imagen"), Byte())
End If

lo cual resulta muy simple.

En el formulario “ListaEmpleados” se usa esta información para cargar el DataGridView

[C#]

private void CargarListaEmpleados()
{
    dgvEmpleados.AutoGenerateColumns = false;
    dgvEmpleados.DataSource = EmpleadosDAL.ObtenerTodos();

    foreach (DataGridViewRow row in dgvEmpleados.Rows)
    {
        //se asigna el alto de la fila para poder ver la imagen correctamente
        row.Height = 120; 

        //se recupera la entidad que es asignada como dato
        EmpleadoEntity empleado = row.DataBoundItem as EmpleadoEntity;

        if (empleado.Imagen == null)
            row.Cells["Imagen"].Value = ImageHelper.ObtenerImagenNoDisponible();
        else
            row.Cells["Imagen"].Value = ImageHelper.ByteArrayToImage(empleado.Imagen); 
    }

}

[VB.NET]

Private Sub CargarListaEmpleados()
	dgvEmpleados.AutoGenerateColumns = False
	dgvEmpleados.DataSource = EmpleadosDAL.ObtenerTodos()

	For Each row As DataGridViewRow In dgvEmpleados.Rows
		'se asigna el alto de la fila para poder ver la imagen correctamente
		row.Height = 120

		'se recupera la entidad que es asignada como dato
		Dim empleado As EmpleadoEntity = TryCast(row.DataBoundItem, EmpleadoEntity)

		If empleado.Imagen Is Nothing Then
			row.Cells("Imagen").Value = ImageHelper.ObtenerImagenNoDisponible()
		Else
			row.Cells("Imagen").Value = ImageHelper.ByteArrayToImage(empleado.Imagen)
		End If
	Next

End Sub

seguramente una pregunta que se hagan es porque en este método se esta recorriendo las filas del DataGridView después de asignar los datos al mismo ?

bien esto tiene una explicación, resulta que la celda de imagen del DataGridView requiere un tipo Image para poder trabajar, pero la entidad que proporcionamos devuelve un array de byte, lo cual no es compatible, entonces es que se aprovecha la capacidad del DataGridView para retornar la entidad que bindea a cada fila para obtener la entidad “Empleado” y convertir el array de byte al tipo Image.

También debo aclara que esto se intento hacer en un evento, como ser el CellFormatting, pero resulto un desastre porque este evento repintaba constantemente la información generando un parpadeo molesto en el control, se podría haber realizado esto mismo en el evento DataBindingComplete, pero para este caso no valía la pena separa la funcionalidad, por eso se dejo allí mismo.

En ese método se hace uso del la funcionalidad de “helper” que no es mas que un grupo de funciones que facilitan ciertas tareas, en este caso convierten el array de byte en Image.

 

Comunicar Formulario y detectar cierre del form hijo


Al realizar un doble click en la celda del DataGridView este entra en modo edición, esto implica simplemente toma la información y pacérsela al formulario

Desde “ListaEmpleados” se trabaja con el evento tomando solo el id del empleado, y en la instancia del formulario se lo pasa en el constructor. También en la misma operación se adjunta el evento FormClosing para poder detectar el cierre el form y proceder a la actualización del la lista de empleados.

[C#]

private void dgvEmpleados_CellContentDoubleClick(object sender, DataGridViewCellEventArgs e)
{
    int IdEmpleado = Convert.ToInt32(dgvEmpleados.Rows[e.RowIndex].Cells["IdEmpleado"].Value);

    //
    // al pasarle un id de empleado este lo cargara para su edicion
    //
    EditarEmpleado frmEditar = new EditarEmpleado(IdEmpleado);
    frmEditar.FormClosing += new FormClosingEventHandler(frmEditar_FormClosing);

    frmEditar.Show();
}

void frmEditar_FormClosing(object sender, FormClosingEventArgs e)
{
	//
	// al cerrarse el form de edicion se ingresa a este evento 
	// para actualizar la informacion del listado
	//

	EditarEmpleado frmEdit = sender as EditarEmpleado;

	if(frmEdit.DialogResult == DialogResult.OK)
		CargarListaEmpleados();

}

[VB.NET]

Private Sub dgvEmpleados_CellContentDoubleClick(ByVal sender As Object, ByVal e As DataGridViewCellEventArgs)

    Dim IdEmpleado As Integer = Convert.ToInt32(dgvEmpleados.Rows(e.RowIndex).Cells("IdEmpleado").Value)

    '
    ' al pasarle un id de empleado este lo cargara para su edicion
    '
    Dim frmEditar As New EditarEmpleado(IdEmpleado)
    AddHandler frmEditar.FormClosing, New FormClosingEventHandler(AddressOf frmEditar_FormClosing)

    frmEditar.Show()

End Sub

Private Sub frmEditar_FormClosing(sender As Object, e As FormClosingEventArgs)
	'
	' al cerrarse el form de edicion se ingresa a este evento 
	' para actualizar la informacion del listado
	'

	Dim frmEdit As EditarEmpleado = TryCast(sender, EditarEmpleado)

	If frmEdit.DialogResult = DialogResult.OK Then
		CargarListaEmpleados()
	End If

End Sub

En el formulario que recibe esta información “EditarEmpleado” solo utiliza el id del empleado, porque con este ira a la db y recuperara la entidad para cargar el formulario, no hace falta enviar la entidad completa, solo la mínima información como para que este form pueda recuperar el resto.

[C#]

  public partial class EditarEmpleado : Form
   {
       private int? _idEmpleado = null;

       public EditarEmpleado()
       {
           InitializeComponent();
       }

       public EditarEmpleado(int idEmpleado)
           : this()
       {
           _idEmpleado = idEmpleado;
       }
	
	.
	.
	.
}	

[VB.NET]

 

Public Partial Class EditarEmpleado
    Inherits Form

    Private _idEmpleado As Nullable(Of Integer) = Nothing

	Public Sub New()
		InitializeComponent()
	End Sub

	Public Sub New(idEmpleado As Integer)
		Me.New()
		_idEmpleado = idEmpleado
	End Sub
	
	.
	.
	.
End Class

Cuando se termina la edición se retorna como un resultado del formulario que indicara si se grabo o se cancelo el formulario, esto se logra por medio del DialogResult.

[C#]

		
private void btnGuardar_Click(object sender, EventArgs e)
{
	.
	.
	.

	//
	// se graba 
	//
	EmpleadosDAL.Save(empleado);

	this.DialogResult = DialogResult.OK;
	this.Close();
}

private void btnCancelar_Click(object sender, EventArgs e)
{
	this.DialogResult = DialogResult.Cancel;
	this.Close();
}		

 

[VB.NET]

Private Sub btnGuardar_Click(sender As Object, e As EventArgs)

	.
	.
	.

	'
	' se graba 
	'
	EmpleadosDAL.Save(empleado)

	Me.DialogResult = DialogResult.OK
       Me.Close()

End Sub

   Private Sub btnCancelar_Click(ByVal sender As Object, ByVal e As EventArgs)

       Me.DialogResult = DialogResult.Cancel
       Me.Close()

   End Sub

Editar Empleado (mostrando la imagen en un Picturebox)


Esta tarea es bastante simple, solo implica recuperar una entidad en concreto haciendo uso de la cada de datos y cargar la entidad en los controles para presentar esta al usuario.

[C#]

public static EmpleadoEntity ObtenerById(int idEmpleado)
{
    EmpleadoEntity empleado = null;

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

        string query = @"SELECT IdEmpleado, Nombre, Apellido, FechaNacimiento, EstadoCivil, Imagen
                         FROM Empleados WHERE IdEmpleado = @idEmpleado";

        SqlCommand cmd = new SqlCommand(query, conn);
        cmd.Parameters.AddWithValue("@idEmpleado", idEmpleado);

        SqlDataReader reader = cmd.ExecuteReader();

        if (reader.Read())
        {
            empleado = ConvertirEmpleado(reader, true);
        }

    }

    return empleado;
}

[VB.NET]

Public Shared Function ObtenerById(ByVal idEmpleado As Integer) As EmpleadoEntity

    Dim empleado As EmpleadoEntity = Nothing

    Using conn As New SqlConnection(ConfigurationManager.ConnectionStrings("default").ToString())
        conn.Open()

        Dim query As String = "SELECT IdEmpleado, Nombre, Apellido, FechaNacimiento, EstadoCivil, Imagen " & _
                              "FROM Empleados WHERE IdEmpleado = @idEmpleado"

        Dim cmd As New SqlCommand(query, conn)
        cmd.Parameters.AddWithValue("@idEmpleado", idEmpleado)

        Dim reader As SqlDataReader = cmd.ExecuteReader()

        If reader.Read() Then
            empleado = ConvertirEmpleado(reader, True)
        End If

    End Using

    Return empleado

End Function

Una vez que se tiene la entidad solo se carga esta en los contoles.

[C#]

private void EditarEmpleado_Load(object sender, EventArgs e)
{
    CargarEstadoCivil();
    CargarEstudio();

    //por defecto se carga una imagen de no disponible
    picImagenEmpleado.Image = ImageHelper.ObtenerImagenNoDisponible();

    //
    // se carga la informacion del empleado que se quiere editar
    //
    if (_idEmpleado.HasValue)
    {
        EmpleadoEntity empleado = EmpleadosDAL.ObtenerById(_idEmpleado.Value);

        _idEmpleado = empleado.IdEmpleado;
        txtNombre.Text = empleado.Nombre;
        txtApellido.Text = empleado.Apellido;
        dtpFechaNacimiento.Value = empleado.FechaNacimiento;

        cbEstadoCivil.SelectedValue = Convert.ToInt32(empleado.EstadoCivil);

        if (empleado.Imagen == null)
            picImagenEmpleado.Image = ImageHelper.ObtenerImagenNoDisponible();
        else
            picImagenEmpleado.Image = ImageHelper.ByteArrayToImage(empleado.Imagen);

        //
        // Se obtienen los estudios del empleado
        //
        AsignarEstudios(empleado);

    }

}

private void CargarEstadoCivil()
{
    cbEstadoCivil.DisplayMember = "Descripcion";
    cbEstadoCivil.ValueMember = "IdEstadoCivil";
    cbEstadoCivil.DataSource = EstadoCivilDAL.ObtenerTodos();
}

private void CargarEstudio()
{
    dgvEstudios.AutoGenerateColumns = false;
    dgvEstudios.DataSource = EstudiosDAL.ObtenerTodos();
}

private void AsignarEstudios(EmpleadoEntity empleado)
{
    
    List<DataGridViewRow> rows = (from row in dgvEstudios.Rows.Cast<DataGridViewRow>()
                                    let idEstudio = Convert.ToInt32(row.Cells["IdEstudio"].Value)
                                  join dd in empleado.Estudios on idEstudio equals dd.IdEstudio
                                  select row).ToList();


    rows.ForEach(o => o.Cells["Seleccion"].Value = true);
    
}

[VB.NET]

   Private Sub EditarEmpleado_Load(ByVal sender As Object, ByVal e As EventArgs)

       CargarEstadoCivil()
       CargarEstudio()

       'por defecto se carga una imagen de no disponible
       picImagenEmpleado.Image = ImageHelper.ObtenerImagenNoDisponible()

       '
       ' se carga la informacion del empleado que se quiere editar
       '
       If _idEmpleado.HasValue Then
           Dim empleado As EmpleadoEntity = EmpleadosDAL.ObtenerById(_idEmpleado.Value)

           _idEmpleado = empleado.IdEmpleado
           txtNombre.Text = empleado.Nombre
           txtApellido.Text = empleado.Apellido
           dtpFechaNacimiento.Value = empleado.FechaNacimiento

           cbEstadoCivil.SelectedValue = Convert.ToInt32(empleado.EstadoCivil)

           If empleado.Imagen Is Nothing Then
               picImagenEmpleado.Image = ImageHelper.ObtenerImagenNoDisponible()
           Else
               picImagenEmpleado.Image = ImageHelper.ByteArrayToImage(empleado.Imagen)
           End If

           '
           ' Se obtienen los estudios del empleado
           '

           AsignarEstudios(empleado)
       End If


   End Sub

   Private Sub CargarEstadoCivil()

       cbEstadoCivil.DisplayMember = "Descripcion"
       cbEstadoCivil.ValueMember = "IdEstadoCivil"
       cbEstadoCivil.DataSource = EstadoCivilDAL.ObtenerTodos()

   End Sub

Private Sub CargarEstudio()
	dgvEstudios.AutoGenerateColumns = False
	dgvEstudios.DataSource = EstudiosDAL.ObtenerTodos()
End Sub

Private Sub AsignarEstudios(empleado As EmpleadoEntity)

       Dim rows As List(Of DataGridViewRow) = (From row In dgvEstudios.Rows.Cast(Of DataGridViewRow)() _
                                                Let idEstudio = Convert.ToInt32(row.Cells("IdEstudio").Value) _
                                                Join dd In empleado.Estudios On idEstudio Equals dd.IdEstudio _
                                                Select row).ToList()


       'rows.ForEach(Function(o) o.Cells("Seleccion").Value = True)
       rows.ForEach(Function(o) InlineAssignHelper(o.Cells("Seleccion").Value, True))

End Sub

La asignación de la imagen es muy simple y similar a como se trabajo en el DataGridview, solo se convierte el array de byte recuperado de la tabla en la base de datos y se carga directo en el PictureBox.

Algo interesante a remarcar es la forma en como se carga la lista de Estudios, en esta se usan dos consultas separadas, por un lado la primera que carga todos los estudios existentes, si se ingresara en modo de alta de empleado solo esta lista seria cargada, pero si se esta editando se recupera la lista de estudios asignados a ese empleado en particular y por medio de una consulta linq (método AsignarEstudios) se realiza la unión entres ambas listas aquellas filas del DataGridView que coincidan serán marcadas porque son las que el empleado tiene registradas en la tabla.

 

Búsqueda de una imagen (cargar archivo a un PictureBox)


Un punto que debería ser menor pero que he visto con mucha dificultad en preguntas en los foros, es como cargar seleccionar un archivo y cargarlo en un PictureBox?

El código es muy simple pero suele generar problemas.

[C#]

private void btnBuscarImagen_Click(object sender, EventArgs e)
{
    OpenFileDialog fileDialog = new OpenFileDialog();
    fileDialog.Filter = "Archivo JPG|*.jpg";

    if (fileDialog.ShowDialog() == DialogResult.OK)
    {
        picImagenEmpleado.Image = Image.FromFile(fileDialog.FileName);
    }
}

[VB.NET]

Private Sub btnBuscarImagen_Click(ByVal sender As Object, ByVal e As EventArgs)

    Dim fileDialog As New OpenFileDialog()
    fileDialog.Filter = "Archivo JPG|*.jpg"

    If fileDialog.ShowDialog() = DialogResult.OK Then
        picImagenEmpleado.Image = Image.FromFile(fileDialog.FileName)
    End If

End Sub

Persistir la edición


La operación de persistir los datos o guardarlos requiere dos etapas, una implica tomar la información de los controles y generar los objetos de dominio que la capa de datos requiere para llevar a cabo la actualización en la base de datos, la segunda será simplemente procesar la entidad generando los INSERT y UPDATE necesario.

[C#]

private void btnGuardar_Click(object sender, EventArgs e)
{
    //
    // Se crea la entidad
    //
    EmpleadoEntity empleado = new EmpleadoEntity() 
    { 
        IdEmpleado = _idEmpleado.GetValueOrDefault(),
        Nombre = txtNombre.Text,
        Apellido= txtApellido.Text,
        FechaNacimiento = dtpFechaNacimiento.Value,
        EstadoCivil = Convert.ToInt16(cbEstadoCivil.SelectedValue),
        Imagen = ImageHelper.ImageToByteArray(picImagenEmpleado.Image)
    };

    //
    // Se asignan los estudios seleccionados, se unicializa la lista 
    // para cargar la selecciond el usuario
    //
    empleado.Estudios = new List<EstudioEntity>();

    IEnumerable<DataGridViewRow> rowsSelected = dgvEstudios.Rows.Cast<DataGridViewRow>()
                                                                .Where(o => Convert.ToBoolean(o.Cells["Seleccion"].Value));

    foreach (DataGridViewRow row in rowsSelected)
    {
        EstudioEntity estudio = new EstudioEntity()
        {
            IdEstudio = Convert.ToInt32(row.Cells["IdEstudio"].Value)
        };
        
        empleado.Estudios.Add(estudio);
    }

    //
    // se graba 
    //
    EmpleadosDAL.Save(empleado);

    this.DialogResult = DialogResult.OK;
    this.Close();
}

[VB.NET]

Private Sub btnGuardar_Click(sender As Object, e As EventArgs)
	'
	' Se crea la entidad
	'
       Dim empleado As New EmpleadoEntity() With { _
                                                  .IdEmpleado = _idEmpleado.GetValueOrDefault(), _
                                                  .Nombre = txtNombre.Text, _
                                                  .Apellido = txtApellido.Text, _
                                                  .FechaNacimiento = dtpFechaNacimiento.Value, _
                                                  .EstadoCivil = Convert.ToInt16(cbEstadoCivil.SelectedValue), _
                                                  .Imagen = ImageHelper.ImageToByteArray(picImagenEmpleado.Image) _
                                                 }

	'
	' Se asignan los estudios seleccionados, se unicializa la lista 
	' para cargar la selecciond el usuario
	'
	empleado.Estudios = New List(Of EstudioEntity)()

       Dim rowsSelected As IEnumerable(Of DataGridViewRow) = dgvEstudios.Rows.Cast(Of DataGridViewRow)().Where(Function(o) Convert.ToBoolean(o.Cells("Seleccion").Value))

       For Each row As DataGridViewRow In rowsSelected

           Dim estudio As New EstudioEntity() With { _
                                                    .IdEstudio = Convert.ToInt32(row.Cells("IdEstudio").Value) _
                                                   }

           empleado.Estudios.Add(estudio)

       Next

	'
	' se graba 
	'
	EmpleadosDAL.Save(empleado)

	Me.DialogResult = DialogResult.OK
       Me.Close()

End Sub

Es preciso remarcar como en la entidad “Empleado” es cargada la lista de Estudios que este posee y solo una entidad es enviada desde la Presentación a la capa de datos. Durante esta operación también se toma la imagen del PictureBox y se convierte en un array de byte.

La actualización realiza varias operaciones las cuales son muy extensas en código, por lo tanto no será puesta directa en el articulo, pero si podrán verse en el código de ejemplo que puede descargarse.

 

Código de Ejemplo


La base de datos utilizada en el ejemplo es la Sql Server Express 2008 R2, como ver en la solución el mdf esta integrado al Visual Studio, por lo tanto con solo tener el sql server express instado esta debería funciona adjuntándose sola al servicio.

En la carpeta “script” del proyecto “DataAccess”  se encuentra un archivo .sql con las instrucciones para crear la estructura de tablas y datos que se requieren para este articulo.

 

[C#] 
[VB.NET]