C# 比较CSV头和映射类

C# 比较CSV头和映射类,c#,csv,csvhelper,C#,Csv,Csvhelper,我有一个过程,我们编写了一个类,使用CsvHelper()将一个大型(ish)CSV导入到我们的应用程序中 我想将标题与地图进行比较,以确保标题的完整性。我们从第三方获得CSV文件,我想确保它不会随时间而改变,我认为最好的方法是将其与地图进行比较 我们有一个这样设置的类(修剪): 及其对应的贴图(也经过修剪): 公共类访问映射:类映射 { 公众访问地图() { Map(m=>m.Count).Name(“Count”); Map(m=>m.CustomerName).Name(“客户名称”);

我有一个过程,我们编写了一个类,使用CsvHelper()将一个大型(ish)CSV导入到我们的应用程序中

我想将标题与地图进行比较,以确保标题的完整性。我们从第三方获得CSV文件,我想确保它不会随时间而改变,我认为最好的方法是将其与地图进行比较

我们有一个这样设置的类(修剪):

及其对应的贴图(也经过修剪):

公共类访问映射:类映射
{
公众访问地图()
{
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))),
       })