Quelle est la meilleure façon d'importer un fichier CSV dans une structure de données fortement typée?
Quelle est la meilleure façon d'importer un fichier CSV dans une structure de données fortement typée?
Réponses:
TextFieldParser de Microsoft est stable et suit la RFC 4180 pour les fichiers CSV. Ne soyez pas rebuté par l' Microsoft.VisualBasic
espace de noms; c'est un composant standard dans le .NET Framework, ajoutez simplement une référence à l' Microsoft.VisualBasic
assembly global .
Si vous compilez pour Windows (par opposition à Mono) et que vous ne prévoyez pas d'analyser des fichiers CSV «cassés» (non conformes à la RFC), alors ce serait le choix évident, car il est gratuit, sans restriction, stable, et activement pris en charge, dont la plupart ne peut pas être dit pour FileHelpers.
Voir aussi: Comment: lire des fichiers texte délimités par des virgules dans Visual Basic pour un exemple de code VB.
TextFieldParser
volonté de travail délimité par des tabulations et d' autres étranges cruft généré Excel aussi. Je me rends compte que votre réponse précédente ne prétendait pas que la bibliothèque était spécifique à VB, elle m'est juste apparue comme impliquant qu'elle était vraiment destinée à VB, et non destinée à être utilisée à partir de C #, ce que je ne pense pas le cas - il existe des classes vraiment utiles dans MSVB.
Utilisez une connexion OleDB.
String sConnectionString = "Provider=Microsoft.Jet.OLEDB.4.0;Data Source=C:\\InputDirectory\\;Extended Properties='text;HDR=Yes;FMT=Delimited'";
OleDbConnection objConn = new OleDbConnection(sConnectionString);
objConn.Open();
DataTable dt = new DataTable();
OleDbCommand objCmdSelect = new OleDbCommand("SELECT * FROM file.csv", objConn);
OleDbDataAdapter objAdapter1 = new OleDbDataAdapter();
objAdapter1.SelectCommand = objCmdSelect;
objAdapter1.Fill(dt);
objConn.Close();
Si vous vous attendez à des scénarios assez complexes pour l'analyse CSV, ne pensez même pas à lancer notre propre analyseur . Il existe de nombreux excellents outils, comme FileHelpers , ou même ceux de CodeProject .
Le fait est que c'est un problème assez courant et vous pouvez parier que de nombreux développeurs de logiciels ont déjà pensé et résolu ce problème.
Brian donne une belle solution pour le convertir en une collection fortement typée.
La plupart des méthodes d'analyse CSV données ne prennent pas en compte les champs d'échappement ou certaines des autres subtilités des fichiers CSV (comme le découpage des champs). Voici le code que j'utilise personnellement. C'est un peu rugueux sur les bords et il n'y a pratiquement pas de rapport d'erreur.
public static IList<IList<string>> Parse(string content)
{
IList<IList<string>> records = new List<IList<string>>();
StringReader stringReader = new StringReader(content);
bool inQoutedString = false;
IList<string> record = new List<string>();
StringBuilder fieldBuilder = new StringBuilder();
while (stringReader.Peek() != -1)
{
char readChar = (char)stringReader.Read();
if (readChar == '\n' || (readChar == '\r' && stringReader.Peek() == '\n'))
{
// If it's a \r\n combo consume the \n part and throw it away.
if (readChar == '\r')
{
stringReader.Read();
}
if (inQoutedString)
{
if (readChar == '\r')
{
fieldBuilder.Append('\r');
}
fieldBuilder.Append('\n');
}
else
{
record.Add(fieldBuilder.ToString().TrimEnd());
fieldBuilder = new StringBuilder();
records.Add(record);
record = new List<string>();
inQoutedString = false;
}
}
else if (fieldBuilder.Length == 0 && !inQoutedString)
{
if (char.IsWhiteSpace(readChar))
{
// Ignore leading whitespace
}
else if (readChar == '"')
{
inQoutedString = true;
}
else if (readChar == ',')
{
record.Add(fieldBuilder.ToString().TrimEnd());
fieldBuilder = new StringBuilder();
}
else
{
fieldBuilder.Append(readChar);
}
}
else if (readChar == ',')
{
if (inQoutedString)
{
fieldBuilder.Append(',');
}
else
{
record.Add(fieldBuilder.ToString().TrimEnd());
fieldBuilder = new StringBuilder();
}
}
else if (readChar == '"')
{
if (inQoutedString)
{
if (stringReader.Peek() == '"')
{
stringReader.Read();
fieldBuilder.Append('"');
}
else
{
inQoutedString = false;
}
}
else
{
fieldBuilder.Append(readChar);
}
}
else
{
fieldBuilder.Append(readChar);
}
}
record.Add(fieldBuilder.ToString().TrimEnd());
records.Add(record);
return records;
}
Notez que cela ne gère pas la casse de bord des champs qui ne sont pas délimités par des guillemets doubles, mais meerley ayant une chaîne entre guillemets à l'intérieur. Voir cet article pour une meilleure explication ainsi que des liens vers certaines bibliothèques appropriées.
Je suis d'accord avec @ NotMyself . FileHelpers est bien testé et gère toutes sortes de cas que vous devrez éventuellement gérer si vous le faites vous-même. Jetez un œil à ce que fait FileHelpers et n'écrivez les vôtres que si vous êtes absolument sûr que (1) vous n'aurez jamais besoin de gérer les cas extrêmes que FileHelpers fait, ou (2) vous aimez écrire ce genre de choses et allez soyez ravi lorsque vous devez analyser des choses comme celle-ci:
1, "Bill", "Smith", "Supervisor", "No Comment"
2, «Drake», «O'Malley», «Concierge,
Oups, je ne suis pas cité et je suis sur une nouvelle ligne!
Je m'ennuyais donc j'ai modifié certaines choses que j'ai écrites. Il essaie d'encapsuler l'analyse d'une manière OO tout en réduisant le nombre d'itérations dans le fichier, il n'itère qu'une fois en haut pour chacun.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
namespace ConsoleApplication1
{
class Program
{
static void Main(string[] args)
{
// usage:
// note this wont run as getting streams is not Implemented
// but will get you started
CSVFileParser fileParser = new CSVFileParser();
// TO Do: configure fileparser
PersonParser personParser = new PersonParser(fileParser);
List<Person> persons = new List<Person>();
// if the file is large and there is a good way to limit
// without having to reparse the whole file you can use a
// linq query if you desire
foreach (Person person in personParser.GetPersons())
{
persons.Add(person);
}
// now we have a list of Person objects
}
}
public abstract class CSVParser
{
protected String[] deliniators = { "," };
protected internal IEnumerable<String[]> GetRecords()
{
Stream stream = GetStream();
StreamReader reader = new StreamReader(stream);
String[] aRecord;
while (!reader.EndOfStream)
{
aRecord = reader.ReadLine().Split(deliniators,
StringSplitOptions.None);
yield return aRecord;
}
}
protected abstract Stream GetStream();
}
public class CSVFileParser : CSVParser
{
// to do: add logic to get a stream from a file
protected override Stream GetStream()
{
throw new NotImplementedException();
}
}
public class CSVWebParser : CSVParser
{
// to do: add logic to get a stream from a web request
protected override Stream GetStream()
{
throw new NotImplementedException();
}
}
public class Person
{
public String Name { get; set; }
public String Address { get; set; }
public DateTime DOB { get; set; }
}
public class PersonParser
{
public PersonParser(CSVParser parser)
{
this.Parser = parser;
}
public CSVParser Parser { get; set; }
public IEnumerable<Person> GetPersons()
{
foreach (String[] record in this.Parser.GetRecords())
{
yield return new Person()
{
Name = record[0],
Address = record[1],
DOB = DateTime.Parse(record[2]),
};
}
}
}
}
Il existe deux articles sur CodeProject qui fournissent du code pour une solution, un qui utilise StreamReader et un qui importe des données CSV à l'aide du pilote de texte Microsoft .
Un bon moyen simple de le faire est d'ouvrir le fichier et de lire chaque ligne dans un tableau, une liste chaînée, une structure de données de votre choix. Soyez prudent lorsque vous manipulez la première ligne.
C'est peut-être au-dessus de votre tête, mais il semble y avoir un moyen direct d'y accéder également en utilisant une chaîne de connexion .
Pourquoi ne pas essayer d'utiliser Python au lieu de C # ou VB? Il a un joli module CSV à importer qui fait tout le gros du travail pour vous.
J'ai dû utiliser un analyseur CSV en .NET pour un projet cet été et me suis installé sur le pilote Microsoft Jet Text. Vous spécifiez un dossier à l'aide d'une chaîne de connexion, puis interrogez un fichier à l'aide d'une instruction SQL Select. Vous pouvez spécifier des types forts à l'aide d'un fichier schema.ini. Je ne l'ai pas fait au début, mais j'obtenais de mauvais résultats là où le type de données n'était pas immédiatement apparent, comme les numéros IP ou une entrée comme "XYQ 3.9 SP1".
Une limitation que j'ai rencontrée est qu'il ne peut pas gérer les noms de colonnes au-dessus de 64 caractères; il tronque. Cela ne devrait pas être un problème, sauf que j'avais affaire à des données d'entrée très mal conçues. Il renvoie un DataSet ADO.NET.
C'était la meilleure solution que j'ai trouvée. Je me méfierais de lancer mon propre analyseur CSV, car je manquerais probablement certains des cas de fin, et je n'ai trouvé aucun autre package d'analyse CSV gratuit pour .NET.
EDIT: De plus, il ne peut y avoir qu'un seul fichier schema.ini par répertoire, je l'ai donc ajouté dynamiquement pour taper fortement les colonnes nécessaires. Il ne tapera fortement que les colonnes spécifiées et en déduira pour tout champ non spécifié. J'ai vraiment apprécié cela, car je travaillais à importer un fichier CSV fluide de plus de 70 colonnes et je ne voulais pas spécifier chaque colonne, uniquement celles qui se comportaient mal.
J'ai tapé du code. Le résultat dans le datagridviewer semblait bon. Il analyse une seule ligne de texte en une liste d'objets.
enum quotestatus
{
none,
firstquote,
secondquote
}
public static System.Collections.ArrayList Parse(string line,string delimiter)
{
System.Collections.ArrayList ar = new System.Collections.ArrayList();
StringBuilder field = new StringBuilder();
quotestatus status = quotestatus.none;
foreach (char ch in line.ToCharArray())
{
string chOmsch = "char";
if (ch == Convert.ToChar(delimiter))
{
if (status== quotestatus.firstquote)
{
chOmsch = "char";
}
else
{
chOmsch = "delimiter";
}
}
if (ch == Convert.ToChar(34))
{
chOmsch = "quotes";
if (status == quotestatus.firstquote)
{
status = quotestatus.secondquote;
}
if (status == quotestatus.none )
{
status = quotestatus.firstquote;
}
}
switch (chOmsch)
{
case "char":
field.Append(ch);
break;
case "delimiter":
ar.Add(field.ToString());
field.Clear();
break;
case "quotes":
if (status==quotestatus.firstquote)
{
field.Clear();
}
if (status== quotestatus.secondquote)
{
status =quotestatus.none;
}
break;
}
}
if (field.Length != 0)
{
ar.Add(field.ToString());
}
return ar;
}
Si vous pouvez garantir qu'il n'y a pas de virgule dans les données, le moyen le plus simple serait probablement d'utiliser String.split .
Par exemple:
String[] values = myString.Split(',');
myObject.StringField = values[0];
myObject.IntField = Int32.Parse(values[1]);
Il existe peut-être des bibliothèques que vous pourriez utiliser pour vous aider, mais c'est probablement aussi simple que possible. Assurez-vous simplement que vous ne pouvez pas avoir de virgules dans les données, sinon vous devrez mieux les analyser.