domingo, 1 de diciembre de 2013

Pasar datos de un DataGridView a otro DataGridView ubicados en Forms diferentes

Hola a todos:

Motivado por uno de los usuarios del Foro de C# me tomo unos minutos parar mostrar como podemos fácilmente enviar valores de un DataGridView a otro DataGridView ubicado en un formulario diferente.
Como siempre pondré el código tanto para C# como para Vb.Net, y tratare de ser los mas explicito posible, si después de leer el articulo, descargar el proyecto de ejemplo y analizar su contenido usted no logra implementarlo, sírvase de hacer las consultas que crea necesarias proporcionando la mayor cantidad de información de su caso.

Recuerde que el objetivo del articulo no es otro mas que el de proporcionar una guía para lo Párvulos en .Net y que solo es una opción para la solución de un problema, recordando que un problema tiene muchas posibles soluciones.

Recuerde que al final de Articulo podrá encontrar los proyectos de ejemplo descargables tanto para C# y Vb.Net.

Entrando en contexto…

Crearemos un proyecto que simulara la carga de artículos de un Formulario a otro Formulario utilizando controles DataGridView para mostrar los datos.
  1. El proyecto arrancara con el Form1 como principal
  2. Al presionar el botón “Agregar” se mostrara el Form2 con un control DataGridView cargado con datos
  3. Al hacer doble Click sobre algún row del DataGridView del Form2 automáticamente se enviara la información contenida en sus celdas al DataGridView del form1, mostrándose también en los controles TextBox.
  4. El proceso antes de agregar el nuevo Row al DataGridView hará una validación sobre los datos ya agregados para descartar que este articulo no se encuentre ya en la lista, de ser así lanzara una mensaje de informando de la situación y no agregara el row, en caso contrario el row será agregado sin problemas.
Dicho todo lo anterior, procedamos a crear nuestro proyecto de ejemplo.
  1. Cree un proyecto del tipo WindowsForms
  2. Automáticamente le creo un formulario llamado Form1, dejemos el nombre de los controles por defecto para no complicarnos la vida nombrándolos en estos momentos, aunque recalco que en proyecto reales el nombre de los controles es vital.
  3. Agregue 4 controles labels y establezca en la propiedad Text, Número, Nombre, Precio y Cantidad respectivamente
  4. Agregue 4 controles TextBox
  5. Agregue un control Button y establezca en la propiedad Text, Cargar.
  6. Agregue un control DataGridView, agregue 4 columnas, establezca la propiedad SelectionMode en FullRowSelect, AllowUserToAddRows, AllowUserToDeleteRows en false
Su diseño podría ser similar a este:
frm1

Ahora, en el Form dos inserte un control DataGridView similar al que acaba de crear en el Form1, para optimizar el tiempo puede copiar y pegar el control del Form1, el diseño que se espera obtenga es similar a este:
frm2
Bien, una vez que tengamos el tema del diseño de nuestros Forms resueltos, procedamos a meter código.
Inserte una nueva clase a su proyecto y llámela EProducto, la clase tendrá una estructura como esta:
C# :
using System;

namespace DataGridViewsendRow
{
    public class EProducto
    {
        //Estas son propiedades Autoimplementadas y su uso requiere
        // del Framework 4.0 Client Profile como mínimo
        public int Numero { get; set; }
        public string Nombre { get; set; }
        public Decimal Precio { get; set; }
        public Decimal Cantidad { get; set; }
    }
}

Vb.Net :
Public Class EProducto
    'Estas son propiedades Autoimplementadas y su uso requiere
    'del Framework 4.0 Client Profile como mínimo
    Public Property Numero() As Integer
    Public Property Nombre() As String
    Public Property Precio() As Decimal
    Public Property Cantidad() As Decimal
End Class

Agregue una nueva clase y llámela IProducto, esta clase contendrá una interfaz que será el puente de comunicación entre ambos Forms, este es el Core de nuestro proyecto y ejemplo.

Dentro de la clase cree la siguiente estructura de código:

C# :
namespace DataGridViewsendRow
{
    //Nombre de la interfaz
    public interface IProducto
    {
        bool LoadDataRow(EProducto producto);
    }
}

Vb.Net :
'Nombre de la interfazPublic Interface IProducto
    Function LoadDataRow(producto As EProducto) As Boolean
End Interface

Esta es la estructura completa de código que necesitara para recibir los datos en el Form1:

C# :
using System;
using System.Collections.Generic;
using System.Linq;
using System.Windows.Forms;

namespace DataGridViewsendRow
{
    //
    //Heredamos la Interfaz IProducto, recuerde que este será nuestro puente de comunicación entre ambos Forms
    public partial class Form1 : Form, IProducto
    {
        //
        //Creamos una lista del tipo Eproducto, que será la encargada de
        //recibir la información del Form2 y almacenarla, posteriormente servirá
        //de fuente de datos para el DataGridView
        private static readonly List<EProducto> Products = new List<EProducto>();

        public Form1()
        {
            InitializeComponent();
        }

        private void button1_Click(object sender, EventArgs e)
        {
            //
            //Instanciamos el Form2
            Form2 frm = new Form2();
            //
            //Le indicamos quien lo mando a llamar usando la Propiedad Caller
            frm.Caller = this;
            //
            //Mostramos el Form2
            frm.ShowDialog();
        }

        /// <summary>
        /// Funcion encargada de agregar un nuevo Item a la lista siempre y cuando no exista
        /// </summary>
        /// <param name="product">instancia de la clase Eproduct</param>
        /// <returns>Si el Item fue agregado a la lista devuelve True si no fue agregado retorna False</returns>
        public bool LoadDataRow(EProducto product)
        {
            //Busca si el Articulo ya se encuentra en la lista
            bool exists = Products.Any(x => x.Numero.Equals(product.Numero));
            //
            //
            //Preguntamos por el resultado de la búsqueda del Articulo dentro de la lista
            if (!exists)
            {
                //
                //Si el articulo no existe dentro de la lista Mapeamos el item de la entidad Eproducto 
                //a los TextBox
                textBox1.Text = Convert.ToString(product.Numero);
                textBox2.Text = product.Nombre;
                textBox3.Text = Convert.ToString(product.Precio);
                textBox4.Text = Convert.ToString(product.Cantidad);
                //
                //Agregamos a la lista de productos el Item enviado por el Form2
                //
                Products.Add(product);
                //
                //
                //
                dataGridView1.AutoGenerateColumns = false;
                //
                //
                dataGridView1.DataSource = null;
                //
                //Establecemos el DataSource del DataGridView enlazándolo a la lista
                //genérica
                dataGridView1.DataSource = Products;
                //
                //Mapeamos las propiedades a las columnas
                dataGridView1.Columns["ColumnNumero"].DataPropertyName = "Numero";
                dataGridView1.Columns["ColumnNombre"].DataPropertyName = "Nombre";
                dataGridView1.Columns["ColumnPrecio"].DataPropertyName = "Precio";
                dataGridView1.Columns["ColumnCantidad"].DataPropertyName = "Cantidad";

                //
                //Retornamos True
                return true;
            }
            //
            //Si la condición exists es igual a False, es decir, que el producto SI existe en la lista
            //retornamos FALSE para mostrar un mensaje información
            return false;
        }
    }
}

Vb.Net :
Public Class Form1
    '
    'Heredamos la Interfaz IProducto, recuerde que este será nuestro puente de comunicación entre ambos Forms
    Implements IProducto

    'Creamos una lista del tipo Eproducto, que será la encargada de
    'recibir la información del Form2 y almacenarla, posteriormente servirá
    'de fuente de datos para el DataGridView
    Private Shared ReadOnly Products As New List(Of EProducto)()

    Private Sub button1_Click(sender As System.Object, e As System.EventArgs) Handles button1.Click
        '
        'Instanciamos el Form2
        Dim frm As New Form2()
        '
        'Le indicamos quien lo mando a llamar usando la Propiedad Caller
        frm.Caller = Me
        '
        'Mostramos el Form2
        frm.ShowDialog()
    End Sub

    ''' <summary>
    ''' Funcion encargada de agregar un nuevo Item a la lista siempre y cuando no exista
    ''' </summary>
    ''' <param name="product">instancia de la clase Eproduct</param>
    ''' <returns>Si el Item fue agregado a la lista devuelve True si no fue agregado retorna False</returns>
    Public Function LoadDataRow(product As EProducto) As Boolean Implements IProducto.LoadDataRow

        'Busca si el Articulo ya se encuentra en la lista
        Dim exists As Boolean = Products.Any(Function(x) x.Numero.Equals(product.Numero))
        '
        '
        'Preguntamos por el resultado de la búsqueda del Articulo dentro de la lista
        If Not exists Then
            '
            'Si el articulo no existe dentro de la lista Mapeamos el item de la entidad Eproducto 
            'a los TextBox
            textBox1.Text = Convert.ToString(product.Numero)
            textBox2.Text = product.Nombre
            textBox3.Text = Convert.ToString(product.Precio)
            textBox4.Text = Convert.ToString(product.Cantidad)
            '
            'Agregamos a la lista de productos el Item enviado por el Form2
            '
            Products.Add(product)
            '
            '
            '
            dataGridView1.AutoGenerateColumns = False
            '
            '
            dataGridView1.DataSource = Nothing
            '
            'Establecemos el DataSource del DataGridView enlazándolo a la lista
            'genérica
            dataGridView1.DataSource = Products
            '
            'Mapeamos las propiedades a las columnas
            dataGridView1.Columns("ColumnNumero").DataPropertyName = "Numero"
            dataGridView1.Columns("ColumnNombre").DataPropertyName = "Nombre"
            dataGridView1.Columns("ColumnPrecio").DataPropertyName = "Precio"
            dataGridView1.Columns("ColumnCantidad").DataPropertyName = "Cantidad"

            '
            'Retornamos True
            Return True
        End If
        '
        'Si la condición exists es igual a False, es decir, que el producto SI existe en la lista
        'retornamos FALSE para mostrar un mensaje información
        Return False
    End Function
End Class

Bien, ya tenemos la codificación del Form1, recuerde que este es el que hereda de la Interfaz, ahora vayamos a ver la codificación del Form2 encargado de enviar los datos del Row seleccionado:

C# :
using System;
using System.Collections.Generic;
using System.Windows.Forms;

namespace DataGridViewsendRow
{
    public partial class Form2 : Form
    {
        //
        //Creamos una instancia de la interfaz IProducto para establecer el formulario llamador
        public IProducto Caller { private get; set; }

        public Form2()
        {
            InitializeComponent();
        }

        /// <summary>
        /// Funcion encargada de devolver una lista de la Entidad EProducto cargada con datos
        /// </summary>
        /// <returns>Lista genérica de Eproucto</returns>
        private static List<EProducto> Products()
        {
            //Creamos la lista de Eproducto
            //
            List<EProducto> products = new List<EProducto>();

            //
            //Instanciamos la clase EProducto para establecerle datos
            //
            EProducto item1 = new EProducto()
            {
                Numero = 1,
                Nombre = "Nombre del producto 1",
                Precio = new decimal(5.5),
                Cantidad = new decimal(6.3)
            };
            EProducto item2 = new EProducto()
            {
                Numero = 2,
                Nombre = "Nombre del producto 2",
                Precio = new decimal(15.5),
                Cantidad = new decimal(2)
            };
            EProducto item3 = new EProducto()
            {
                Numero = 3,
                Nombre = "Nombre del producto 3",
                Precio = new decimal(25),
                Cantidad = new decimal(5)
            };
            EProducto item4 = new EProducto()
            {
                Numero = 4,
                Nombre = "Nombre del producto 4",
                Precio = new decimal(150.5),
                Cantidad = new decimal(12)
            };
            EProducto item5 = new EProducto()
            {
                Numero = 5,
                Nombre = "Nombre del producto 5",
                Precio = new decimal(4.5),
                Cantidad = new decimal(3)
            };
            EProducto item6 = new EProducto()
            {
                Numero = 6,
                Nombre = "Nombre del producto 6",
                Precio = new decimal(5),
                Cantidad = new decimal(6)
            };
            //
            //Agregamos las instancias con datos de Eproducto a la lista del mismo tipo
            //
            products.Add(item1);
            products.Add(item2);
            products.Add(item3);
            products.Add(item4);
            products.Add(item5);
            products.Add(item6);

            //
            //Devolvemos la lista
            //
            return products;
        }

        /// <summary>
        /// Método encargado de poblar el DatagridView con datos de una lista genérica, en este caso
        /// la devuelta por la función Products() creada previamente,
        /// </summary>
        private void FillDgv()
        {
            //Evitamos generar nuevas columnas a la izquierda de las que creamos en tiempo de diseño
            //
            dataGridView1.AutoGenerateColumns = false;
            //
            //Establecemos la fuente de datos, observe que únicamente mandamos a llamar a la función
            dataGridView1.DataSource = Products();
            //
            //Mapeamos las propiedades de la clase Eproducto devuelta por la función
            //Products() a las columnas del DataGridView
            //
            dataGridView1.Columns["ColumnNumero"].DataPropertyName = "Numero";
            dataGridView1.Columns["ColumnNombre"].DataPropertyName = "Nombre";
            dataGridView1.Columns["ColumnPrecio"].DataPropertyName = "Precio";
            dataGridView1.Columns["ColumnCantidad"].DataPropertyName = "Cantidad";

        }

        private void Form2_Load(object sender, EventArgs e)
        {
            //
             //Usamos el evento Load del Form2 para mostrar el DataGridView con datos
            FillDgv();
        }

        private void dataGridView1_CellDoubleClick(object sender, DataGridViewCellEventArgs e)
        {
            try
            {
                //
                //Si el row en el que hicimos doble click es el encabezado del DataGridView, nos retornamos.
                if (e.RowIndex == -1)
                    return;

                //
                //Obtenemos el row en el cual se hizo doble Click
                //
                DataGridViewRow row = dataGridView1.Rows[e.RowIndex];

                //Instanciamos la clase Eproducto para cargar los datos tomándolos de las celdas del row
                EProducto item = new EProducto
                {
                    //
                    //Recuerde convertir al tipo de dato correcto
                    //
                    Numero = Convert.ToInt32(row.Cells["ColumnNumero"].Value),
                    Nombre = Convert.ToString(row.Cells["ColumnNombre"].Value),
                    Precio = Convert.ToDecimal(row.Cells["ColumnPrecio"].Value),
                    Cantidad = Convert.ToDecimal(row.Cells["ColumnCantidad"].Value)
                };

                //
                //Si no existe llamador para nuestro formulario nos retornamos sin hacer ninguna acción
                //
                if (Caller == null)
                    return;

                //Si el Form1 devolvió false por haber encontrado el Producto dentro de la lista
                //Informamos de lo sucedido al usuario
                if (!Caller.LoadDataRow(item))
                {
                    MessageBox.Show("El Producto ya existe en la lista", "Atención", MessageBoxButtons.OK, MessageBoxIcon.Stop);
                }
            }
            catch (Exception ex)
            {
                MessageBox.Show(string.Format("Error : {0}", ex.Message), "Error Inesperado", MessageBoxButtons.OK, MessageBoxIcon.Error);
            }
        }
    }
}

Vb.Net :
Public Class Form2
    '
    'Creamos una instancia de la interfaz IProducto para establecer el formulario llamador
    Public Property Caller() As IProducto


    ''' <summary>
    ''' Función encargada de devolver una lista de la Entidad EProducto cargada con datos
    ''' </summary>
    ''' <returns>Lista genérica de Eproucto</returns>
    Private Shared Function Products() As List(Of EProducto)
        'Creamos la lista de Eproducto
        '
        Dim _products As New List(Of EProducto)()

        '
        'Instanciamos la clase EProducto para establecerle datos
        '
        Dim item1 As New EProducto()
        item1.Numero = 1
        item1.Nombre = "Nombre del producto 1"
        item1.Precio = New Decimal(5.5)
        item1.Cantidad = New Decimal(6.3)

        Dim item2 As New EProducto()
        item2.Numero = 2
        item2.Nombre = "Nombre del producto 2"
        item2.Precio = New Decimal(15.5)
        item2.Cantidad = New Decimal(2)

        Dim item3 As New EProducto()
        item3.Numero = 3
        item3.Nombre = "Nombre del producto 3"
        item3.Precio = New Decimal(25)
        item3.Cantidad = New Decimal(5)

        Dim item4 As New EProducto()
        item4.Numero = 4
        item4.Nombre = "Nombre del producto 4"
        item4.Precio = New Decimal(150.5)
        item4.Cantidad = New Decimal(12)

        Dim item5 As New EProducto()
        item5.Numero = 5
        item5.Nombre = "Nombre del producto 5"
        item5.Precio = New Decimal(4.5)
        item5.Cantidad = New Decimal(3)

        Dim item6 As New EProducto()
        item6.Numero = 6
        item6.Nombre = "Nombre del producto 6"
        item6.Precio = New Decimal(5)
        item6.Cantidad = New Decimal(6)

        '
        'Agregamos las instancias con datos de Eproducto a la lista del mismo tipo
        '
        _products.Add(item1)
        _products.Add(item2)
        _products.Add(item3)
        _products.Add(item4)
        _products.Add(item5)
        _products.Add(item6)

        '
        'Devolvemos la lista
        '
        Return _products
    End Function

    ''' <summary>
    ''' Método encargado de poblar el DatagridView con datos de una lista genérica, en este caso
    ''' la devuelta por la función Products() creada previamente,
    ''' </summary>
    Private Sub FillDgv()
        'Evitamos generar nuevas columnas a la izquierda de las que creamos en tiempo de diseño
        '
        dataGridView1.AutoGenerateColumns = False
        '
        'Establecemos la fuente de datos, observe que únicamente mandamos a llamar a la función
        dataGridView1.DataSource = Products()
        '
        'Mapeamos las propiedades de la clase Eproducto devuelta por la función
        'Products() a las columnas del DataGridView
        '
        dataGridView1.Columns("ColumnNumero").DataPropertyName = "Numero"
        dataGridView1.Columns("ColumnNombre").DataPropertyName = "Nombre"
        dataGridView1.Columns("ColumnPrecio").DataPropertyName = "Precio"
        dataGridView1.Columns("ColumnCantidad").DataPropertyName = "Cantidad"

    End Sub

    Private Sub Form2_Load(sender As System.Object, e As System.EventArgs) Handles MyBase.Load
        FillDgv()
    End Sub

    Private Sub dataGridView1_CellDoubleClick(sender As System.Object, e As System.Windows.Forms.DataGridViewCellEventArgs) Handles dataGridView1.CellDoubleClick
        Try
            '
            'Si el row en el que hicimos doble click es el encabezado del DataGridView, nos retornamos.
            If e.RowIndex = -1 Then
                Return
            End If

            '
            'Obtenemos el row en el cual se hizo doble Click
            '
            Dim row As DataGridViewRow = dataGridView1.Rows(e.RowIndex)

            'Instanciamos la clase Eproducto para cargar los datos tomándolos de las celdas del row
            '
            'Recuerde convertir al tipo de dato correcto
            '
            Dim item As New EProducto()
            item.Numero = Convert.ToInt32(row.Cells("ColumnNumero").Value)
            item.Nombre = Convert.ToString(row.Cells("ColumnNombre").Value)
            item.Precio = Convert.ToDecimal(row.Cells("ColumnPrecio").Value)
            item.Cantidad = Convert.ToDecimal(row.Cells("ColumnCantidad").Value)

            '
            'Si no existe llamador para nuestro formulario nos retornamos sin hacer ninguna acción
            '
            If Caller Is Nothing Then
                Return
            End If

            'Si el Form1 devolvió false por haber encontrado el Producto dentro de la lista
            'Informamos de lo sucedido al usuario
            If Not Caller.LoadDataRow(item) Then
                MessageBox.Show("El Producto ya existe en la lista", "Atención", MessageBoxButtons.OK, MessageBoxIcon.[Stop])
            End If
        Catch ex As Exception
            MessageBox.Show(String.Format("Error : {0}", ex.Message), "Error Inesperado", MessageBoxButtons.OK, MessageBoxIcon.Error)
        End Try

    End Sub
End Class

Hasta este momento hemos creado nuestro diseño, nuestra clase entidad, la interfaz y nuestro estructura de código interno, ¿Que nos falta?, pues únicamente probar nuestro proyecto de ejemplo:
Presione F5 para realizar las pruebas.
Presione el botón “Agregar”
Si realizo las cosas de acuerdo a las instrucciones, podrá ver el DataGridView del Form2 poblado con datos:
frm3
Haga doble Click sobre cualquier Row del datagridView para enviar los datos al Form1: 
frm4
 Ahora haga doble Click sobre un Producto ya enviado previamente:
frm5
 Bueno hemos llegado al final del articulo, si siguió el articulo completo no tendrá problemas para la implementación y adaptación de este ejemplo en sus proyecto de desarrollo.
Saludos desde Monterrey, Nuevo León México!

Ejemplo C#
Ejemplo Vb.Net
Nota: El proyecto fue desarrollado en Vs2010 Ultímate usando Framework 4.0 Client Profile

domingo, 27 de octubre de 2013

ReportViewer y Rdlc, ejemplo Factura (Base de datos)

Hola a todos:

Este articulo es la continuación de : ReportViewer y Rdlc, ejemplo Factura (datos fijos) a si que si aun no lee ese articulo deberá de hacerlo antes de continuar con este, ya que aprovecharemos el diseño que ya tenemos creado, únicamente modificaremos el código existente en el Form1 que es el encargado de enviar los datos al Rdlc y  para extender el articulo tomaremos los datos de una Base de datos que para fines ilustrativos se hará uso de una Bd SqlCompact.

Antes de continuar descargue el proyecto de ejemplo y agregue una Base de datos Sqlcompact si no sabe como agregar una Bd sqlCompact sírvase de leer este articulo:

Agregar una Base de datos SqlCompact a un proyecto de Visual Studio

Agregue tres tablas con las siguientes características:

Tabla Articulo, contendrá toda la información de los artículos:
b1

Tabla Factura, contendrá los datos del encabezado de factura, el Id de la tabla será del tipo Identity autoincrementable, este campo representara el numero consecutivo de factura:
b2

Tabla DetalleFactura, será la tabla que conservara la relación entre la tabla Factura y Artículo, la importancia de esta tabla es que nos evita duplicar información al momento de crear un factura, ya que el Encabezado de la factura se almacena en la tabla Factura, y el detalle de la factura almacenara la relación de la factura con los artículos.
b3

Una vez que tengamos las tablas agreguemos algunos datos de ejemplo, puede descargar y estos datos aquí.

Después de que creamos las tablas y les insertamos estos datos de ejemplo, vayamos a lo que nos interesa, la codificación de nuestra aplicación.

Vaya al proyecto de Vs y agregue una nueva Clase. llámela DataAccess, esta clase será la encargada de hacer la comunicación de nuestra aplicación con la Base de datos y contendrá una función leerá y retornara información de nuestras tablas, simulando ser la capa de Acceso a Datos de una arquitectura en capas.
Recuerde agregar las referencias a los ensamblados System.Data.SqlCe y System.Configuration, si agrego una base de datos ya existente.

Dentro de la clase DataAccess crearemos una función que será la encargada de hacer la solicitud de la factura a nuestra Base de datos, quedando de la siguiente manera:

Código C#:
using System;
using System.Data.SqlServerCe;
using System.Configuration;

namespace ReportViewerInvoiceReport_CSharp
{
    public class DataAccess
    {
        /// <summary>
        /// Función encargada de hacer la solictud de información de una Factura a la base de datos
        /// </summary>
        /// <Autor>José Luis García Bautista</Autor>
        /// <param name="invoicenumber">Número de factura solicitada</param>
        /// <returns>Una instancia simple de EFactura</returns>
        public static EFactura GetFactura(int invoicenumber)
        {
            //
            //Instanciamos la clase Efactura
            //
            EFactura invoice = new EFactura();
            //
            //Abrimos la cadena de conexión, leyendoe el archivo de configuración
            //Recuerde siempre encerrar en un bloque Using la cadena de conexión para asegurar liberar
            //recuersos despues de terminar la ejecución
            //
            using (SqlCeConnection cnx = new SqlCeConnection(ConfigurationManager.ConnectionStrings["connString"].ToString()))
            {
                //
                //Abrimos la cadena de conexión
                //
                cnx.Open();
                //
                //Creamos una constante, a la cual le asignaremos nuestra consulta Sql parametrizada
                //
                const string sqlAction = @"SELECT F.Numero, F.Cliente, F.Rfc, F.Direccion, F.FechaFacturacion,  
                                                D.NumeroArticulo, A.Upc, A.Descripcion, A.Precio, D.Piezas, ROUND(A.Precio * D.Piezas, 2) AS Importe
                                         FROM Factura F
                                              INNER JOIN DetalleFactura D ON F.Numero = D.NumeroFactura
                                              INNER JOIN Articulo A ON D.NumeroArticulo = A.Numero
                                         WHERE F.Numero = @numFactura";
                //
                //Creamos nuestro comando, asignandole nuestra consulta y la conexión
                //
                using (SqlCeCommand cmd = new SqlCeCommand(sqlAction, cnx))
                {
                    //
                    //Asignamos los parametros y sus valores
                    //
                    cmd.Parameters.AddWithValue("@numFactura", invoicenumber);
                    //
                    //Creamos nuestro objeto DataReader encargada de atrapar el resultado de la ejecución de la consulta
                    //
                    SqlCeDataReader dataReader = cmd.ExecuteReader();
                    //
                    //si nuestro comando devolvio informacion, entonces nuestro DataReader contendra estos datos
                    //
                    while (dataReader.Read())
                    {
                        //
                        //Vamos asignando valores a nuestras propiedades contenidas en la instancia de EFactura
                        //tomando los valores de los campos del DataReader...
                        //
                        invoice.Numero = Convert.ToInt32(dataReader["Numero"]);
                        invoice.Nombre = Convert.ToString(dataReader["Cliente"]);
                        invoice.Rfc = Convert.ToString(dataReader["Rfc"]);
                        invoice.Direccion = Convert.ToString(dataReader["Direccion"]);
                        invoice.FechaFacturacion = Convert.ToDateTime(dataReader["FechaFacturacion"]);
                        //
                        //Creamos una instancia de EArticulo, recuerde que dentro de nuestra clase EFactura tenemos
                        //lista generica de EArticulo llamado Detail, Detail como su nombre lo indica sera la encargada de
                        //almacenar la lista de articulos que conformán el detalle de la factura, asi que crearemos una instancia
                        //de esta clase para poder agregarla a la lista
                        //
                        EArticulo articulo = new EArticulo();
                        articulo.Numero = Convert.ToInt32(dataReader["NumeroArticulo"]);
                        articulo.Upc = Convert.ToString(dataReader["Upc"]);
                        articulo.Descripcion = Convert.ToString(dataReader["Descripcion"]);
                        articulo.Precio = Convert.ToDecimal(dataReader["Precio"]);
                        articulo.Piezas = Convert.ToDecimal(dataReader["Piezas"]);
                        articulo.Importe = Convert.ToDecimal(dataReader["Importe"]);
                        invoice.Detail.Add(articulo);
                    }
                }
            }
            //
            //Por ultimo devolvemos la instancia de Efactura
            //
            return invoice;
        }

    }
}

Código Vb.Net:
Imports System.Data.SqlServerCe
Imports System.Configuration

Public Class DataAccess

    ''' <summary>
    ''' Función encargada de hacer la solictud de información de una Factura a la base de datos
    ''' </summary>
    ''' <Autor>José Luis García Bautista</Autor>
    ''' <param name="invoicenumber">Número de factura solicitada</param>
    ''' <returns>Una instancia simple de EFactura</returns>
    Public Shared Function GetFactura(invoicenumber As Integer) As EFactura
        '
        'Instanciamos la clase Efactura
        '
        Dim invoice As New EFactura()
        '
        'Abrimos la cadena de conexión, leyendoe el archivo de configuración
        'Recuerde siempre encerrar en un bloque Using la cadena de conexión para asegurar liberar
        'recuersos despues de terminar la ejecución
        '
        Using cnx As New SqlCeConnection(ConfigurationManager.ConnectionStrings("connString").ToString())
            '
            'Abrimos la cadena de conexión
            '
            cnx.Open()
            '
            'Creamos una constante, a la cual le asignaremos nuestra consulta Sql parametrizada
            '
            Const sqlAction As String = "SELECT F.Numero, F.Cliente, F.Rfc, F.Direccion, F.FechaFacturacion, " _
                                             & "D.NumeroArticulo, A.Upc, A.Descripcion, A.Precio, D.Piezas, " _
                                             & "ROUND(A.Precio * D.Piezas, 2) AS Importe " _
                                      & "FROM Factura F INNER JOIN DetalleFactura D ON F.Numero = D.NumeroFactura " _
                                      & "               INNER JOIN Articulo A ON D.NumeroArticulo = A.Numero " _
                                      & "WHERE F.Numero = @numFactura"
            '
            'Creamos nuestro comando, asignandole nuestra consulta y la conexión
            '
            Using cmd As New SqlCeCommand(sqlAction, cnx)
                '
                'Asignamos los parametros y sus valores
                '
                cmd.Parameters.AddWithValue("@numFactura", invoicenumber)
                '
                'Creamos nuestro objeto DataReader encargada de atrapar el resultado de la ejecución de la consulta
                '
                Dim dataReader As SqlCeDataReader = cmd.ExecuteReader()
                '
                'si nuestro comando devolvio informacion, entonces nuestro DataReader contendra estos datos
                '
                While dataReader.Read()
                    '
                    'Vamos asignando valores a nuestras propiedades contenidas en la instancia de EFactura
                    'tomando los valores de los campos del DataReader...
                    '
                    invoice.Numero = Convert.ToInt32(dataReader("Numero"))
                    invoice.Nombre = Convert.ToString(dataReader("Cliente"))
                    invoice.Rfc = Convert.ToString(dataReader("Rfc"))
                    invoice.Direccion = Convert.ToString(dataReader("Direccion"))
                    invoice.FechaFacturacion = Convert.ToDateTime(dataReader("FechaFacturacion"))
                    '
                    'Creamos una instancia de EArticulo, recuerde que dentro de nuestra clase EFactura tenemos
                    'lista generica de EArticulo llamado Detail, Detail como su nombre lo indica sera la encargada de
                    'almacenar la lista de articulos que conformán el detalle de la factura, asi que crearemos una instancia
                    'de esta clase para poder agregarla a la lista
                    '
                    Dim articulo As New EArticulo()
                    articulo.Numero = Convert.ToInt32(dataReader("NumeroArticulo"))
                    articulo.Upc = Convert.ToString(dataReader("Upc"))
                    articulo.Descripcion = Convert.ToString(dataReader("Descripcion"))
                    articulo.Precio = Convert.ToDecimal(dataReader("Precio"))
                    articulo.Piezas = Convert.ToDecimal(dataReader("Piezas"))
                    articulo.Importe = Convert.ToDecimal(dataReader("Importe"))
                    invoice.Detail.Add(articulo)
                End While
            End Using
        End Using
        '
        'Por ultimo devolvemos la instancia de Efactura
        '
        Return invoice
    End Function

End Class

Después de haber creado la función dentro de nuestra clase de acceso a datos, vayamos a crear la codificación para nuestro Formulario encargado de enviar los datos al Rdlc, hablo de nuestro form1, esta clase será la única que sufrirá cambios con respecto al articulo anterior, por favor remplace las líneas de código por estas existentes por las siguiente:

Código C#:
using System;
using System.Linq;
using System.Windows.Forms;

namespace ReportViewerInvoiceReport_CSharp
{
    public partial class Form1 : Form
    {
        //
        //Creamos la instancia de la clase Efactura
        //
        EFactura _factura = new EFactura();

        public Form1()
        {
            InitializeComponent();
        }

        /// <summary>
        /// Función encargada de recuperar de la Base de datos la informacion de una Factura
        /// </summary>
        /// <Autor>José Luis García Bautista </Autor>
        /// <param name="invoiceNumber">Número de factura solicitada</param>
        private void FillDgv(int invoiceNumber)
        {
            try
            {
                //
                //Establecemo el valor de la instancia de la clase EFactura, el valor devuelto por la funcion GetFactura
                //de la clase DataAccess, recuerde que GetFactura devuelve un tipo EFactura.
                //
                _factura = DataAccess.GetFactura(invoiceNumber);

                //
                //Preguntamos si _factura contiene items en su detalle, si no contiene detalle, informamos
                //al usuario y nos salimos del método...
                //
                if (_factura.Detail.Count == 0)
                {
                    MessageBox.Show("La factura solicitada no existe", "Factura", MessageBoxButtons.OK,
                                    MessageBoxIcon.Information);
                    return;
                }
                //
                //Si llegamos a este punto de ejecucion, significa que nuestra factura es valida
                //
                //Asignamos valores a los controles
                //
                txtnumero.Text = Convert.ToString(_factura.Numero);
                txtnombre.Text = _factura.Nombre;
                txtrfc.Text = _factura.Rfc;
                txtdireccion.Text = _factura.Direccion;
                dtpfecha.Value = _factura.FechaFacturacion;

                //Establecemos la propiedad AutoGenerateColumns en False para evitar que se agreguen
                //nuevas columnas a la derecha de las que creamos en tiempo de diseño.
                //
                dgvdetalle.AutoGenerateColumns = false;
                //
                //Establecemos el DataSource del control DataGridView usando la instancia EFactura / Detail
                //que previamente asignamos el valor devuelto por GetFactura
                //
                dgvdetalle.DataSource = _factura.Detail;
                //
                //Mapeamos las propiedades de la clase EArticulo, contenido en Efactura 
                //
                dgvdetalle.Columns["columnNumero"].DataPropertyName = "Numero";
                dgvdetalle.Columns["columnUpc"].DataPropertyName = "Upc";
                dgvdetalle.Columns["columnDescripcion"].DataPropertyName = "Descripcion";
                dgvdetalle.Columns["columnPiezas"].DataPropertyName = "Piezas";
                dgvdetalle.Columns["columnPrecio"].DataPropertyName = "Precio";
                dgvdetalle.Columns["columnImporte"].DataPropertyName = "Importe";

                //
                //Hacemos las sumatorias usando un método de extensión de Linq
                //como tenemos un objeto Efactura que dentro contiene una coleccion de tipo Earticulo y este objeto
                //tiene dentro la propiedad Importe que es lo que nos interesa sumar. Entonces
                //hacemos la sumatoria usando este objeto
                //
                decimal sum = _factura.Detail.Sum(x => x.Importe);
                //
                //Obtenemos el Iva (para los que no sean de México, el Iva es el Impuesto al Valor Agregado)
                //
                decimal iva = (Math.Round(((sum / 116) * 16), 2));
                //
                //Obtenemos el Subtotal
                //
                decimal subtotal = Math.Round(sum - iva, 2);

                txttotal.Text = Convert.ToString(Math.Round(sum, 2));
                txtiva.Text = Convert.ToString(iva);
                txtsubtotal.Text = Convert.ToString(subtotal);
            }
            catch (Exception e)
            {
                MessageBox.Show(String.Format("Error: {0}", e.Message), "Error inesperado", MessageBoxButtons.OK,
                MessageBoxIcon.Error);
            }
        }

        private void InvoiceGenerate()
        {
            //
            //Hamos los calculos de Total, Iva y Subtotal usando el objeto EFactura
            //
            _factura.Total = _factura.Detail.Sum(x => x.Importe);
            _factura.Iva = Math.Round(((_factura.Total/116)*16), 2);
            _factura.Subtotal = Math.Round((_factura.Total - _factura.Iva), 2);
            
            //
            //Creamos una instancia del Formulario que contiene nuestro
            //ReportViewer
            //
            FacturaRpt frm = new FacturaRpt();
            //
            //Usamos las propiedades publicas del formulario, aqui es donde enviamos el valor
            //que se mostrara en los parametros creados en el LocalReport, para este ejemplo
            //estamos Seteando los valores directamente pero usted puede usar algun control
            //
            frm.Titulo = "Este es un ejemplo de Factura";
            frm.Empresa = "Este es un ejemplo del Nombre de la Empresa";
            //
            //Recuerde que invoice es una Lista Generica declarada en FacturaRtp, es una lista
            //porque el origen de datos del LocalReport unicamente permite ser enlazado a objetos que 
            //implementen IEnumerable.
            //
            //Usamos el metod Add porque Invoice es una lista e invoice es una entidad simple
            frm.Invoice.Add(_factura);
            //
            //Enviamos el detalle de la Factura, como Detail es una lista e invoide.Details tambien
            //es un lista del tipo EArticulo bastara con igualarla
            //
            frm.Detail = _factura.Detail;
            frm.Show();
        }

        private void btnImprimir_Click(object sender, EventArgs e)
        {
            InvoiceGenerate();
        }

        private void txtnumero_KeyDown(object sender, KeyEventArgs e)
        {
            if (e.KeyData == Keys.Enter & !string.IsNullOrWhiteSpace(txtnumero.Text)) FillDgv(Convert.ToInt32(txtnumero.Text));
        }
    }
}

Código Vb.Net:
Public Class Form1
    '
    'Creamos la instancia de la clase Efactura
    '
    Dim _factura As New EFactura()


    ''' <summary>
    ''' Función encargada de recuperar de la Base de datos la informacion de una Factura
    ''' </summary>
    ''' <Autor>José Luis García Bautista </Autor>
    ''' <param name="invoiceNumber">Número de factura solicitada</param>
    Private Sub FillDgv(invoiceNumber As Integer)
        Try
            '
            'Establecemo el valor de la instancia de la clase EFactura, el valor devuelto por la funcion GetFactura
            'de la clase DataAccess, recuerde que GetFactura devuelve un tipo EFactura.
            '
            _factura = DataAccess.GetFactura(invoiceNumber)

            '
            'Preguntamos si _factura contiene items en su detalle, si no contiene detalle, informamos
            'al usuario y nos salimos del método...
            '
            If _factura.Detail.Count = 0 Then
                MessageBox.Show("La factura solicitada no existe", "Factura", MessageBoxButtons.OK, MessageBoxIcon.Information)
                Return
            End If
            '
            'Si llegamos a este punto de ejecucion, significa que nuestra factura es valida
            '
            'Asignamos valores a los controles
            '
            txtnumero.Text = Convert.ToString(_factura.Numero)
            txtnombre.Text = _factura.Nombre
            txtrfc.Text = _factura.Rfc
            txtdireccion.Text = _factura.Direccion
            dtpfecha.Value = _factura.FechaFacturacion

            'Establecemos la propiedad AutoGenerateColumns en False para evitar que se agreguen
            'nuevas columnas a la derecha de las que creamos en tiempo de diseño.
            '
            dgvdetalle.AutoGenerateColumns = False
            '
            'Establecemos el DataSource del control DataGridView usando la instancia EFactura / Detail
            'que previamente asignamos el valor devuelto por GetFactura
            '
            dgvdetalle.DataSource = _factura.Detail
            '
            'Mapeamos las propiedades de la clase EArticulo, contenido en Efactura 
            '
            dgvdetalle.Columns("columnNumero").DataPropertyName = "Numero"
            dgvdetalle.Columns("columnUpc").DataPropertyName = "Upc"
            dgvdetalle.Columns("columnDescripcion").DataPropertyName = "Descripcion"
            dgvdetalle.Columns("columnPiezas").DataPropertyName = "Piezas"
            dgvdetalle.Columns("columnPrecio").DataPropertyName = "Precio"
            dgvdetalle.Columns("columnImporte").DataPropertyName = "Importe"

            '
            'Hacemos las sumatorias usando un método de extensión de Linq
            'como tenemos un objeto Efactura que dentro contiene una coleccion de tipo Earticulo y este objeto
            'tiene dentro la propiedad Importe que es lo que nos interesa sumar. Entonces
            'hacemos la sumatoria usando este objeto
            '
            Dim sum As Decimal = _factura.Detail.Sum(Function(x) x.Importe)
            '
            'Obtenemos el Iva (para los que no sean de México, el Iva es el Impuesto al Valor Agregado)
            '
            Dim iva As Decimal = (Math.Round(((sum / 116) * 16), 2))
            '
            'Obtenemos el Subtotal
            '
            Dim subtotal As Decimal = Math.Round(sum - iva, 2)

            txttotal.Text = Convert.ToString(Math.Round(sum, 2))
            txtiva.Text = Convert.ToString(iva)
            txtsubtotal.Text = Convert.ToString(subtotal)
        Catch e As Exception
            MessageBox.Show([String].Format("Error: {0}", e.Message), "Error inesperado", MessageBoxButtons.OK, MessageBoxIcon.[Error])
        End Try
    End Sub

    Private Sub InvoiceGenerate()
        '
        'Hamos los calculos de Total, Iva y Subtotal usando el objeto EFactura
        '
        _factura.Total = _factura.Detail.Sum(Function(x) x.Importe)
        _factura.Iva = Math.Round(((_factura.Total / 116) * 16), 2)
        _factura.Subtotal = Math.Round((_factura.Total - _factura.Iva), 2)

        '
        'Creamos una instancia del Formulario que contiene nuestro
        'ReportViewer
        '
        Dim frm As New FacturaRpt()
        '
        'Usamos las propiedades publicas del formulario, aqui es donde enviamos el valor
        'que se mostrara en los parametros creados en el LocalReport, para este ejemplo
        'estamos Seteando los valores directamente pero usted puede usar algun control
        '
        frm.Titulo = "Este es un ejemplo de Factura"
        frm.Empresa = "Este es un ejemplo del Nombre de la Empresa"
        '
        'Recuerde que invoice es una Lista Generica declarada en FacturaRtp, es una lista
        'porque el origen de datos del LocalReport unicamente permite ser enlazado a objetos que 
        'implementen IEnumerable.
        '
        'Usamos el metod Add porque Invoice es una lista e invoice es una entidad simple
        frm.Invoice.Add(_factura)
        '
        'Enviamos el detalle de la Factura, como Detail es una lista e invoide.Details tambien
        'es un lista del tipo EArticulo bastara con igualarla
        '
        frm.Detail = _factura.Detail
        frm.Show()
    End Sub

    Private Sub btnImprimir_Click(sender As System.Object, e As System.EventArgs) Handles btnImprimir.Click
        InvoiceGenerate()
    End Sub

    Private Sub txtnumero_KeyDown(sender As System.Object, e As System.Windows.Forms.KeyEventArgs) Handles txtnumero.KeyDown
        If e.KeyData = Keys.Enter And Not String.IsNullOrWhiteSpace(txtnumero.Text) Then
            FillDgv(Convert.ToInt32(txtnumero.Text))
        End If
    End Sub
End Class

Por ultimo nos queda probar nuestro reporte, genere el proyecto, ejecútelo, ponga un numero de factura de 1 al 5 (recuerde que solo insertamos 5 facturas de ejemplo), con el cursor en el TextBox número presione “Enter”, si hicimos las cosas con forme el articulo tendremos un formulario cargado con datos como este:

2

Ahora, presione el botón imprimir para ver nuestro reporte:
3

Listo, hemos terminado con nuestro reporte, recuerde que no siempre las cosas salen a la primera así que si algún punto no le quedo claro pues a leer de nuevo, si tiene algunas dudas siéntase con toda la confianza de hacer las preguntas pertinentes.

Por ultimo

En este articulo de ejemplo usamos una Base de datos SqlCe por la facilidad que brinda para agregarlo al proyecto, así que usamos todos los objetos de Ado.Net para manejo de Bases de datos propios para este motor, si usted cuenta con otra base de datos, recuerde que tiene que cambiar estos objetos, para ser mas claros hablo de cambiar:

SqlCeConnection por :
  • Access: OleDbConnection
  • SqlServer: SqlConnection
  • MySql : MySqlConnection
  • Sqllite : SQlLiteConnection
SqlCeCommand por:
  • Access: OleDbCommand
  • SqlServer: SqlCommand
  • MySql : MySqlCommand
  • Sqllite : SQlLiteCommand
y SqlCeDataReader  por:
  • Access: OleDataReader
  • SqlServer: SqlDataReader
  • MySql : MySqlDataReader
  • Sqllite : SQlLiteDataReader
Dicho todo lo anterior, nos leemos a la próxima

Saludos desde Monterrey, Nuevo León México!
Ejemplo C#
Ejemplo Vb.Net

Nota: El proyecto fue desarrollado en Vs2010 Ultimate usando una Bd SqlCe v3.5 y Framework 4.0