在将CSV读取到DataTable中时,我试图为似乎无效的布尔值和null值添加选项。例如,一个文件包含类似于以下内容的数据:
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.
如果我手动更改数据并将0
替换为false
,1
替换为true
,则布尔值可以工作,但是会出现类似的错误:{ {1}}
为了使此功能正常工作,我在这里缺少什么吗?还是类型转换器选项仅适用于已知对象?
编辑:
在解析CSV文件时,我无法使用任何预定义的对象模型,因为它们可以包含任意数量的字段。只要存在模式,程序就应该知道如何处理它。模式示例如下所示:
Input string was not in a correct format. Couldn't store <> in MaxDiscount Column. Expected type is Int32.
在这种情况下,架构中的{
"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
}
]
}
将与CSV文件中的列相关。从理论上讲,这将使我能够在运行时解析文件并验证数据类型,而不必每次引入新的CSV布局时都创建一个新的对象模型。
答案 0 :(得分:3)
我认为CsvDataReader
类没有用-GetFieldType
的实现返回typeof(string)
,GetValue
也返回string
,因此尽管它实现了类型的数据访问器方法,它们永远不会被DataTable
类Load
方法调用。
因此没有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
类执行,并将使用所有转换选项。
顺便说一句,实际上不需要显示的布尔值或null值的选项-所有它们都由CsvHelper
默认类型转换器处理。
答案 1 :(得分:1)
如果要指定列和列类型,数据表将以自动转换的类型加载。
使用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; }
}
答案 2 :(得分:1)
[第二次尝试]
只要DataTable
创建了CsvDataReader
的集合并将DataColumns
设置为CsvDataReader
,就可以通过Configuration.Delimiter
将数据加载到Active
对象中逗号,但...布尔字段(BooleanFalseValuesAttribute
)并不是真正的布尔值。
根据我的测试和对文档的理解,只有一种方法来获取正确的数据-通过helper类,该类需要将attributes设置为字段。其中两个非常重要:
BooleanTrueValuesAttribute
用来表示字符串的字符串值 布尔值转换时为false。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; } }
转换时用于表示布尔值true的字符串值。
因此,类的装饰可能类似于:
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>();
方法:
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();
如果我对您的理解很好,您想将数据提取到DataTable对象中……看一下:
Id MaxDiscount Name Active AltId
1 null Foo True ABC123
2 10 Bar False DEF345
结果是:
output$race_standings_table = DT::renderDataTable({
standings %>%
filter(year == input$round,
name..35 == input$race_name)
})