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:

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:

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.

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:

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

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
- Access: OleDbCommand
- SqlServer: SqlCommand
- MySql : MySqlCommand
- Sqllite : SQlLiteCommand
- Access: OleDataReader
- SqlServer: SqlDataReader
- MySql : MySqlDataReader
- Sqllite : SQlLiteDataReader
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