Mostrando las entradas con la etiqueta OLEDB. Mostrar todas las entradas
Mostrando las entradas con la etiqueta OLEDB. Mostrar todas las entradas

lunes, 2 de abril de 2018

Entrenamiento - Segundo Seminario de Desarrollo Web ASP.NET MVC & JavaScript

Segundo Seminario de Desarrollo Web ASP.NET MVC & JavaScript

Continuando con los Seminarios de una vez al mes el tema a tratar será como manejar archivos de Office 2007 (xlsx, docx, pptx) desde aplicaciones .NET, tanto Windows como Web.
Este tema es muy importante ya que la mayoría de programadores diariamente trabajan con Excel y algunos con Word, sobre todo archivos xlsx y docx.
En este seminario se verán las diferentes técnicas para poder crear y leer estos archivos, ya que sino se conocen las técnicas adecuadas, se tendrán problemas de velocidad (performance) tanto para leer como para escribir.

Seminario 2: Manejando Archivos de Office desde .NET (C#)

1. Creación de un Archivo de Excel desde Windows
  1.1. Crear con Automatización COM o ActiveX (Interop)
  1.2. Crear con Archivos de Texto y OLEDB
  1.3. Crear con Archivos XML si es un Archivo xlsx

2. Lectura de un Archivo de Excel desde Windows
  2.1. Leer con OLEDB en ADO.NET
  2.2. Leer Archivos XML si es un Archivo xlsx

3. Creación de un Archivo de Word desde Windows
  3.1. Crear con Automatización COM o ActiveX (Interop)
  3.2. Crear con Archivos XML si es un Archivo docx

4. Lectura de un Archivo de Word desde Windows
  4.1. Leer Archivos XML si es un Archivo docx
  4.2. Leer un Documento Plantilla y Crear un Nuevo Documento

5. Preview de Archivos de Office desde Aplicaciones Web
  5.1. Preview de una Hoja de Excel
  5.2. Preview de un Documento de Word
  5.3. Preview de Diapositivas de Power Point

6. Creación de Archivos de Office desde Aplicaciones Web
  6.1. Crear y Descargar un Archivo de Excel
  6.2. Crear y Descargar un Archivo de Word
  6.3. Crear y Descargar Varios Archivos de Excel o Word

Día y Hora:
- Día: Sábado 7 de Abril del 2018
- Horario: 4:00 pm a 10:00 pm.
- Duración: 6 horas

Inversión:
- 100 Soles para Alumnos Regulares de Talleres
- 200 Soles para Público en General

Participantes:
- 15 Mínimo
- 25 Máximo

Lugar:
- Mi Domicilio (Zona Norte de Lima)
Nota: A los que no conocen el lugar solo si se registran les enviaré la dirección exacta.

Contacto:
Los interesados enviar un mail a Luis.duenash@gmail.com con los siguientes datos:
Asunto: Segundo Seminario de Desarrollo Web ASP.NET MVC & JavaScript
Contenido del Mail:
- Nombre Completo
- Lugar de Trabajo o Estudio
- Alumno Regular de Talleres: Si / No

lunes, 1 de agosto de 2016

El Demo del Día: Exportar a Excel 2007 desde Listas y Tablas usando OLEDB

Exportar a Excel 2007 desde Listas y Tablas usando OLEDB

1. Requerimiento

- Se desea exportar a Excel 2007 varias hojas desde tablas en un DataSet.
- Se desea exportar a Excel 2007 varias hojas desde listas de objetos de un objeto contenedor.

2. Técnicas para Crear un Archivo de Excel

Existen muchas técnicas pero vamos a describir brevemente las 3 principales:

2.1 Automatización ActiveX o COM

Consiste en abrir una instancia de Excel desde otra aplicación (en este caso en .NET) y llenar las celdas de cada hoja una por una, luego grabar el archivo y cerrar la instancia de Excel abierta.

Hay 2 inconvenientes: el primero es que tenemos que tener instalado Excel donde se va a ejecutar la aplicación y si es web sería en el servidor, el segundo es que si es una aplicación web hay muchos problemas desde dar permisos al COM de Excel para que se ejecute hasta cerrar la instancia que siempre se queda abierta.

Esta técnica es la peor, pero quizás la más popular (por ser la más fácil), si es una aplicación Windows no hay muchos problemas por que cada aplicación corre en diferentes máquinas, pero si es web, les aconsejaría que cambien de técnica (jamás se debería usar en ASP.NET).

2.2. Usando OLEDB

Esta técnica consiste en crear un archivo de texto separados por comas o algún caracter delimitador y luego usando OLEDB ejecutar un comando que convierta el archivo de texto creado previamente en un archivo de Excel 2007.

También tenemos 2 grandes inconvenientes: el primero es que hay que tener instalado el "Microsoft Access Database Engine 2010 Redistributable", el segundo es que se presentarán problemas de codificación (Encoding), formatos y estilos, por ejemplo códigos que son cadenas con ceros a la izquierda que se convierten en numéricos (sin ceros a la izquierda), ajuste de decimales, formato de fechas y horas, campos memos, colores de fondo, colores de texto, tamaños, etc.

En este post veremos esta técnica sin considerar los problemas que se pueden presentar por el tipo de datos a exportar, el cual lo veremos en otro post que será de mucha ayuda para cientos de programadores que pasan por estos problemas.

2.3. Usado Archivos

Esta técnica es la más optima y natural, consiste en crear archivos XML con los formatos establecidos por Open XML y luego comprimirlos en un archivo de Excel 2007 (xlsx).

Si el programador conoce la especificación y formato de cada archivo xml necesario y sabe manejo de archivos para crearlos y comprimirlos, entonces lo más recomendable es que lo haga el mismo, sino, puede usar una librería de terceros que lo haga por él, tales como: SpreedSheet (DevExpress), NPOI, EPPlus, etc.

En otros post enseñaré como crear archivos de Excel simples (datos sin formato) y complejos (con imágenes, formatos, etc).

3. Crear una Aplicación Windows Forms en C#

Abrir Visual Studio y crear una aplicación Windows Forms en C# llamada "Excel_Crear_OLEDB", cambiarle de nombre al formulario por "frmExcel" y realizar el diseño similar a la figura mostrada:

Diseño del formulario "frmExcel"

En el diseño hay los siguientes controles:
- Botón para Llenar desde Listas llamado "btnLlenarLista".
- Botón para Llenar desde Tablas llamado "btnLlenarTabla".
- Botón para Exportar Listas a Excel llamado "btnExportarListas". Deshabilitado por defecto.
- Botón para Exportar Tablas a Excel llamado "btnExportarTablas". Deshabilitado por defecto.
- Control Tab llamado "tabExcel" con 2 tabPages: "tabCategorias" y "tabProducto".
- Control DataGridView llamado "dgvCategoria" ubicado en el tab de Categorias.
- Control DataGridView llamado "dgvProducto" ubicado en el tab de Productos.

4. Crear las Clases con las Entidades de Negocio

Lo primero que debemos crear son las clases de entidades para las listas de objetos de categorías y productos:

Archivo de Clase: beCategoria.cs

namespace Excel_Crear_OLEDB
{
    public class beCategoria
    {
        public int IdCategoria { get; set; }
        public string Nombre { get; set; }
    }
}

Archivo de Clase: beProducto.cs

namespace Excel_Crear_OLEDB
{
    public class beProducto
    {
        public int IdProducto { get; set; }
        public string Nombre { get; set; }
        public int IdCategoria { get; set; }
        public decimal Precio { get; set; }
        public short Stock { get; set; }
    }
}

Archivo de Clase: beCategoriaProducto.cs

using System.Collections.Generic;

namespace Excel_Crear_OLEDB
{
    public class beCategoriaProducto
    {
        public List<beCategoria> ListaCategoria { get; set; }
        public List<beProducto> ListaProducto { get; set; }
    }
}

5. Crear una Clase que permita llenar los Datos

Lo segundo que debemos hacer es crear una clase para llenar los datos de las tablas y listas de objetos tanto para categorías como productos.

En una aplicación real esto se reemplazará por la llamada a un componente de reglas del negocio y solo se elegirá uno de las 2 repositorios de datos: DataSet o Objeto con Listas. Lo recomendable es elegir el Objeto con Listas ya que consume menos memoria y procesa más rápido.

Archivo de Clase: Data.cs

using System;
using System.Collections.Generic;
using System.Data;

namespace Excel_Crear_OLEDB
{
    public class Data
    {
        public static List<beCategoria> ListaCategorias()
        {
            //Llenar la Lista de Categorias
            List<beCategoria> lbeCategoria = new List<beCategoria>();
            lbeCategoria.Add(new beCategoria
            {
                IdCategoria = 1,
                Nombre = "Bebidas"
            });
            lbeCategoria.Add(new beCategoria
            {
                IdCategoria = 2,
                Nombre = "Cereales"
            });
            lbeCategoria.Add(new beCategoria
            {
                IdCategoria = 3,
                Nombre = "Condimentos"
            });
            lbeCategoria.Add(new beCategoria
            {
                IdCategoria = 4,
                Nombre = "Lacteos"
            });
            lbeCategoria.Add(new beCategoria
            {
                IdCategoria = 5,
                Nombre = "Pastas"
            });
            return lbeCategoria;
        }

        public static List<beProducto> ListaProductos()
        {
            //Llenar la Lista de Productos
            List<beProducto> lbeProducto = new List<beProducto>();
            lbeProducto.Add(new beProducto
            {
                IdProducto = 1,
                Nombre = "Pepsi",
                IdCategoria = 1,
                Precio = 1.5M,
                Stock = 100
            });
            lbeProducto.Add(new beProducto
            {
                IdProducto = 2,
                Nombre = "Avena 3 Ositos",
                IdCategoria = 2,
                Precio = 2.0M,
                Stock = 50
            });
            lbeProducto.Add(new beProducto
            {
                IdProducto = 3,
                Nombre = "Ajinomoto",
                IdCategoria = 3,
                Precio = 0.5M,
                Stock = 200
            });
            lbeProducto.Add(new beProducto
            {
                IdProducto = 4,
                Nombre = "Leche Gloria",
                IdCategoria = 4,
                Precio = 3.5M,
                Stock = 300
            });
            lbeProducto.Add(new beProducto
            {
                IdProducto = 5,
                Nombre = "Fideos Lavaggi",
                IdCategoria = 5,
                Precio = 3.0M,
                Stock = 150
            });
            lbeProducto.Add(new beProducto
            {
                IdProducto = 6,
                Nombre = "Sprite",
                IdCategoria = 1,
                Precio = 2.0M,
                Stock = 200
            });
            lbeProducto.Add(new beProducto
            {
                IdProducto = 7,
                Nombre = "Quinua Avena",
                IdCategoria = 2,
                Precio = 2.5M,
                Stock = 150
            });
            lbeProducto.Add(new beProducto
            {
                IdProducto = 8,
                Nombre = "Tuco Sibarita",
                IdCategoria = 3,
                Precio = 0.5M,
                Stock = 200
            });
            lbeProducto.Add(new beProducto
            {
                IdProducto = 9,
                Nombre = "Leche Pura Vida",
                IdCategoria = 4,
                Precio = 3.5M,
                Stock = 300
            });
            lbeProducto.Add(new beProducto
            {
                IdProducto = 10,
                Nombre = "Spagetti Don Victorio",
                IdCategoria = 5,
                Precio = 2.0M,
                Stock = 250
            });
            return lbeProducto;
        }

        public static DataTable TablaCategorias()
        {
            //Llenar la Tabla de Categorias
            DataTable tblCategoria = new DataTable();
            tblCategoria.Columns.Add("IdCategoria",
                Type.GetType("System.Int32"));
            tblCategoria.Columns.Add("Nombre",
                Type.GetType("System.String"));
            DataRow drCat1 = tblCategoria.NewRow();
            drCat1[0] = 1;
            drCat1[1] = "Bebidas";
            tblCategoria.Rows.Add(drCat1);
            DataRow drCat2 = tblCategoria.NewRow();
            drCat2[0] = 2;
            drCat2[1] = "Cereales";
            tblCategoria.Rows.Add(drCat2);
            DataRow drCat3 = tblCategoria.NewRow();
            drCat3[0] = 3;
            drCat3[1] = "Condimentos";
            tblCategoria.Rows.Add(drCat3);
            DataRow drCat4 = tblCategoria.NewRow();
            drCat4[0] = 4;
            drCat4[1] = "Lacteos";
            tblCategoria.Rows.Add(drCat4);
            DataRow drCat5 = tblCategoria.NewRow();
            drCat5[0] = 5;
            drCat5[1] = "Pastas";
            tblCategoria.Rows.Add(drCat5);
            return tblCategoria;
        }

        public static DataTable TablaProductos()
        {
            //Llenar la Tabla de Productos
            DataTable tblProducto = new DataTable();
            tblProducto.Columns.Add("IdProducto",
                Type.GetType("System.Int32"));
            tblProducto.Columns.Add("Nombre",
                Type.GetType("System.String"));
            tblProducto.Columns.Add("IdCategoria",
                Type.GetType("System.Int32"));
            tblProducto.Columns.Add("Precio",
                Type.GetType("System.Decimal"));
            tblProducto.Columns.Add("Stock",
                Type.GetType("System.Int16"));
            DataRow drPro1 = tblProducto.NewRow();
            drPro1[0] = 1;
            drPro1[1] = "Pepsi";
            drPro1[2] = 1;
            drPro1[3] = 1.5M;
            drPro1[4] = 100;
            tblProducto.Rows.Add(drPro1);
            DataRow drPro2 = tblProducto.NewRow();
            drPro2[0] = 2;
            drPro2[1] = "Avena 3 Ositos";
            drPro2[2] = 2;
            drPro2[3] = 2.0M;
            drPro2[4] = 50;
            tblProducto.Rows.Add(drPro2);
            DataRow drPro3 = tblProducto.NewRow();
            drPro3[0] = 3;
            drPro3[1] = "Ajinomoto";
            drPro3[2] = 3;
            drPro3[3] = 0.5M;
            drPro3[4] = 200;
            tblProducto.Rows.Add(drPro3);
            DataRow drPro4 = tblProducto.NewRow();
            drPro4[0] = 4;
            drPro4[1] = "Leche Gloria";
            drPro4[2] = 4;
            drPro4[3] = 3.5M;
            drPro4[4] = 300;
            tblProducto.Rows.Add(drPro4);
            DataRow drPro5 = tblProducto.NewRow();
            drPro5[0] = 5;
            drPro5[1] = "Fideos Lavaggi";
            drPro5[2] = 5;
            drPro5[3] = 3.0M;
            drPro5[4] = 150;
            tblProducto.Rows.Add(drPro5);
            DataRow drPro6 = tblProducto.NewRow();
            drPro6[0] = 6;
            drPro6[1] = "Sprite";
            drPro6[2] = 1;
            drPro6[3] = 2.0M;
            drPro6[4] = 200;
            tblProducto.Rows.Add(drPro6);
            DataRow drPro7 = tblProducto.NewRow();
            drPro7[0] = 7;
            drPro7[1] = "Quinua Avena";
            drPro7[2] = 2;
            drPro7[3] = 2.5M;
            drPro7[4] = 150;
            tblProducto.Rows.Add(drPro7);
            DataRow drPro8 = tblProducto.NewRow();
            drPro8[0] = 8;
            drPro8[1] = "Tuco Sibarita";
            drPro8[2] = 3;
            drPro8[3] = 0.5M;
            drPro8[4] = 200;
            tblProducto.Rows.Add(drPro8);
            DataRow drPro9 = tblProducto.NewRow();
            drPro9[0] = 9;
            drPro9[1] = "Leche Pura Vida";
            drPro9[2] = 4;
            drPro9[3] = 3.5M;
            drPro9[4] = 200;
            tblProducto.Rows.Add(drPro9);
            DataRow drPro10 = tblProducto.NewRow();
            drPro10[0] = 10;
            drPro10[1] = "Spagetti Don Victorio";
            drPro10[2] = 5;
            drPro10[3] = 2.0M;
            drPro10[4] = 250;
            tblProducto.Rows.Add(drPro10);
            return tblProducto;
        }
    }
}

6. Crear una Clase que permita Crear Archivos de Texto

La tercera parte a codificar es crear una clase que permita crear archivos de texto desde Listas de Objetos o desde Tablas para lo cual usamos IO (FileStream y StreamWriter) y Reflection (PropertyInfo). Adicionalmente para las listas se usa los tipos Genéricos (<T>).

Archivo de Clase: Texto.cs

using System;
using System.Collections.Generic;
using System.IO;
using System.Text;
using System.Data;
using System.Reflection;

namespace Excel_Crear_OLEDB
{
    public class Texto
    {
        public static void ExportarDeTabla(DataTable tabla, string archivo, char separador)
        {
            using (FileStream fs = new FileStream(archivo, FileMode.Create, 
                       FileAccess.Write, FileShare.Write))
            {
                using (StreamWriter sw = new StreamWriter(fs, Encoding.Default))
                {
                    for (int i = 0; i < tabla.Columns.Count; i++)
                    {
                        sw.Write(tabla.Columns[i].ColumnName);
                        if (i < tabla.Columns.Count - 1) sw.Write(separador);
                    }
                    sw.WriteLine();
                    for (int j = 0; j < tabla.Rows.Count; j++)
                    {
                        for (int i = 0; i < tabla.Columns.Count; i++)
                        {
                            sw.Write(tabla.Rows[j][i].ToString());
                            if (i < tabla.Columns.Count - 1) sw.Write(separador);
                        }
                        sw.WriteLine();
                    }
                }
            }
        }

        public static void ExportarDeLista<T>(List<T> lista, string archivo, char separador)
        {
            using (FileStream fs = new FileStream(archivo, FileMode.Create, 
                       FileAccess.Write, FileShare.Write))
            {
                using (StreamWriter sw = new StreamWriter(fs, Encoding.Default))
                {
                    PropertyInfo[] propiedades = lista[0].GetType().GetProperties();
                    for (int i = 0; i < propiedades.Length; i++)
                    {
                        sw.Write(propiedades[i].Name);
                        if (i < propiedades.Length - 1) sw.Write(separador);
                    }
                    sw.WriteLine();
                    for (int j = 0; j < lista.Count; j++)
                    {
                        propiedades = lista[j].GetType().GetProperties();
                        for (int i = 0; i < propiedades.Length; i++)
                        {
                            sw.Write(propiedades[i].GetValue(lista[j], null).ToString());
                            if (i < propiedades.Length - 1) sw.Write(separador);
                        }
                        sw.WriteLine();
                    }
                }
            }
        }
    }
}

6. Crear una Clase que permita Crear Archivos de Excel 2007

La cuarta parte a codificar y quizás la mas importante, es crear una clase que use OLEDB para crear archivos de Excel 2007 a partir del archivo de texto previamente creado.

Archivo de Clase: Excel.cs

using System;
using System.Collections.Generic;
using System.IO;
using System.Text;
using System.Data;
using System.Data.OleDb;
using System.Reflection;

namespace Excel_Crear_OLEDB
{
    public class Excel
    {
        public static void ExportarDeLista<T>(List<T> lista, string archivoXlsx, string hoja)
        {          
            string ruta = Path.GetDirectoryName(archivoXlsx);
            string archivoTxt = String.Format("{0}\\{1}.txt", ruta, hoja);
            Texto.ExportarDeLista<T>(lista, archivoTxt, ',');
            crearExcel2007(archivoXlsx, hoja, true);
            File.Delete(archivoTxt);
        }

        public static void ExportarDeTabla(DataTable tabla, string archivoXlsx, string hoja)
        {
            string ruta = Path.GetDirectoryName(archivoXlsx);
            string archivoTxt = String.Format("{0}\\{1}.txt", ruta, hoja);
            Texto.ExportarDeTabla(tabla, archivoTxt, ',');
            crearExcel2007(archivoXlsx, hoja, true);
            File.Delete(archivoTxt);
        }

        static void crearExcel2007(string archivoXlsx, string hoja, bool eliminar)
        {
            string ruta = Path.GetDirectoryName(archivoXlsx);
            using (OleDbConnection con = new OleDbConnection
                      ("provider=Microsoft.Ace.oledb.12.0; data source=" + ruta + ";
                       extended properties=Text;"))
            {
                con.Open();
                OleDbCommand cmd = new OleDbCommand("Select * Into " + hoja +
                " In ''[Excel 12.0 xml;Database=" + archivoXlsx + "]From " + hoja + "#TXT", con);
                cmd.ExecuteNonQuery();
            }
        }

        static void crearExcel2003(string archivoXls, string hoja, bool eliminar)
        {
            string ruta = Path.GetDirectoryName(archivoXls);
            using (OleDbConnection con = new OleDbConnection
                      ("provider=Microsoft.Jet.oledb.4.0;data source=" + ruta + ";
                        extended properties=Text;"))
            {
                con.Open();
                OleDbCommand cmd = new OleDbCommand("Select * Into " + hoja +
                " In ''[Excel 8.0;Database=" + archivoXls + "]From " + hoja + "#TXT", con);
                cmd.ExecuteNonQuery();
            }
        }
    }
}

Nota: Solo se está usando la función crearExcel2007 que genera archivos xlsx, pero he incluido adicionalmente la función crearExcel2003 que genera archivos xls (por si alguno desea crear el formato antiguo).

7. Programar el formulario para que use las Clases creadas y Exporte

El último bloque a codificar es el formulario "frmExcel" en el cual debe escribir el siguiente código:

using System;
using System.Collections.Generic;
using System.Data;
using System.Drawing;
using System.IO;
using System.Diagnostics;
using System.Windows.Forms;

namespace Excel_Crear_OLEDB
{
    public partial class frmExcel : Form
    {
        private beCategoriaProducto obeCatPro;
        private DataSet dstCatPro;
        private string archivoXlsx;

        public frmExcel()
        {
            InitializeComponent();
        }

        private void llenarDesdeListas(object sender, EventArgs e)
        {
            //Simular que se llenan las listas desde la base de datos
            obeCatPro = new beCategoriaProducto();
            obeCatPro.ListaCategoria = Data.ListaCategorias();          
            obeCatPro.ListaProducto = Data.ListaProductos();
            //Enlazar las listas del objeto principal a las grillas
            dgvCategoria.DataSource = obeCatPro.ListaCategoria;
            dgvProducto.DataSource = obeCatPro.ListaProducto;
            //Habilitar botón de Exportar Listas a Excel y deshabilitar el otro
            btnExportarListas.Enabled = true;
            btnExportarTablas.Enabled = false;
        }

        private void llenarDesdeTablas(object sender, EventArgs e)
        {
            //Simular que se llenan las tablas desde la base de datos
            dstCatPro = new DataSet();
            dstCatPro.Tables.Add(Data.TablaCategorias());
            dstCatPro.Tables.Add(Data.TablaProductos());
            //Enlazar las tablas del DataSet a las grillas
            dgvCategoria.DataSource = dstCatPro.Tables[0];
            dgvProducto.DataSource = dstCatPro.Tables[1];
            //Habilitar botón de Exportar Tablas a Excel y deshabilitar el otro
            btnExportarListas.Enabled = false;
            btnExportarTablas.Enabled = true;
        }

        private void guardarExcel(Action exportar)
        {
            SaveFileDialog sfd = new SaveFileDialog();
            sfd.Title = "Selecciona el Directorio e ingresa Nombre del Archivo Excel";
            sfd.Filter = "Archivos de Excel 2007|*.xlsx";
            if (sfd.ShowDialog().Equals(DialogResult.OK))
            {
                archivoXlsx = sfd.FileName;
                Stopwatch oCronometro = new Stopwatch();
                oCronometro.Start();
                exportar();
                oCronometro.Stop();
                MessageBox.Show(String.Format("Archivo Excel fué creado en {0:n0} msg",
                oCronometro.Elapsed.TotalMilliseconds));
            }
        }

        private void exportarDesdeListas()
        {
            if (File.Exists(archivoXlsx)) File.Delete(archivoXlsx);
            Excel.ExportarDeLista(obeCatPro.ListaCategoria, archivoXlsx, "Categorias");
            Excel.ExportarDeLista(obeCatPro.ListaProducto, archivoXlsx, "Productos");          
        }

        private void exportarDesdeTablas()
        {
            if (File.Exists(archivoXlsx)) File.Delete(archivoXlsx);
            Excel.ExportarDeTabla(dstCatPro.Tables[0], archivoXlsx, "Categorias");
            Excel.ExportarDeTabla(dstCatPro.Tables[1], archivoXlsx, "Productos");
        }

        private void exportarListasExcel(object sender, EventArgs e)
        {
            guardarExcel(exportarDesdeListas);
        }

        private void exportarTablasExcel(object sender, EventArgs e)
        {
            guardarExcel(exportarDesdeTablas);
        }
    }
}

Nota: Se usa la clase Stopwatch para medir el tiempo que demora en procesar cada técnica, el cual será similar ya que la demora en ambas es la primera vez que se establece conexión con Oledb.

8. Ejecutar y Probar la Aplicación Windows Forms

Grabar la aplicación y pulsar F5 para ejecutarla, se mostrará una ventana similar a la figura:

Ventana de ejecución del formulario "frmExcel"

Luego clic al primer botón "Llenar desde Listas" y se mostrará en las grillas los datos desde las listas de categorías y productos, tal como se muestra en las siguientes figuras:

Ventana con la Ficha de Categorías para Listas

Ventana con la Ficha de Productos para Listas

Se habilitará el botón de "Exportar Listas a Excel" al cual si se da click se mostrará el siguiente diálogo de guardar:

Diálogo de Guardar Excel para Listas

Escribir el nombre del archivo de Excel 2007, por ejemplo "Listas.xlsx" y "Guardar" e inmediatamente se mostrará un cuadro de diálogo con el tiempo que demoró la operación.

Nota: Como ya lo mencioné antes, la primera vez que se realiza una conexión con un origen de datos demora mas del doble, el resto de veces es más rápido, para cualquiera de los 2 orígenes de datos.

Continuando con las pruebas click al botón "Llenar desde Tablas" y se mostrará en las grillas los datos desde las tablas de categorías y productos, similar a las presentadas en las figuras de arriba.

Ventana con la Ficha de Categorías para Tablas

Se habilitará el botón de "Exportar Tablas a Excel" al cual si se da click se mostrará el siguiente diálogo de guardar:

Diálogo de Guardar Excel para Tablas

Escribir el nombre del archivo de Excel 2007, por ejemplo "Tablas.xlsx" y "Guardar" e inmediatamente se mostrará un cuadro de diálogo con el tiempo que demoró la operación.

Finalmente, pueden abrir los 2 archivos de Excel creados que contendrán ambos 2 hojas: Categorías y Productos con los datos de los orígenes.

9. Comentario Final

En este post hemos explicado detalladamente una de las técnicas que hay para crear un archivo XLSX de Excel 2007 (también hemos incluido la función para crear archivos XLS de Excel 2003) usando archivos de texto y OLEDB.

Como ya lo mencioné para datos simples como textos y números no hay muchos inconvenientes pero si queremos formatos en números, fechas y hora, usar campos memo, etc. se presentarán complicaciones que se resuelven usando un archivo de configuración (Schema.ini) el cual será motivo de otro post.

Pero otra desventaja de esta técnica es que si queremos incluir gráficos, imágenes u otros objetos, no es posible usando OLEDB, es solo para datos simples (No objetos), para lo cual mejor usamos la técnica de archivos XML comprimidos en un XLSX.

Finalmente, creo que este post les será de mucha utilidad a muchas personas que diariamente exportan a Excel listas de registros desde DataTables algunos y otros desde Listas de Objetos usando una instancia de Excel, lo cual no debe hacerse y aunque sea hay que reemplazarla por esta técnica.

10. Descarga la Aplicación Windows

martes, 19 de julio de 2016

El Demo del Día: Leer Archivos de Excel 2007 usando OLEDB

Leer Archivos de Excel 2007 usando OLEDB

Vamos a retomar nuevamente los Demos, con algo muy solicitado que es leer Archivos de Excel 2007 (XSLX), esta forma de lectura la usaba hace muchos años, pero ahora la comparto para los que no han llevado un curso conmigo.

1. Técnicas para Leer Archivos de Excel

Para leer un archivo de Excel con todas sus hojas hay varias formas que describo brevemente a continuación:

- Automatización ActiveX o COM

Consiste en abrir una instancia de Excel desde otra aplicación (en este caso en .NET) y obtener el valor de cada celda o de un rango de celda por cada hoja. Esta forma es la mas usada y es la de peor rendimiento y requiere tener instalado el Excel. Esta forma es la mas antigua cuando todavía no existía ADO.

- Usando OLEDB

Consiste en usar el proveedor de OLEDB en ADO.NET para conectarse a un archivo de Excel 12.0 (XSLX) para lo cual es necesario tener instalado "Microsoft Access Database Engine 2010 Redistributable" el cual puede descargarse de la siguiente dirección:
https://www.microsoft.com/en-us/download/details.aspx?id=13255
En esta página habrán 2 ejecutables:
- AccessDatabaseEngine.exe (25.3 MB) para equipos con S.O. de 32 bits
- AccessDatabaseEngine_X64.exe (27.3 MB) para equipos con S.O. de 64 bits
Esta forma es la que veremos en este Demo.

- Usando Archivos

Es la forma mas óptima y consiste en descomprimir el archivo XLSX y leer los XML donde se encuentran definidos los datos y las hojas. Esto se puede hacer de forma nativa o usando Librerías de Terceros como SpreedSheet (DevExpress), NPOI, EPPlus, ExcelDataReader, etc.
En el siguiente Demo que subiré trataré como leer Excel en forma nativa usando archivos XML.

2. Crear una Aplicación Windows Forms en C#

Abrir Visual Studio y crear una aplicación Windows Forms en C# llamada "Excel_Leer_OLEDB", cambiarle de nombre al formulario por "frmVisorExcel" y realizar el diseño similar a la figura mostrada:

En el diseño hay una etiqueta (lblArchivo), un cuadro de texto (txtArchivo), 2 botones (btnAbrir y btnNuevo) y un control Tab (tabExcel) sin ninguna página ya que estas se crean en tiempo de ejecución en función al número de hojas.

3. Escribir el Código de la Aplicación en C#

Abrir el formulario "frmVisorExcel" y escribir el siguiente código:

using System;
using System.Data;
using System.Data.OleDb;
using System.Windows.Forms;

namespace Excel_Leer_OLEDB
{
    public partial class frmVisorExcel : Form
    {
        public frmVisorExcel()
        {
            InitializeComponent();
        }

        private void abrirExcel(object sender, EventArgs e)
        {
            OpenFileDialog ofd = new OpenFileDialog();
            ofd.Title = "Selecciona el archivo de Excel a abrir";
            ofd.Filter = "Archivo de Excel 2007|*.xlsx";
            ofd.InitialDirectory = @"C:\Data\NET\Practicas\Archivos\Excel";
            if (ofd.ShowDialog().Equals(DialogResult.OK))
            {
                string archivo = ofd.FileName;
                txtArchivo.Text = archivo;
                tabExcel.Controls.Clear();
                using (OleDbConnection con = new OleDbConnection
                ("provider=Microsoft.Ace.oledb.12.0;data source=" + archivo + ";
                 extended properties=Excel 12.0"))
                {
                    try
                    {
                        con.Open();
                        DataTable tblHojas = con.GetOleDbSchemaTable(OleDbSchemaGuid.Tables,
                        new object[] { null, null, null, "TABLE" });
                        foreach (DataRow fila in tblHojas.Rows)
                        {
                            if (fila["TABLE_NAME"].ToString().EndsWith("$"))
                            {
                                string nombreHoja = fila["TABLE_NAME"].ToString();
                                DataTable tblHoja = new DataTable();
                                using (OleDbDataAdapter dap = new OleDbDataAdapter
                                ("Select * From [" + nombreHoja + "]", con))
                                {
                                    dap.Fill(tblHoja);
                                }
                                DataGridView dgv = new DataGridView();
                                dgv.Dock = DockStyle.Fill;
                                dgv.ReadOnly = true;
                                dgv.SelectionMode = DataGridViewSelectionMode.FullRowSelect;
                                dgv.AllowUserToAddRows = false;
                                dgv.DataSource = tblHoja;
                                TabPage pagina = new TabPage();
                                pagina.Text = nombreHoja.Replace("$","");
                                pagina.Controls.Add(dgv);
                                tabExcel.Controls.Add(pagina);
                            }
                        }                      
                    }
                    catch (Exception ex)
                    {
                        MessageBox.Show(ex.Message,"Error al leer el Excel");
                    }
                }
            }
        }

        private void nuevaLectura(object sender, EventArgs e)
        {
            txtArchivo.Clear();
            tabExcel.Controls.Clear();
        }
    }
}

Nota: El método "abrirExcel" estaasociado al evento click del botón "btnAbrir" y el método "nuevaLectura" esta asociado al evento click del botón "btnNuevo".

4. Ejecutar y Probar la Aplicación Windows Forms

Grabar la aplicación y pulsar F5 para ejecutarla, luego clic al botón "..." y se mostrará el diálogo de abrir mostrando los archivos de Excel 2007 (xlsx), tal como se muestra en la siguiente figura:

Dialogo de Abrir archivos de Excel

Seleccionar un archivo del diálogo y se cargarán todas las hojas del libro, mostrándose en una página del control Tab una grilla, tal como se muestra en la siguiente figura:

Primer Tab con la primera Hoja

En mi caso abrí un archivo con 2 hojas, la primera que tiene Personajes de JavaScript y la segunda con algunos términos de programación, tal como se muestra en la siguiente figura:

Segundo Tab con la segunda Hoja

Si se desea se puede abrir cualquier otro archivo dando click nuevamente al botón "...", tal como se muestra en la siguiente figura:

Archivo Excel con solo una Hoja

En este último caso se abrió un archivo con solo una Hoja de Productos.

5. Comentario Final

En este Demo hemos visto como Leer todas las hojas de un libro de Excel 2007 usando OLEDB, para lo cual se uso el método "GetOleDbSchemaTable()" con la restricción de tablas para obtener todos los nombres de tablas pero solo mostramos las que terminan en $, que son las Hojas de Excel.

Se uso un "OleDbDataAdapter" para ejecutar el comando Select para obtener los datos de la hoja en un "DataTable" y luego se creó dinámicamente una grilla, un tabPage y se agregó la grilla al tabPage y el tabPage al tabControl para que se vea los datos de cada hoja.

El que desea conservar todos los datos para después trabajarlos y luego guardarlos en una base de datos, en ves de usar un DataTable local (en el DataAdapter) debe usar un DataSet global (a nivel de clase).

Espero que les sea útil este Demo y en estos días publicaré una técnica más rápida que es leer archivos xml dentro del paquete (xlsx) la cual es más rápida aunque mas laboriosa y que algunos usan sin darse cuenta al usar librerías de lectura de archivos Excel.

6. Descarga la Aplicación Windows

2016_07_19_Demo_Excel_Leer_OLEDB

jueves, 3 de julio de 2014

El Demo del Día: Convertir Lista de Objetos en un Archivo DBF y Viceversa

Convertir Lista de Objetos en un Archivo DBF y Viceversa

Requerimiento

Se desea leer datos de una Base de Datos de SQL Server y a partir de estos crear un archivo DBF.

Solución

Usamos una Lista de Objetos para recibir los datos de SQL Server y luego usamos OLEDB y Reflection para crear una tabla e insertar los registros en esta.

Crear la Clase con los Métodos de Conversión

Crear una aplicación Windows Forms en C# llamada "Exportar_DBF" y luego crear una clase con 2 métodos estáticos, uno que genere un archivo DBF a partir de una lista de objetos y otro que a partir de un archivo DBF genere una Lista de Objetos, tal como se muestra en el siguiente código:

using System;
using System.Text; //StringBuilder
using System.Collections.Generic; //List<T>
using System.Reflection; //PropertyInfo, Assembly
using System.Data; //CommandBehavior
using System.Data.OleDb; //OledbConnection, OledbCommand, OleDbDataReader
using System.IO; //File, Path

namespace Exportar_DBF
{
    public class ucListaDBF<T>
    {
        public static void ListaADBF(List<T> lista,string nombreTabla)
        {
            StringBuilder sbCreate = new StringBuilder();
            sbCreate.Append("Create Table ");
            sbCreate.Append(nombreTabla);
            sbCreate.AppendLine(" (");
            PropertyInfo[] propiedades = lista[0].GetType().GetProperties();
            for (int i = 0; i < propiedades.Length; i++)
            {
                sbCreate.Append(propiedades[i].Name);
                sbCreate.Append(" ");
                sbCreate.Append(convertirTipo(propiedades[i].PropertyType.ToString()));
                if (i < propiedades.Length - 1) sbCreate.AppendLine(",");
            }
            sbCreate.AppendLine("");
            sbCreate.AppendLine(")");

            string ruta = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location);
            string archivo = string.Format("{0}\\{1}.dbf", ruta, nombreTabla);
            if (File.Exists(archivo)) File.Delete(archivo);
            using (OleDbConnection con = new OleDbConnection
                ("provider=Microsoft.Jet.Oledb.4.0;data source=" + ruta + ";
                  extended properties=dBase IV"))
            {
                con.Open();
                OleDbCommand cmd = new OleDbCommand(sbCreate.ToString(), con);
                cmd.ExecuteNonQuery();

                StringBuilder sbInsert = new StringBuilder();
                for (int j = 0; j < lista.Count; j++)
                {
                    sbInsert.Clear();
                    propiedades = lista[j].GetType().GetProperties();
                    sbInsert.Append("Insert Into ");
                    sbInsert.Append(nombreTabla);
                    sbInsert.Append(" Values (");
                    for (int i = 0; i < propiedades.Length; i++)
                    {
                        if (propiedades[i].PropertyType.ToString().Contains("String")
                            || propiedades[i].PropertyType.ToString().Contains("DateTime"))
                            sbInsert.Append("'");
                        sbInsert.Append(propiedades[i].GetValue(lista[j], null).ToString().Replace("'",""));
                        if (propiedades[i].PropertyType.ToString().Contains("String")
                            || propiedades[i].PropertyType.ToString().Contains("DateTime"))
                            sbInsert.Append("'");
                        if (i < propiedades.Length - 1) sbInsert.Append(",");
                    }
                    sbInsert.AppendLine(")");
                    cmd.CommandText = sbInsert.ToString();
                    cmd.ExecuteNonQuery();
                }
            }
        }

        private static string convertirTipo(string tipoNET)
        {
            string tipoOLEDB = "";
            tipoNET = tipoNET.ToLower();
            if (tipoNET.Contains("int")) tipoOLEDB = "Long";
            else
            {
                if (tipoNET.Contains("decimal")) tipoOLEDB = "Double";
                else
                {
                    if (tipoNET.Contains("datetime")) tipoOLEDB = "DateTime";
                    else
                    {
                        tipoOLEDB = "Text(100)";
                    }
                }
            }
            return (tipoOLEDB);
        }

        public static List<T> DBFALista(string nombreTabla)
        {
            List<T> lista = new List<T>();
            Type tipo = typeof(T);
            string ruta = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location);
            using (OleDbConnection con = new OleDbConnection
                ("provider=Microsoft.Jet.Oledb.4.0;data source=" + ruta + ";
                  extended properties=dBase IV"))
            {
                con.Open();
                OleDbCommand cmd = new OleDbCommand("Select * From " + nombreTabla, con);
                OleDbDataReader drd = cmd.ExecuteReader(CommandBehavior.SingleResult);
                if (drd != null)
                {
                    object obe = null;
                    string tipoDato;
                    PropertyInfo propiedad;
                    while (drd.Read())
                    {
                        obe = Activator.CreateInstance(tipo);
                        for (int i = 0; i < drd.FieldCount; i++)
                        {
                            propiedad = obe.GetType().GetProperty(drd.GetName(i).ToLower(),
                                BindingFlags.IgnoreCase|BindingFlags.Public|BindingFlags.Instance);
                            tipoDato = drd[i].GetType().ToString().ToLower();
                            if (tipoDato.Contains("int16")) propiedad.SetValue(obe, drd.GetInt16(i));
                            else
                            {
                                if (tipoDato.Contains("int32")) propiedad.SetValue(obe, drd.GetInt32(i));
                                else
                                {
                                    if (tipoDato.Contains("decimal")) propiedad.SetValue
                                      (obe, drd.GetDecimal(i));
                                    else
                                    {
                                        if (tipoDato.Contains("datetime")) propiedad.SetValue
                                          (obe, drd.GetDateTime(i));
                                        else
                                        {
                                            if (tipoDato.Contains("double")) propiedad.SetValue
                                              (obe, (int)drd.GetDouble(i));
                                            else propiedad.SetValue(obe, drd.GetString(i));
                                        }
                                    }
                                }
                            }
                        }
                        lista.Add((T)obe);
                    }
                }
            }
            return (lista);
        }
    }
}

Nota: Por defecto OLEDB crea en mayúsculas el nombre de la tabla y los campos, es por eso que para leer la propiedad se usa el método "GetProperty" con 2 parámetros: el nombre del campo y un flag que indica que no sea case sensitive: BindingFlags.IgnoreCase|BindingFlags.Public|BindingFlags.Instance.

Crear el Procedimiento Almacenado en la Base de Datos de SQL Server

Create Procedure [dbo].[uspEmployeesListar]
As
Select EmployeeID,LastName,FirstName,
IsNull(BirthDate,'1/1/1900') As BirthDate
From Employees Order By 1

Crear la Clase con la Entidad del Negocio

using System;
namespace Exportar_DBF
{
    public class beEmpleado
    {
        public int IdEmpleado { get; set; }
        public string Apellido { get; set; }
        public string Nombre { get; set; }
        public DateTime FechaNac { get; set; }
    }
}

Nota: Para el caso de trabajar con DBF tener cuidado que los nombres de las propiedades tengan como máximo 10 caracteres, de lo contrario el método "DBFALista" generará un error.

Crear la Clase de Datos

using System;
using System.Data; //CommandType
using System.Data.SqlClient; //SqlConnection, SqlCommand, SqlDataReader
using System.Collections.Generic; //List

namespace Exportar_DBF
{
    public class daEmpleado
    {
        public List<beEmpleado> listar(SqlConnection con)
        {
            List<beEmpleado> lbeEmpleado = null;
            SqlCommand cmd = new SqlCommand("uspEmployeesListar", con);
            cmd.CommandType = CommandType.StoredProcedure;
            SqlDataReader drd = cmd.ExecuteReader(CommandBehavior.SingleResult);
            if (drd != null)
            {
                lbeEmpleado = new List<beEmpleado>();
                int posIdEmpleado = drd.GetOrdinal("EmployeeID");
                int posApellido = drd.GetOrdinal("LastName");
                int posNombre = drd.GetOrdinal("FirstName");
                int posFechaNacimiento = drd.GetOrdinal("BirthDate");
                beEmpleado obeEmpleado;
                while (drd.Read())
                {
                    obeEmpleado = new beEmpleado();
                    obeEmpleado.IdEmpleado = drd.GetInt32(posIdEmpleado);
                    obeEmpleado.Apellido = drd.GetString(posApellido);
                    obeEmpleado.Nombre = drd.GetString(posNombre);
                    obeEmpleado.FechaNac = drd.GetDateTime(posFechaNacimiento);
                    lbeEmpleado.Add(obeEmpleado);
                }
                drd.Close();
            }
            return (lbeEmpleado);
        }
    }
}

Crear la Clase con las Reglas del Negocio

using System;
using System.Collections.Generic;
using System.Data.SqlClient; //SqlConnection
using System.Configuration; //ConfigurationManager

namespace Exportar_DBF
{
    public class brEmpleado
    {
        public string Conexion { get; set; }

        public brEmpleado()
        {
            Conexion = ConfigurationManager.ConnectionStrings
                ["conNW"].ConnectionString;
        }

        public List<beEmpleado> listar()
        {
            List<beEmpleado> lbeEmpleado = null;
            using (SqlConnection con = new SqlConnection(Conexion))
            {
                try
                {
                    con.Open();
                    daEmpleado odaEmpleado = new daEmpleado();
                    lbeEmpleado = odaEmpleado.listar(con);
                }
                catch (SqlException ex)
                {
                    //Capturar el error y grabar un Log
                }
            } //con.Close(); con.Dispose(); con = null;
            return (lbeEmpleado);
        }
    }
}

Modificar el Archivo de Configuración de la Aplicación

Abrir el archivo App.Config y aumentar la cadena de conexión a Northwind (conNW):

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
    <connectionStrings>
      <add name="conNW" providerName="SQLServer"
         connectionString="uid=UsuarioNW;pwd=123456;
         data source=DSOFT\Sqlexpress;database=Northwind"/>
    </connectionStrings>
    <startup>
        <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.5" />
    </startup>
</configuration>

Crear el Formulario Windows Forms de Prueba

Cambiar el nombre del formulario a "frmListaDBF" y arrastrar 3 controles:
- Un DataGridView llamado "dgvEmpleado"
- Un Button llamado "btnExportarDBF"
- Un Button llamado "btnCargarDBF"


Escribir el siguiente código en el formulario:

using System;
using System.Collections.Generic;
using System.Drawing;
using System.Windows.Forms;

namespace Exportar_DBF
{
    public partial class frmListaDBF : Form
    {
        private List<beEmpleado> lbeEmpleado;

        public frmListaDBF()
        {
            InitializeComponent();
        }

        private void cargarDatosSQL(object sender, EventArgs e)
        {
            brEmpleado obrEmpleado = new brEmpleado();
            lbeEmpleado = obrEmpleado.listar();
            dgvEmpleado.DataSource = lbeEmpleado;
        }

        private void exportarListaDBF(object sender, EventArgs e)
        {
            ucListaDBF<beEmpleado>.ListaADBF(lbeEmpleado, "Empleado");
            MessageBox.Show("DBF creado");
        }

        private void cargarDBF(object sender, EventArgs e)
        {
            lbeEmpleado = ucListaDBF<beEmpleado>.DBFALista("Empleado");
            MessageBox.Show("Lista creada");
        }
    }
}

Ejecutar y Probar el Formulario Creado

Grabar la aplicación, compilar y ejecutar, clic al botón "Exportar a DBF" y se creará en el directorio de la aplicación "Exportar_DBF\bin\Debug" el archivo "EMPLEADO.DBF", luego para comprobar que el DBF fue creado clic al botón "Cargar DBF" que lee el DBF en una lista de objetos y lo muestra en la misma grilla.


Comentario Final

Usando OLEDB y Reflection hemos creado dinámicamente un archivo DBF con datos almacenados en una lista de objetos, también hemos realizado la carga del archivo DBF en una Lista de Objetos. El único cuidado que debemos tener es que los nombres de campos no deben pasar los 10 caracteres porque hay una limitación de los campos en un archivo DBF.

Descarga:
Demo09_Exportar_DBF