1
votes

L'option NullValues ​​ne fonctionne pas lors du chargement dans DataTable

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.


6 commentaires

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


3 Réponses :


1
votes

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();


0 commentaires

1
votes

[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();


4 commentaires

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.



4
votes

À mon avis, la classe 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 .

Ainsi, aucun mappage CsvHelper ne se produit - la conversion est effectuée par DataTable en utilisant une chaîne standard pour taper des convertisseurs.

Je suggérerais de supprimer l'utilisation de 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();
}

Essentiellement préparer le mappage de type de colonne et en utilisant la méthode 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.

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 CsvHelper .


3 commentaires

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 :(