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>
{
}
}
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
..............................................................................
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
Gracias, pensando siempre en consumir menos memoria.
ResponderBorrarMuy buen aporte, como siempre pensando en la performance de las aplicaciones. Lo mejor lista de objetos.
ResponderBorrar