Lors de la lecture d'un CSV dans un DataTable, j'essaie d'ajouter des options pour les valeurs booléennes et nulles qui ne semblent pas fonctionner. Par exemple, un fichier contenant des données similaires à:
{ "type": "Part", "bucket": "s3Bucket", "prefix": "prefix/of/datafile", "targetDirectory": "..\\path\\to\\working\\dir", "delimiter": ",", "properties": [ { "name": "Id", "type": "System.String", "required": true, "nullable": false, "isId": true, "defaultValue": null, "minLength": 6, "maxLength": 8 }, { "name": "MaxDiscount", "type": "System.Int32", "required": true, "nullable": true, "isId": false, "defaultValue": null, "minLength": -1, "maxLength": -1 }, { "name": "Name", "type": "System.String", "required": true, "nullable": false, "isId": false, "defaultValue": null, "minLength": 1, "maxLength": 127 }, { "name": "Active", "type": "System.Boolean", "required": true, "nullable": false, "isId": false, "defaultValue": null, "minLength": 1, "maxLength": 1 }, { "name": "AltId", "type": "System.String", "required": true, "nullable": true, "isId": false, "defaultValue": null, "minLength": 1, "maxLength": 127 } ] }
Et la logique suivante qui utilise un fichier de schéma pour obtenir dynamiquement les en-têtes et les types de données que nous attendons:
var dt = new DataTable(); using (var reader = new StreamReader(file.FullName)) using (var csv = new CsvReader(reader)) { csv.Configuration.HasHeaderRecord = true; csv.Configuration.IgnoreQuotes = false; csv.Configuration.TypeConverterOptionsCache.GetOptions<int>().NullValues.Add(string.Empty); csv.Configuration.TypeConverterOptionsCache.GetOptions<bool>().BooleanFalseValues.Add("0"); csv.Configuration.TypeConverterOptionsCache.GetOptions<bool>().BooleanTrueValues.Add("1"); using (var dr = new CsvDataReader(csv)) { foreach (var p in schema.Properties) { var type = Type.GetType(p.Type, true, true); var dc = new DataColumn { ColumnName = p.Name, Unique = p.IsId, AllowDBNull = p.Nullable, DataType = type }; dt.Columns.Add(dc); } dt.Load(dr); } }
Cela conduit à l'erreur La chaîne n'a pas été reconnue comme un booléen valide. Impossible de stocker dans la colonne active. Le type attendu est booléen.
Si je change manuellement les données et remplacez 0
par false
et 1
avec true
, alors les valeurs booléennes fonctionnent, mais j'obtiens une erreur similaire: La chaîne d'entrée n'était pas dans un format correct. Impossible de stocker dans la colonne MaxDiscount. Le type attendu est Int32.
Y a-t-il quelque chose qui me manque ici pour que cela fonctionne? Ou les options du convertisseur de type ne fonctionnent-elles que sur des objets connus?
MODIFIER:
Je ne peux pas utiliser de modèle d'objet prédéfini lors de l'analyse des fichiers CSV car ils peuvent contenir n'importe quel nombre des champs. Tant qu'un schéma existe, le programme doit savoir comment le gérer. Un exemple de schéma serait quelque chose comme le suivant:
Id,MaxDiscount,Name,Active,AltId 1,,Foo,1,ABC123 2,10,Bar,0,DEF345
Dans ce cas, les Propriétés
dans le schéma se rapporteraient aux colonnes du fichier CSV. Ceci, en théorie, me permettrait d'analyser les fichiers et de valider les types de données au moment de l'exécution, plutôt que d'avoir à créer un nouveau modèle d'objet chaque fois qu'une nouvelle mise en page CSV est introduite.
3 Réponses :
Depuis la Documentation CsvHelper
Si vous souhaitez spécifier des colonnes et des types de colonnes, la table de données sera chargée avec les types automatiquement convertis.
Ce que je vois, c'est ignorer les options de conversion de type CsvReader
lors de l'utilisation de CsvDataReader
.
Mais si vous utilisez csv. GetRecords
il utilisera des options de conversion de type défini.
public class csvData { public int Id { get; set; } public string MaxDiscount { get; set; } public string Name { get; set; } public bool Active { get; set; } public string AltId { get; set; } }
Vous devrez avoir comme classe pour votre fichier csv comme ci-dessous
List<csvData> result = csv.GetRecords<csvData>().ToList();
[Deuxième essai]
J'ai pu charger des données dans l'objet DataTable
via CsvDataReader
tant que la collection de DataColumns
a été créé par CsvDataReader
et Configuration.Delimiter
a été défini sur virgule, mais ... champ booléen ( Active
) n’était pas Ce n'est pas vraiment booléen.
Selon mes tests et ma compréhension de la documentation, il n'y a qu'une seule façon d'obtenir des données appropriées - via la classe d'assistance, qui doit définir attributs aux champs. Deux d'entre eux sont très importants:
BooleanFalseValuesAttribute
Les valeurs de chaîne utilisées pour représenter un boolean false lors de la conversion.BooleanTrueValuesAttribute
Les valeurs de chaîne utilisées pour représenter un booléen true lors de la conversion.
Ainsi, la décoration de la classe peut ressembler à:
Id MaxDiscount Name Active AltId 1 null Foo True ABC123 2 10 Bar False DEF345
Et la classe d'assistance, qui mappe les champs:
List<MyData> records = null; using (var reader = new StreamReader(myfile)) using (var csv = new CsvReader(reader)) { csv.Configuration.HasHeaderRecord = true; csv.Configuration.IgnoreQuotes = false; csv.Configuration.Delimiter = ","; csv.Configuration.RegisterClassMap<MyDataMapper>(); records = csv.GetRecords<MyData>().ToList(); dt = records.Select(x=>dt.LoadDataRow(new object[] { x.Id, x.MaxDiscount, x.Name, x.Active, x.AltId },false)) .CopyToDataTable(); dt.Dump();
pour reproduire le problème, vous devez ajouter manuellement des colonnes datables et lui donner un type de données tel que Active
est booléen.
@WaelAbbas, merci pour votre précieux commentaire. J'ai mis à jour ma réponse. S'il vous plaît, jetez un oeil.
Cette réponse est excellente, mais j'ai négligé de laisser de côté un détail important et c'est que je ne peux pas utiliser d'objets prédéfinis. Le but de l'application que j'écris est de prendre n'importe quel fichier CSV et de l'analyser dans un datatable. Je suis en train de mettre à jour ma question en conséquence
@DrydenLong, je pense que cela ne peut pas être fait avec CsvDataReader
. Veuillez lire le "forum" de github pour les problèmes signalés: GetValue of CsvDataReader ne prend pas en charge TypeConverter et réponse également Ivan Stoev.
À mon avis, la classe Ainsi, aucun mappage Je suggérerais de supprimer l'utilisation de Essentiellement préparer le mappage de type de colonne et en utilisant la méthode Btw, aucune des options affichées pour les valeurs booléennes ou nulles n'est vraiment nécessaire - tout ce qu'elles sont gérés par les convertisseurs de type par défaut CsvDataReader
est inutile - l'implémentation de GetFieldType
renvoie typeof (string)
, GetValue code > renvoie également des
string
s, donc bien qu'il implémente les méthodes d'accès aux données typées, elles ne sont jamais appelées par la méthode DataTable
class Load
. CsvHelper
ne se produit - la conversion est effectuée par DataTable
en utilisant une chaîne standard pour taper des convertisseurs. CsvDataReader
et en remplaçant l'appel dt.Load (dr);
par quelque chose comme ceci: static void Load(DataTable dt, CsvReader csv)
{
if (csv.Configuration.HasHeaderRecord)
{
if (!csv.Read()) return;
csv.ReadHeader();
}
var valueTypes = new Type[dt.Columns.Count];
for (int i = 0; i < valueTypes.Length; i++)
{
var dc = dt.Columns[i];
var type = dc.DataType;
if (dc.AllowDBNull && type.IsValueType)
type = typeof(Nullable<>).MakeGenericType(type);
valueTypes[i] = type;
}
var valueBuffer = new object[valueTypes.Length];
dt.BeginLoadData();
while (csv.Read())
{
for (int i = 0; i < valueBuffer.Length; i++)
valueBuffer[i] = csv.GetField(valueTypes[i], i);
dt.LoadDataRow(valueBuffer, true);
}
dt.EndLoadData();
}
CsvReader.GetField (type, index)
pour remplir les valeurs de DataRow
. De cette façon, la conversion est effectuée par la classe CsvReader
et utilisera toutes les options de conversion. CsvHelper
.
C'est parfait, un peu plus compliqué que ce que j'espérais (ça l'est toujours) mais exactement les résultats dont j'avais besoin.
Je viens de perdre 2 heures et j'ai sauté le déjeuner car CsvDataReader ignore la configuration de CsvReader ....
@PanagiotisKanavos :(
où / comment le
schéma
est-il défini?Que se passe-t-il si vous utilisez la version int nullable
GetOptions ()
au lieu de la version non nullable?Le schéma @ grek40 est analysé à partir d'un fichier JSON qui contient des informations sur le CSV en cours de lecture. Lorsque j'utilise l'option nullable int, j'obtiens exactement la même erreur
Pouvez-vous publier vos informations de schéma?
Les informations sur le schéma @WaelAbbas ont été publiées, merci!
@DrydenLong vérifiez ma réponse et si vous avez besoin d'une datatable comme source de données, vous pouvez la remplacer par
List