【问题标题】:NullValues Option Not Working When Loading to DataTable加载到 DataTable 时 NullValues 选项不起作用
【发布时间】:2019-02-06 00:20:21
【问题描述】:

在将 CSV 读入 DataTable 时,我正在尝试为似乎不起作用的布尔值和空值添加选项。例如,包含类似于以下数据的文件:

Id,MaxDiscount,Name,Active,AltId
1,,Foo,1,ABC123
2,10,Bar,0,DEF345

以及以下使用模式文件动态获取我们期望的标头和数据类型的逻辑:

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

这会导致错误String was not recognized as a valid Boolean. Couldn't store &lt;0&gt; in Active Column. Expected type is Boolean.

如果我手动更改数据并将0 替换为false 并将1 替换为true,则布尔值有效,但出现类似错误:Input string was not in a correct format. Couldn't store &lt;&gt; in MaxDiscount Column. Expected type is Int32.

为了让它发挥作用,我这里有什么遗漏吗?还是类型转换器选项仅适用于已知对象?

编辑:

在解析 CSV 文件时,我无法使用任何预定义的对象模型,因为它们可以包含任意数量的字段。只要存在模式,程序就应该知道如何处理它。示例模式如下所示:

{
  "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
    }
  ]
}

在这种情况下,架构中的 Properties 将与 CSV 文件中的列相关。理论上,这将允许我在运行时解析文件并验证数据类型,而不必在每次引入新的 CSV 布局时创建新的对象模型。

【问题讨论】:

  • schema 在哪里/如何定义?
  • 如果您使用可空的 int 版本 GetOptions&lt;int?&gt;() 而不是不可空的版本会怎样?
  • @grek40 模式是从一​​个 JSON 文件中解析出来的,该文件包含有关正在读取的 CSV 的信息。当我使用可为空的 int 选项时,我得到完全相同的错误
  • 您能发布您的架构信息吗?
  • @WaelAbbas Schema 信息已发布,谢谢!

标签: c# .net csvhelper


【解决方案1】:

在我看来CsvDataReader 类是没用的——GetFieldType 的实现返回typeof(string)GetValue 也返回strings,所以虽然它实现了类型化数据访问器方法,但它们永远不会被DataTableLoad 方法。

因此不会发生 CsvHelper 映射 - 转换由 DataTable 使用标准字符串到类型转换器完成。

我建议删除 CsvDataReader 类的使用,并将 dt.Load(dr); 调用替换为以下内容:

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) 方法填充DataRow 值。这样,转换由CsvReader 类执行,并将使用所有转换选项。

顺便说一句,真正需要显示的布尔值或空值选项 - 所有它们都由 CsvHelper 默认类型转换器处理。

【讨论】:

  • 这个完美,比我希望的要复杂一点(总是如此),但正是我需要的结果。
  • 我刚刚浪费了 2 个小时,没吃午饭,因为 CsvDataReader 忽略了 CsvReader 的配置 ....
  • @PanagiotisKanavos :(
【解决方案2】:

来自CsvHelper documentation

如果要指定列和列类型,数据表将加载自动转换的类型。

我看到它在使用CsvDataReader 时忽略了CsvReader 类型转换器选项。

但如果您使用csv.GetRecords,它将使用已定义的类型转换器选项。

List<csvData> result = csv.GetRecords<csvData>().ToList();

您需要为您的 csv 文件设置类,如下所示

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; }
}

【讨论】:

    【解决方案3】:

    [第二次尝试]

    只要DataColumns 的集合是由CsvDataReader 创建的,并且Configuration.Delimiter 设置为逗号,我就可以通过CsvDataReader 将数据加载到DataTable 对象中,但是...布尔字段(@ 987654329@) 并不是真正的布尔值。

    根据我的测试和我对文档的理解,只有一种方法可以获取正确的数据 - 通过帮助程序类,它需要将 attributes 设置为字段。其中两个非常重要:

    BooleanFalseValuesAttribute 用于表示一个字符串值 转换时的布尔值 false。 BooleanTrueValuesAttribute 转换时用于表示布尔值 true 的字符串值。

    所以,类的装饰可能是这样的:

    public class MyData
    {
        [Name("Id")]
        public int Id { get; set; }
        [Name("MaxDiscount")]
        public int? MaxDiscount { get; set; }
        [Name("Name")]
        public string Name { get; set; }
        [Name("Active")]
        [BooleanTrueValues("1")]
        [BooleanFalseValues("0")]
        public bool? Active { get; set; }
        [Name("AltId")]
        public string AltId { get; set; }
    }
    

    还有帮助类,它映射字段:

    public class MyDataMapper: ClassMap<MyData>
    {
        public MyDataMapper()
        {
            Map(m => m.Id);
            Map(m => m.MaxDiscount);
            Map(m => m.Name);
            Map(m => m.Active);
            Map(m => m.AltId);
        }
    }
    

    然后我尝试设置配置:

    csv.Configuration.RegisterClassMap<MyDataMapper>();
    

    能够通过CsvDataReader 对象将数据抓取到DataTable,但是......没有成功:(

    似乎CsvDataReader 出于某种原因忽略了配置(或者我无法成功设置它)。

    每当需要映射字段时,文档说获取数据的正确方法是使用GetRecords&lt;T&gt; 方法:

    var records = csv.GetRecords<Foo>();
    

    见:Mapping properties

    如果我理解你的话,你想将数据提取到 DataTable 对象中......看看这个:

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

    结果是:

    Id MaxDiscount Name Active AltId
    1  null        Foo  True   ABC123 
    2  10          Bar  False  DEF345 
    

    【讨论】:

    • 要重现该问题,您必须手动添加数据表列,并为其指定数据类型,如 Active 是布尔值。
    • @WaelAbbas,感谢您的宝贵意见。我已经更新了我的答案。请看一下。
    • 这个答案很好,但我忽略了一个重要的细节,那就是我不能使用预定义的对象。我正在编写的应用程序的目的是接收任何 CSV 文件并将其解析为数据表。我正在相应地更新我的问题
    • @DrydenLong,我想,CsvDataReader 做不到。请阅读 github“论坛”以了解报告的问题:GetValue of CsvDataReader doesnt support TypeConverter 以及 Ivan Stoev 的回答。
    猜你喜欢
    • 2017-09-15
    • 2023-03-11
    • 2018-02-05
    • 2013-08-05
    • 2014-08-27
    • 1970-01-01
    • 1970-01-01
    • 2014-08-25
    • 1970-01-01
    相关资源
    最近更新 更多