c# - texto - leer un archivo csv en c
Leer archivos CSV usando C# (10)
Antes que nada necesitamos entender qué es CSV y cómo escribirlo.
- Cada cadena siguiente (
/r/n
) es la siguiente fila de "tabla". - Las celdas de "Tabla" están separadas por algún símbolo delimitador. Los símbolos más utilizados son
/t
or,
- Cada celda posiblemente puede contener este símbolo delimitador (la celda debe comenzar con el símbolo de comillas y termina con este símbolo en este caso)
- Cada celda posiblemente puede contener
/r/n
sybols (la celda debe comenzar con el símbolo de comillas y termina con este símbolo en este caso)
La forma más fácil para que C # / Visual Basic funcione con archivos CSV es utilizar la biblioteca estándar Microsoft.VisualBasic
. Solo necesita agregar la referencia necesaria y la siguiente cadena a su clase:
using Microsoft.VisualBasic.FileIO;
Sí, puedes usarlo en C #, no te preocupes. Esta biblioteca puede leer archivos relativamente grandes y admite todas las reglas necesarias, por lo que podrá trabajar con todos los archivos CSV.
Hace algún tiempo, escribí una clase simple de CSV de lectura / escritura basada en esta biblioteca. Usando esta clase simple podrás trabajar con CSV como con 2 dimensiones de matriz. Puede encontrar mi clase en el siguiente enlace: https://github.com/ukushu/DataExporter
Ejemplo simple de uso:
Csv csv = new Csv("/t");//delimiter symbol
csv.FileOpen("c://file1.csv");
var row1Cell6Value = csv.Rows[0][5];
csv.AddRow("asdf","asdffffff","5")
csv.FileSave("c://file2.csv");
Estoy escribiendo una aplicación de importación simple y necesito leer un archivo CSV, mostrar el resultado en un DataGrid
y mostrar las líneas dañadas del archivo CSV en otra cuadrícula. Por ejemplo, muestre las líneas que son más cortas que 5 valores en otra grilla. Estoy tratando de hacer eso así:
StreamReader sr = new StreamReader(FilePath);
importingData = new Account();
string line;
string[] row = new string [5];
while ((line = sr.ReadLine()) != null)
{
row = line.Split('','');
importingData.Add(new Transaction
{
Date = DateTime.Parse(row[0]),
Reference = row[1],
Description = row[2],
Amount = decimal.Parse(row[3]),
Category = (Category)Enum.Parse(typeof(Category), row[4])
});
}
pero es muy difícil operar en arrays en este caso. ¿Hay alguna forma mejor de dividir los valores?
CSV puede complicarse muy rápido.
Use algo robusto y bien probado:
FileHelpers: www.filehelpers.net
FileHelpers es una biblioteca .NET gratuita y fácil de usar para importar / exportar datos de registros de longitud fija o delimitados en archivos, cadenas o flujos.
En ocasiones, utilizar bibliotecas es genial cuando no se quiere reinventar la rueda, pero en este caso se puede hacer el mismo trabajo con menos líneas de código y más fácil de leer en comparación con el uso de bibliotecas. Aquí hay un enfoque diferente que me resulta muy fácil de usar.
- En este ejemplo, uso StreamReader para leer el archivo
- Regex para detectar el delimitador de cada línea (s).
- Una matriz para recopilar las columnas del índice 0 a n
using (StreamReader reader = new StreamReader(fileName))
{
string line;
while ((line = reader.ReadLine()) != null)
{
//Define pattern
Regex CSVParser = new Regex(",(?=(?:[^/"]*/"[^/"]*/")*(?![^/"]*/"))");
//Separating columns to array
string[] X = CSVParser.Split(line);
/* Do something with X */
}
}
Mi experiencia es que hay muchos formatos de cvs diferentes. Especialmente cómo manejan el escape de citas y delimitadores dentro de un campo.
Estas son las variantes con las que me he encontrado:
- las comillas se cotizan y duplican (excel), es decir, 15 "-> campo1," 15 "" ", campo3
- las cotizaciones no se cambian a menos que el campo se cotice por alguna otra razón. es decir, 15 "-> campo1,15", campos3
- las comillas se escapan con /. es decir, 15 "-> campo1," 15 / "", campo3
- las citas no se cambian en absoluto (esto no siempre es posible analizar correctamente)
- el delimitador se cita (excel). es decir, a, b -> campo1, "a, b", campo3
- delimiter se escapó con /. es decir, a, b -> campo1, a /, b, campo3
He intentado muchos de los analizadores csv existentes, pero no hay ninguno que pueda manejar las variantes con las que me he encontrado. También es difícil averiguar a partir de la documentación qué variantes de escape admiten los analizadores.
En mis proyectos ahora uso VB TextFieldParser o un splitter personalizado.
No reinventar la rueda. Aproveche lo que ya está en .NET BCL.
- agregue una referencia al
Microsoft.VisualBasic
(sí, dice VisualBasic pero también funciona en C #, recuerde que al final todo es IL) - utilice la clase
Microsoft.VisualBasic.FileIO.TextFieldParser
para analizar el archivo CSV
Aquí está el código de ejemplo:
using (TextFieldParser parser = new TextFieldParser(@"c:/temp/test.csv"))
{
parser.TextFieldType = FieldType.Delimited;
parser.SetDelimiters(",");
while (!parser.EndOfData)
{
//Processing row
string[] fields = parser.ReadFields();
foreach (string field in fields)
{
//TODO: Process field
}
}
}
Funciona muy bien para mí en mis proyectos de C #.
Aquí hay algunos enlaces más / información:
Otro en esta lista, Cinchoo ETL - una biblioteca de código abierto para leer y escribir archivos CSV
Para un archivo CSV de muestra a continuación
Id, Name
1, Tom
2, Mark
Rápidamente puede cargarlos usando la biblioteca de la siguiente manera
using (var reader = new ChoCSVReader("test.csv").WithFirstLineHeader())
{
foreach (dynamic item in reader)
{
Console.WriteLine(item.Id);
Console.WriteLine(item.Name);
}
}
Si tiene una clase POCO que coincida con el archivo CSV
public class Employee
{
public int Id { get; set; }
public string Name { get; set; }
}
Puede usarlo para cargar el archivo CSV como se muestra a continuación
using (var reader = new ChoCSVReader<Employee>("test.csv").WithFirstLineHeader())
{
foreach (var item in reader)
{
Console.WriteLine(item.Id);
Console.WriteLine(item.Name);
}
}
Consulte los artículos en CodeProject sobre cómo usarlo.
Descargo de responsabilidad: soy el autor de esta biblioteca
Para completar las respuestas anteriores, uno puede necesitar una colección de objetos de su archivo CSV, ya sea analizados por el TextFieldParser
o por el método string.Split
, y luego cada línea convertida en un objeto a través de Reflection. Obviamente, primero necesita definir una clase que coincida con las líneas del archivo CSV.
Utilicé el Serializador CSV simple de Michael Kropat que se encuentra aquí: Clase genérica a CSV (todas las propiedades) y reutilicé sus métodos para obtener los campos y las propiedades de la clase deseada.
Deserializo mi archivo CSV con el siguiente método:
public static IEnumerable<T> ReadCsvFileTextFieldParser<T>(string fileFullPath, string delimiter = ";") where T : new()
{
if (!File.Exists(fileFullPath))
{
return null;
}
var list = new List<T>();
var csvFields = GetAllFieldOfClass<T>();
var fieldDict = new Dictionary<int, MemberInfo>();
using (TextFieldParser parser = new TextFieldParser(fileFullPath))
{
parser.SetDelimiters(delimiter);
bool headerParsed = false;
while (!parser.EndOfData)
{
//Processing row
string[] rowFields = parser.ReadFields();
if (!headerParsed)
{
for (int i = 0; i < rowFields.Length; i++)
{
// First row shall be the header!
var csvField = csvFields.Where(f => f.Name == rowFields[i]).FirstOrDefault();
if (csvField != null)
{
fieldDict.Add(i, csvField);
}
}
headerParsed = true;
}
else
{
T newObj = new T();
for (int i = 0; i < rowFields.Length; i++)
{
var csvFied = fieldDict[i];
var record = rowFields[i];
if (csvFied is FieldInfo)
{
((FieldInfo)csvFied).SetValue(newObj, record);
}
else if (csvFied is PropertyInfo)
{
var pi = (PropertyInfo)csvFied;
pi.SetValue(newObj, Convert.ChangeType(record, pi.PropertyType), null);
}
else
{
throw new Exception("Unhandled case.");
}
}
if (newObj != null)
{
list.Add(newObj);
}
}
}
}
return list;
}
public static IEnumerable<MemberInfo> GetAllFieldOfClass<T>()
{
return
from mi in typeof(T).GetMembers(BindingFlags.Public | BindingFlags.Instance | BindingFlags.Static)
where new[] { MemberTypes.Field, MemberTypes.Property }.Contains(mi.MemberType)
let orderAttr = (ColumnOrderAttribute)Attribute.GetCustomAttribute(mi, typeof(ColumnOrderAttribute))
orderby orderAttr == null ? int.MaxValue : orderAttr.Order, mi.Name
select mi;
}
Recomiendo CsvHelper de Nuget .
(Agregar una referencia a Microsoft.VisualBasic simplemente no parece correcto, no solo es feo, probablemente ni siquiera sea multiplataforma).
Yo uso esto aquí:
http://www.codeproject.com/KB/database/GenericParser.aspx
La última vez que estaba buscando algo así, lo encontré como una respuesta a esta question .
private static DataTable ConvertCSVtoDataTable(string strFilePath)
{
DataTable dt = new DataTable();
using (StreamReader sr = new StreamReader(strFilePath))
{
string[] headers = sr.ReadLine().Split('','');
foreach (string header in headers)
{
dt.Columns.Add(header);
}
while (!sr.EndOfStream)
{
string[] rows = sr.ReadLine().Split('','');
DataRow dr = dt.NewRow();
for (int i = 0; i < headers.Length; i++)
{
dr[i] = rows[i];
}
dt.Rows.Add(dr);
}
}
return dt;
}
private static void WriteToDb(DataTable dt)
{
string connectionString =
"Data Source=localhost;" +
"Initial Catalog=Northwind;" +
"Integrated Security=SSPI;";
using (SqlConnection con = new SqlConnection(connectionString))
{
using (SqlCommand cmd = new SqlCommand("spInsertTest", con))
{
cmd.CommandType = CommandType.StoredProcedure;
cmd.Parameters.Add("@policyID", SqlDbType.Int).Value = 12;
cmd.Parameters.Add("@statecode", SqlDbType.VarChar).Value = "blagh2";
cmd.Parameters.Add("@county", SqlDbType.VarChar).Value = "blagh3";
con.Open();
cmd.ExecuteNonQuery();
}
}
}