将 CSV 标头与地图类进行比较

如何解决将 CSV 标头与地图类进行比较

我有一个过程,我们编写了一个类来使用 CsvHelper (https://joshclose.github.io/CsvHelper) 将一个大的(ish)CSV 导入我们的应用程序。

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

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

public class VisitExport
{
    public int? Count { get; set; }
    public string CustomerName { get; set; }
    public string CustomerAddress { get; set; }
}

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

public class VisitMap : ClassMap<VisitExport>
{
    public VisitMap()
    {
        Map(m => m.Count).Name("Count");
        Map(m => m.CustomerName).Name("Customer Name");
        Map(m => m.CustomerAddress).Name("Customer Address");
    }
}

这是我用于读取 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;
        }
    }

有没有办法比较 CSV 标题和我的地图?

解决方法

带有标题的 CSV 文件有两种基本情况:缺少 CSV 列和额外的 CSV 列。 CsvHelper 已经检测到第一个,而第二个的检测不是开箱即用的,需要对 CsvReader 进行子类化。

(由于 CsvHelper 按名称将 CSV 列映射到模型属性,因此在 CSV 文件中排列列的顺序不会被视为重大更改。)

请注意,这仅适用于实际包含标题的 CSV 文件。由于您没有设置 CsvConfiguration.HasHeaderRecord = false,因此我认为这适用于您的用例。

关于这两种情况的详细信息如下。

缺少 CSV 列。

目前 CsvHelper 已经 在这种情况下默认抛出异常。当找到未映射的数据模型属性时,调用 CsvConfiguration.HeaderValidated。默认情况下,它设置为 ConfigurationFunctions.HeaderValidated,其当前行为是在有任何未映射的模型属性时抛出 HeaderValidationException。如果您愿意,您可以用自己的逻辑替换或扩展 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 here

额外的 CSV 列。

目前 CsvHelper 不会在发生这种情况时通知应用程序。请参阅 Throw if csv contains unexpected columns #1032 以确认这不是开箱即用的。

GitHub comment 中,用户 leopignataro 建议了一种解决方法,即子类化 CsvReader 并自行添加必要的验证逻辑。但是,评论中显示的版本似乎无法处理重复的列名或嵌入的引用。 CsvHelper 的以下子类应该正确执行此操作。它基于 CsvReader.ValidateHeader(ClassMap map,List<InvalidHeader> invalidHeaders) 中的逻辑。它递归地遍历传入的 ClassMap,尝试找到与每个成员或构造函数参数对应的 CSV 标头,并标记每个映射的索引。之后,如果有任何未映射的标头,则调用提供的 Action<CsvContext,List<string>> OnUnmappedCsvHeaders 以通知应用程序问题并在需要时抛出一些异常:

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

然后在您的代码中,按照您的意愿处理 OnUnmappedCsvHeaders 回调,例如抛出 CsvHelperException 或其他一些自定义异常:

using (var readCsv  = new ValidatingCsvReader(reader,culture)
       {
           OnUnmappedCsvHeaders = (context,headers) => throw new CsvHelperException(context,string.Format("Unmapped CSV headers: \"{0}\"",string.Join(",",headers))),})

演示小提琴:

这可以使用额外的测试,例如用于具有参数化构造函数和附加可变属性的数据模型。

,

在抓到HeaderValidationException之前先抓到CsvHelperException怎么样

catch (HeaderValidationException ex)
{
    var message = ex.Message.Split('\n')[0];
    var currentHeader = ex.Context.Reader.HeaderRecord;

    message += $"{Environment.NewLine}Header:  \"{string.Join(",currentHeader)}\"";
    
    Console.WriteLine(message);
    fileLoadedLink.Text = "Error loading file.";
    viewModel.IsFileLoaded = false;
}
catch (CsvHelperException ex)
{
    Console.WriteLine(ex.InnerException != null ? ex.InnerException.Message : ex.Message);
    fileLoadedLink.Text = "Error loading file.";
    viewModel.IsFileLoaded = false;
}

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。

相关推荐


Selenium Web驱动程序和Java。元素在(x,y)点处不可单击。其他元素将获得点击?
Python-如何使用点“。” 访问字典成员?
Java 字符串是不可变的。到底是什么意思?
Java中的“ final”关键字如何工作?(我仍然可以修改对象。)
“loop:”在Java代码中。这是什么,为什么要编译?
java.lang.ClassNotFoundException:sun.jdbc.odbc.JdbcOdbcDriver发生异常。为什么?
这是用Java进行XML解析的最佳库。
Java的PriorityQueue的内置迭代器不会以任何特定顺序遍历数据结构。为什么?
如何在Java中聆听按键时移动图像。
Java“Program to an interface”。这是什么意思?
Java在半透明框架/面板/组件上重新绘画。
Java“ Class.forName()”和“ Class.forName()。newInstance()”之间有什么区别?
在此环境中不提供编译器。也许是在JRE而不是JDK上运行?
Java用相同的方法在一个类中实现两个接口。哪种接口方法被覆盖?
Java 什么是Runtime.getRuntime()。totalMemory()和freeMemory()?
java.library.path中的java.lang.UnsatisfiedLinkError否*****。dll
JavaFX“位置是必需的。” 即使在同一包装中
Java 导入两个具有相同名称的类。怎么处理?
Java 是否应该在HttpServletResponse.getOutputStream()/。getWriter()上调用.close()?
Java RegEx元字符(。)和普通点?