使用 CSVhelper C# 合并具有不同 headers 的 CSV 文件

Merging CSV files with different headers using CSVhelper C#

尝试使用 CSVhelper 将目录中的多个 .csv 文件合并为一个 .csv 文件时。目录中有 50 个 .csv 文件,在这 50 个文件中有两组文件结构,一组有 7 列,一组有 6 列。每个文件都有完全相同的前 5 headers 但是取决于文件最后两列将改变。

CSV 文件格式 1 示例:

CSV 文件格式 2 示例:

目录中的每个文件都将包含这些结构中的任何一个,并在列中包含不同的数据。新文件的输出将包含来自所有栏操作、代码和错误消息的数据。如果我只使用具有示例 1 结构的文件,则文件会完美地组合在一起。但是,如果我包含具有这两种结构的文件并尝试在我的新文件中使用示例 2 中的 'ErrorIPAddress',我会收到以下错误:

An unhandled exception of type 'CsvHelper.TypeConversion.CsvTypeConverterException' occurred in CsvHelper.dll

这一行:`IEnumerable dataRecord = reader.GetRecords().ToList();

我的问题是:如何使用一个文件中不在另一个文件中的列?我已经尝试用以下内容映射它:Map(m => m.ErrorIPAddress).Index(5); 我相信这是导致我出现问题的行,就好像我将其注释掉错误不会持续存在但是显然我不会将我需要的数据获取到新的.csv。如果我尝试按名称映射:Map( m => m.ErrorIPAddress ).Name( "ErrorIPAddress" ); 我收到错误消息,指出 ErrorIPAddress 不在 .csv 文件中,因为并非所有文件都有该列。

输出 .csv 格式:

最后一列将由格式 2 中的 ErrorIPAddress 列生成。

您不需要外部库。使用下面的代码

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Globalization;
using System.Data;
using System.Data.OleDb;
using System.IO;


namespace ConsoleApplication1
{
    class Program
    {
        const string FOLDER = @"c:\temp\test";
        static void Main(string[] args)
        {
            CSVReader reader = new CSVReader();

            //table containing merged csv files
            DataTable dt = new DataTable();

            //get csv files one at a time
            foreach (string file in Directory.GetFiles(FOLDER, "*.csv"))
            {
                //read csv file into a new dataset
                DataSet ds = reader.ReadCSVFile(file, true);
                //datatable containing new csv file
                DataTable dt1 = ds.Tables[0];

                //add new columns to datatable dt if doesn't exist
                foreach(DataColumn col in dt1.Columns.Cast<DataColumn>())
                {
                    //test if column exists and add if it doesn't
                    if (!dt.Columns.Contains(col.ColumnName))
                    {
                        dt.Columns.Add(col.ColumnName, typeof(string));
                    }
                }

                //array of column names in new table
                string[] columnNames = dt1.Columns.Cast<DataColumn>().Select(x => x.ColumnName).ToArray();

                //copy row from dt1 into dt
                foreach(DataRow row in dt1.AsEnumerable())
                {
                    //add new row to table dt
                    DataRow newRow = dt.Rows.Add();

                    //add data from dt1 into dt
                    for(int i = 0; i < columnNames.Count(); i++)
                    {
                        newRow[columnNames[i]] = row[columnNames[i]];
                    }
                }
            }

        }
    }
    public class CSVReader
    {

        public DataSet ReadCSVFile(string fullPath, bool headerRow)
        {

            string path = fullPath.Substring(0, fullPath.LastIndexOf("\") + 1);
            string filename = fullPath.Substring(fullPath.LastIndexOf("\") + 1);
            DataSet ds = new DataSet();

            try
            {

                //read csv file using OLEDB Net Library
                if (File.Exists(fullPath))
                {
                    string ConStr = string.Format("Provider=Microsoft.Jet.OLEDB.4.0;Data Source={0}" + ";Extended Properties=\"Text;HDR={1};FMT=Delimited\\"", path, headerRow ? "Yes" : "No");
                    string SQL = string.Format("SELECT * FROM {0}", filename);
                    OleDbDataAdapter adapter = new OleDbDataAdapter(SQL, ConStr);
                    adapter.Fill(ds, "TextFile");
                    ds.Tables[0].TableName = "Table1";
                }

                //replace spaces in column names with underscore
                foreach (DataColumn col in ds.Tables["Table1"].Columns)
                {
                    col.ColumnName = col.ColumnName.Replace(" ", "_");
                }
            }

            catch (Exception ex)
            {
                Console.WriteLine(ex.Message);
            }
            return ds;
        }
    }
}

我假设您使用的是单个 class 定义,所有字段看起来像这样:

public class StudentWebAccess
{
    public int StudentID { get; set; }
    public string Gender { get; set; }
    public int Grade { get; set; }        
    public int IPAddress { get; set; } // Also ErrorIPAddress?
    public DateTime DateTime { get; set; }
    public string Action { get; set; }
    public string Code { get; set; } // Also ErrorMessage?
}

因此,为了读取文件格式 2,您使用的是 CsvClassMap,但没有正确匹配属性和字段名称。它应该看起来像这样:

public class CsvFile2Map : CsvClassMap<StudentWebAccess>
{
    public CsvFile2Map()
    {            
        Map(m => m.IPAddress).Name("ErrorIPAddress");
        Map(m => m.Code).Name("ErrorMessage");
    }
}

如果您的 class 文件使用 ErrorIPAddress 而不是 IPAddress,您必须反转映射。

Map(m => m.ErrorIPAddress).Name("IPAddress");