NullValues选项不工作当加载到数据表

问题描述 投票:1回答:3

当读取一个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 <0> in Active Column. Expected type is Boolean.

如果我手动更改数据,并与0false1取代true,我那么布尔值的工作,但我得到一个类似的错误:Input string was not in a correct format. Couldn't store <> 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布局推出的每一次创建一个新的对象模型。

c# .net csvhelper
3个回答
4
投票

在我看来,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默认类型转换器处理。


1
投票

CsvHelper documentation

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

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

但是,如果你使用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; }
}

1
投票

[第二试]

我能为DataTable的收集是由CsvDataReader创建和DataColumns设置为逗号通过CsvDataReader只要将数据加载到Configuration.Delimiter对象,但...布尔场(Active)是不是真的布尔值。

按我的测试,我的文档的理解,只有一个得到正确的数据的方式 - 通过辅助类,这需要设置attributes到各个领域。其中两个是非常重要的:

BooleanFalseValuesAttribute转换时用于表示一个布尔假字符串值。 BooleanTrueValuesAttribute转换时用于表示一个布尔真字符串值。

所以,类的装饰可能看起来像:

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

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

似乎CsvDataReader忽略了某些原因配置(或者我没能成功地将它设置)。

每当有一个需要映射领域,文件说,抓住数据的正确方法是使用GetRecords<T>方法:

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 
© www.soinside.com 2019 - 2024. All rights reserved.