C# 比较CSV头和映射类
我有一个过程,我们编写了一个类,使用CsvHelper()将一个大型(ish)CSV导入到我们的应用程序中 我想将标题与地图进行比较,以确保标题的完整性。我们从第三方获得CSV文件,我想确保它不会随时间而改变,我认为最好的方法是将其与地图进行比较 我们有一个这样设置的类(修剪): 及其对应的贴图(也经过修剪):C# 比较CSV头和映射类,c#,csv,csvhelper,C#,Csv,Csvhelper,我有一个过程,我们编写了一个类,使用CsvHelper()将一个大型(ish)CSV导入到我们的应用程序中 我想将标题与地图进行比较,以确保标题的完整性。我们从第三方获得CSV文件,我想确保它不会随时间而改变,我认为最好的方法是将其与地图进行比较 我们有一个这样设置的类(修剪): 及其对应的贴图(也经过修剪): 公共类访问映射:类映射 { 公众访问地图() { Map(m=>m.Count).Name(“Count”); Map(m=>m.CustomerName).Name(“客户名称”);
公共类访问映射:类映射
{
公众访问地图()
{
Map(m=>m.Count).Name(“Count”);
Map(m=>m.CustomerName).Name(“客户名称”);
映射(m=>m.CustomerAddress).Name(“客户地址”);
}
}
这是我用来读取CSV文件的代码,它非常有效。我已经为错误准备了一个try-catch,但是理想的情况是,如果它特别因为头部未匹配而失败,我会特别处理它
private void fileLoadedLink_LinkClicked(object sender, LinkLabelLinkClickedEventArgs e)
{
try
{
var filePath = string.Empty;
data = new List<VisitExport>();
using (OpenFileDialog openFileDialog = new OpenFileDialog())
{
openFileDialog.InitialDirectory = new KnownFolder(KnownFolderType.Downloads).Path;
openFileDialog.Filter = "csv files (*.csv)|*.csv";
openFileDialog.FilterIndex = 2;
openFileDialog.RestoreDirectory = true;
if (openFileDialog.ShowDialog() == DialogResult.OK)
{
filePath = openFileDialog.FileName;
var fileStream = openFileDialog.OpenFile();
var culture = CultureInfo.GetCultureInfo("en-GB");
using (StreamReader reader = new StreamReader(fileStream))
using (var readCsv = new CsvReader(reader, culture))
{
var map = new VisitMap();
readCsv.Context.RegisterClassMap(map);
var fileContent = readCsv.GetRecords<VisitExport>();
data = fileContent.ToList();
fileLoadedLink.Text = filePath;
viewModel.IsFileLoaded = true;
}
}
}
}
catch (CsvHelperException ex)
{
Console.WriteLine(ex.InnerException != null ? ex.InnerException.Message : ex.Message);
fileLoadedLink.Text = "Error loading file.";
viewModel.IsFileLoaded = false;
}
}
private void fileLoadedLink\u LinkClicked(对象发送方,LinkLabelLinkClickedEventArgs e)
{
尝试
{
var filePath=string.Empty;
数据=新列表();
使用(OpenFileDialog OpenFileDialog=新建OpenFileDialog())
{
openFileDialog.InitialDirectory=新的KnownFolder(KnownFolderType.Downloads).Path;
openFileDialog.Filter=“csv文件(*.csv)|*.csv”;
openFileDialog.FilterIndex=2;
openFileDialog.RestoreDirectory=true;
if(openFileDialog.ShowDialog()==DialogResult.OK)
{
filePath=openFileDialog.FileName;
var fileStream=openFileDialog.OpenFile();
var culture=CultureInfo.GetCultureInfo(“en GB”);
使用(StreamReader=newstreamreader(fileStream))
使用(var readCsv=new CsvReader(读取器,区域性))
{
var map=new VisitMap();
readCsv.Context.RegisterClassMap(map);
var fileContent=readCsv.GetRecords();
data=fileContent.ToList();
fileLoadedLink.Text=文件路径;
viewModel.isfileload=true;
}
}
}
}
捕获(CsvHelperException ex)
{
Console.WriteLine(ex.InnerException!=null?ex.InnerException.Message:ex.Message);
fileLoadedLink.Text=“加载文件时出错。”;
viewModel.isfileload=false;
}
}
有没有办法比较Csv标题和我的地图?在捕获
CsvHelperException
之前捕获HeaderValidationException
怎么样
catch(HeaderValidationException例外)
{
var message=ex.message.Split('\n')[0];
var currentHeader=ex.Context.Reader.HeaderRecord;
message+=$“{Environment.NewLine}头:\“{string.Join(“,”,currentHeader)}\”;
控制台写入线(消息);
fileLoadedLink.Text=“加载文件时出错。”;
viewModel.isfileload=false;
}
捕获(CsvHelperException ex)
{
Console.WriteLine(ex.InnerException!=null?ex.InnerException.Message:ex.Message);
fileLoadedLink.Text=“加载文件时出错。”;
viewModel.isfileload=false;
}
对于带有标题的CSV文件,有两种基本情况:缺少CSV列和额外的CSV列。第一个已由CsvHelper
检测,而第二个的检测不是现成的,需要对CsvReader
进行子类化
(由于CsvHelper按名称将CSV列映射到模型属性,因此在CSV文件中排列列的顺序不会被视为破坏性更改。)
请注意,这仅适用于实际包含标题的CSV文件。由于您没有设置CsvConfiguration.HasHeaderRecord=false
,我假设这适用于您的用例
这两起案件的详细情况如下
缺少CSV列。
目前,CsvHelper已经在这种情况下默认抛出异常。找到未映射的数据模型属性时,将调用。默认情况下,如果存在任何未映射的模型属性,则将其设置为当前行为是抛出一个。如果愿意,您可以使用自己的逻辑替换或扩展HeaderValidated
:
var culture = CultureInfo.GetCultureInfo("en-GB");
var config = new CsvConfiguration (culture)
{
HeaderValidated = (args) =>
{
// Add additional logic as required here
ConfigurationFunctions.HeaderValidated(args);
},
};
using (var readCsv = new CsvReader(reader, config))
{
// Remainder unchanged
演示小提琴#1
额外的CSV列。
当前,CsvHelper
不会在发生这种情况时通知应用程序。查看哪一项确认这不是开箱即用的
在一个示例中,用户建议一种变通方法,即自己创建子类并添加必要的验证逻辑。但是,注释中显示的版本似乎无法处理重复的列名或嵌入的引用。CsvHelper
的以下子类应正确执行此操作。它基于中的逻辑。它递归地遍历传入的类映射
,尝试查找对应于每个成员或构造函数参数的CSV头,并标记映射的每个成员或构造函数的索引。之后,如果存在任何未映射的头,将调用提供的操作onUnmappedCvsHeaders
,以通知应用程序该问题,并根据需要引发一些异常:
public class ValidatingCsvReader : CsvReader
{
public ValidatingCsvReader(TextReader reader, CultureInfo culture, bool leaveOpen = false) : this(new CsvParser(reader, culture, leaveOpen)) { }
public ValidatingCsvReader(TextReader reader, CsvConfiguration configuration) : this(new CsvParser(reader, configuration)) { }
public ValidatingCsvReader(IParser parser) : base(parser) { }
public Action<CsvContext, List<string>> OnUnmappedCsvHeaders { get; set; }
public override void ValidateHeader(Type type)
{
base.ValidateHeader(type);
var headerRecord = HeaderRecord;
var mapped = new BitArray(headerRecord.Length);
var map = Context.Maps[type];
FlagMappedHeaders(map, mapped);
var unmappedHeaders = Enumerable.Range(0, headerRecord.Length).Where(i => !mapped[i]).Select(i => headerRecord[i]).ToList();
if (unmappedHeaders.Count > 0)
{
OnUnmappedCsvHeaders?.Invoke(Context, unmappedHeaders);
}
}
protected virtual void FlagMappedHeaders(ClassMap map, BitArray mapped)
{
// Logic adapted from https://github.com/JoshClose/CsvHelper/blob/0d753ff09294b425e4bc5ab346145702eeeb1b6f/src/CsvHelper/CsvReader.cs#L157
// By https://github.com/JoshClose
foreach (var parameter in map.ParameterMaps)
{
if (parameter.Data.Ignore)
continue;
if (parameter.Data.IsConstantSet)
// If ConvertUsing and Constant don't require a header.
continue;
if (parameter.Data.IsIndexSet && !parameter.Data.IsNameSet)
// If there is only an index set, we don't want to validate the header name.
continue;
if (parameter.ConstructorTypeMap != null)
{
FlagMappedHeaders(parameter.ConstructorTypeMap, mapped);
}
else if (parameter.ReferenceMap != null)
{
FlagMappedHeaders(parameter.ReferenceMap.Data.Mapping, mapped);
}
else
{
var index = GetFieldIndex(parameter.Data.Names.ToArray(), parameter.Data.NameIndex, true);
if (index >= 0)
mapped.Set(index, true);
}
}
foreach (var memberMap in map.MemberMaps)
{
if (memberMap.Data.Ignore || !CanRead(memberMap))
continue;
if (memberMap.Data.ReadingConvertExpression != null || memberMap.Data.IsConstantSet)
// If ConvertUsing and Constant don't require a header.
continue;
if (memberMap.Data.IsIndexSet && !memberMap.Data.IsNameSet)
// If there is only an index set, we don't want to validate the header name.
continue;
var index = GetFieldIndex(memberMap.Data.Names.ToArray(), memberMap.Data.NameIndex, true);
if (index >= 0)
mapped.Set(index, true);
}
foreach (var referenceMap in map.ReferenceMaps)
{
if (!CanRead(referenceMap))
continue;
FlagMappedHeaders(referenceMap.Data.Mapping, mapped);
}
}
}
演示小提琴:
这可能需要额外的测试,例如,对于具有参数化构造函数和其他可变属性的数据模型。我在这里收到的最佳答案。非常感谢你,
var culture = CultureInfo.GetCultureInfo("en-GB");
var config = new CsvConfiguration (culture)
{
HeaderValidated = (args) =>
{
// Add additional logic as required here
ConfigurationFunctions.HeaderValidated(args);
},
};
using (var readCsv = new CsvReader(reader, config))
{
// Remainder unchanged
public class ValidatingCsvReader : CsvReader
{
public ValidatingCsvReader(TextReader reader, CultureInfo culture, bool leaveOpen = false) : this(new CsvParser(reader, culture, leaveOpen)) { }
public ValidatingCsvReader(TextReader reader, CsvConfiguration configuration) : this(new CsvParser(reader, configuration)) { }
public ValidatingCsvReader(IParser parser) : base(parser) { }
public Action<CsvContext, List<string>> OnUnmappedCsvHeaders { get; set; }
public override void ValidateHeader(Type type)
{
base.ValidateHeader(type);
var headerRecord = HeaderRecord;
var mapped = new BitArray(headerRecord.Length);
var map = Context.Maps[type];
FlagMappedHeaders(map, mapped);
var unmappedHeaders = Enumerable.Range(0, headerRecord.Length).Where(i => !mapped[i]).Select(i => headerRecord[i]).ToList();
if (unmappedHeaders.Count > 0)
{
OnUnmappedCsvHeaders?.Invoke(Context, unmappedHeaders);
}
}
protected virtual void FlagMappedHeaders(ClassMap map, BitArray mapped)
{
// Logic adapted from https://github.com/JoshClose/CsvHelper/blob/0d753ff09294b425e4bc5ab346145702eeeb1b6f/src/CsvHelper/CsvReader.cs#L157
// By https://github.com/JoshClose
foreach (var parameter in map.ParameterMaps)
{
if (parameter.Data.Ignore)
continue;
if (parameter.Data.IsConstantSet)
// If ConvertUsing and Constant don't require a header.
continue;
if (parameter.Data.IsIndexSet && !parameter.Data.IsNameSet)
// If there is only an index set, we don't want to validate the header name.
continue;
if (parameter.ConstructorTypeMap != null)
{
FlagMappedHeaders(parameter.ConstructorTypeMap, mapped);
}
else if (parameter.ReferenceMap != null)
{
FlagMappedHeaders(parameter.ReferenceMap.Data.Mapping, mapped);
}
else
{
var index = GetFieldIndex(parameter.Data.Names.ToArray(), parameter.Data.NameIndex, true);
if (index >= 0)
mapped.Set(index, true);
}
}
foreach (var memberMap in map.MemberMaps)
{
if (memberMap.Data.Ignore || !CanRead(memberMap))
continue;
if (memberMap.Data.ReadingConvertExpression != null || memberMap.Data.IsConstantSet)
// If ConvertUsing and Constant don't require a header.
continue;
if (memberMap.Data.IsIndexSet && !memberMap.Data.IsNameSet)
// If there is only an index set, we don't want to validate the header name.
continue;
var index = GetFieldIndex(memberMap.Data.Names.ToArray(), memberMap.Data.NameIndex, true);
if (index >= 0)
mapped.Set(index, true);
}
foreach (var referenceMap in map.ReferenceMaps)
{
if (!CanRead(referenceMap))
continue;
FlagMappedHeaders(referenceMap.Data.Mapping, mapped);
}
}
}
using (var readCsv = new ValidatingCsvReader(reader, culture)
{
OnUnmappedCsvHeaders = (context, headers) => throw new CsvHelperException(context, string.Format("Unmapped CSV headers: \"{0}\"", string.Join(",", headers))),
})