jueves, 12 de junio de 2014

El Demo del Día: Leer y Escribir Archivos de Texto usando Lista de Objetos

Leer y Escribir Archivos de Texto usando Lista de Objetos

Requerimiento
Se desea leer un archivo con registros separador por un carácter, por ejemplo por comas, punto y coma o por el símbolo "|".
Luego el usuario podrá modificar los registros existentes y al final debería grabar en el mismo archivo o si desea en otro archivo todo los registros.

Solución
La mejor solución es usar una Lista de Objetos debido a que esta consume menos memoria que el DataTable (exactamente la tercera parte) y se procesa mas rápido (la quinta parte aproximadamente), luego lo enlazamos a una grilla, en este caso el control DataGridView de WinForms y luego podemos grabar nuevamente el contenido de la Lista de Objetos en un archivo de texto.

Paso 1: Crear una Clase Genérica para crear los Métodos que usen la Lista de Objetos
using System.Reflection;
using System.IO;

namespace LeerCrearArchivoTextoLista
{
    public class ucTextoLista<T>
    {
    }
}

Paso 2: Función Genérica para Leer un Archivo de Texto en una Lista de Objetos
        public static List<T> TextoALista(string archivo, char separador)
        {
            List<T> lista = new List<T>();
            Type tipo = typeof(T);
            if (File.Exists(archivo))
            {
                using (StreamReader sr = new StreamReader(archivo))
                {
                    string[] cabeceras = sr.ReadLine().Split(separador);
                    if (cabeceras != null && cabeceras.Length > 0)
                    {
                        string[] campos = null;
                        object obe = null;
                        string tipoDato;
                        while (!sr.EndOfStream)
                        {
                            obe = Activator.CreateInstance(tipo);
                            campos = sr.ReadLine().Split(separador);
                            if (campos != null && campos.Length > 0)
                            {
                                for (int i = 0; i < campos.Length; i++)
                                {
                                    tipoDato =obe.GetType().GetProperty(cabeceras[i]).PropertyType.
                                                      ToString().ToLower();
                                    if (tipoDato.Contains("int16"))
                                    obe.GetType().GetProperty(cabeceras[i]).SetValue
                                      (obe, short.Parse(campos[i]));
                                    else
                                    {
                                        if (tipoDato.Contains("int32"))
                                        obe.GetType().GetProperty(cabeceras[i]).SetValue
                                          (obe, int.Parse(campos[i]));
                                        else
                                        {
                                            if (tipoDato.Contains("decimal"))
                                                obe.GetType().GetProperty(cabeceras[i]).SetValue
                                                  (obe, decimal.Parse(campos[i]));
                                            else obe.GetType().GetProperty(cabeceras[i]).
                                              SetValue(obe, campos[i]);
                                        }
                                    }
                                }
                            }
                            lista.Add((T)obe);
                        }
                    }
                }
            }
            return (lista);
        }

Paso 3: Función Genérica para Crear un Archivo de Texto desde una Lista de Objetos
        public static void ListaATexto(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();
                    }
                }
            }
        }

Paso 4: Archivo de Texto separados por un caractér (csv)
Crear un archivo de texto llamado "Productos.txt" con los siguientes datos:
ProductID,ProductName,SupplierID,CategoryID,UnitPrice,UnitsInStock
1,Chai,2,1,10.0000,76
2,Chang,7,4,3.0000,22
3,Chicha,10,1,10.0000,35
4,Chef Anton's Cajun Seasoning,2,2,22.0000,80
5,Chef Anton's Gumbo Mix,2,2,21.3500,31
6,Grandma's Boysenberry Spread,2,3,25.0000,50
7,Uncle Bob's Organic Dried Pears,3,7,30.0000,98
8,Northwoods Cranberry Sauce,3,2,40.0000,99
9,Mishi Kobe Niku,4,6,97.0000,96
10,Ikura,4,8,31.0000,93
..............................................................................

Paso 5: Crear una Clase Entidad para Almacenar los Productos
    public class beProducto
    {
        public int IdProducto { get; set; }
        public string Nombre { get; set; }
        public int IdProveedor { get; set; }
        public int IdCategoria { get; set; }
        public decimal PrecioUnitario { get; set; }
        public short Stock { get; set; }
    }

Paso 6: Crear un  Formulario con una Grilla que pueda usar las 2 Funciones
Asumiendo que las 2 funciones estáticas fueron creadas en una clase llamada "ucTextoLista" y hemos creado un formulario llamado: "frmPrueba" conteniendo un control DataGridView llamado: "dgvTexto" y 2 botones llamados: "btnLeerArchivoTxt" y "btnCrearArchivoTxt", tal como se muestra en la siguiente imagen:


Escribir el siguiente código en el formulario:
    public partial class frmPrueba : Form
    {
        List<beProducto> lista;

        public frmPrueba()
        {
            InitializeComponent();
        }

        private void btnLeerArchivoTxt_Click(object sender, EventArgs e)
        {
            lista = ucTextoLista<beProducto>.TextoALista("Productos.txt", ',');
            dgvTexto.DataSource = lista;
        }

        private void btnCrearArchivoTxt_Click(object sender, EventArgs e)
        {
            ucTextoLista<beProducto>.ListaATexto(lista,"Productos2.txt", ',');
            MessageBox.Show("Archivo de Texto creado");
        }
    }

Paso 7: Ejecutar la aplicación y Probar
Para ejecutar la aplicación se tiene que tener el archivo: "Productos.txt" en la carpeta bin/debug donde corre la aplicación con los datos descritos en el paso 4.
Primero dar clic al primer botón: "Leer Archivo Txt" y se cargarán los datos de los productos desde el archivo; luego ingresar nuevos registros, modificar los existentes y finalmente dar clic al botón "Crear Archivo Txt" y observar que se ha creado en la carpeta bin/debug el archivo "Productos2.txt".


Comentario:
Esta forma de leer y crear archivos de texto es mas rápida pero necesita conocer Reflection para trabajar con una Lista de Objetos y poder deducir sus propiedades, recuperar valores y asignar valores a cualquier tipo de objeto. Como pocos desarrolladores conocen el poder de Reflection se van por el "lado oscuro" (ineficiente pero fácil) y terminan trabajando con el DataSet, DataTable, DataView, XML, etc.

Descarga:
Demo02_LeerCrearArchivoTextoLista

2 comentarios:

  1. Gracias, pensando siempre en consumir menos memoria.

    ResponderBorrar
  2. Muy buen aporte, como siempre pensando en la performance de las aplicaciones. Lo mejor lista de objetos.

    ResponderBorrar