CSVHelper 使用每个文件上的多个映射解析多个 CSV

Posted

技术标签:

【中文标题】CSVHelper 使用每个文件上的多个映射解析多个 CSV【英文标题】:CSVHelper Parsing Multiple CSVs using Multiple Mappings on each File 【发布时间】:2022-01-12 01:30:05 【问题描述】:

我正在尝试读取多个 CSV 文件,但在读取 CSV 文件之前,我不知道需要哪个 Classmap。我还需要使用这些映射来解释多个不同的标头名称。我的索引中目前有以下代码。有没有办法检查第一个字段“csv.GetField(0)”与每行的 3 个映射中的所有值,然后将该值添加到我的一个类列表中,然后添加到数据库中。我目前在我的 while 循环中收到一个错误,说列表中不存在三种记录类型之一,因为我认为它使用单个映射而不是多个映射会卡住

公共类 IndexModel : PageModel 私有只读 ClientContext _context; 公共 IndexModel(ClientContext 上下文) _context = 上下文;

    [BindProperty]
    public FileUpload fileUpload  get; set; 

    //create new lists for all class data types to add to database
    public List<Client> clients = new List<Client>();
    public List<Demographic> demographics = new List<Demographic>();
    public List<ReturnData> returnDatas = new List<ReturnData>();

    public void OnGet()
    
        ViewData["SuccessMessage"] = "Upload necessary files (MAX OF 4 FILES)";
    
    public ActionResult OnPostUpload(FileUpload fileUpload)
    
        //counter
        int i = 1;

        //type of Record
        RecordType type = RecordType.None;

        //for each file in the form files
        foreach (var file in fileUpload.FormFiles)
        
            //display filenames
            ViewData[i.ToString()] = file.FileName;
            i++;

            var config = new CsvConfiguration(CultureInfo.InvariantCulture)
            
                MissingFieldFound = null,
                HeaderValidated = null,
                IgnoreBlankLines = true,
                UseNewObjectForNullReferenceMembers = false
            ;

            //use streamReader and csvHelper to pull records from file
            using var stream = new MemoryStream();
            using var writer = new StreamWriter(stream);
            using var sreader = new StreamReader(file.OpenReadStream());
            using (var csv = new CsvReader(sreader, config))
            
                string[] headerRow = csv.HeaderRecord;

                //add all context mappings to interpret mutliple different header names
                csv.Context.RegisterClassMap<ClientMap>();
                csv.Context.RegisterClassMap<DemoMap>();
                csv.Context.RegisterClassMap<DataMap>();

                //grab records and add them to class lists
                while (csv.Read())
                
                    clients.Add(csv.GetRecord<Client>());
                    demographics.Add(csv.GetRecord<Demographic>());
                    returnDatas.Add(csv.GetRecord<ReturnData>());
                    

                
            
        

        foreach (var client in clients)
        
            _context.Client.Add(client);
            _context.SaveChanges();
        

        foreach (var demographic in demographics)
        
            _context.Demographic.Add(demographic);
            _context.SaveChanges();
        

        foreach (var returnData in returnDatas)
        
            _context.ReturnData.Add(returnData);
            _context.SaveChanges();
        

        //Save database changes
        _context.SaveChanges();

        //Process uploaded files
        ViewData["SuccessMessage"] = fileUpload.FormFiles.Count.ToString() + " file(s) uploaded!";
        var DropDownAndCheckBoxCount = i;
        return Page();
    
    public class FileUpload
    
        [Required]
        [Display(Name = "File")]
        public List<IFormFile> FormFiles  get; set;  // convert to list
        public string SuccessMessage  get; set; 
    



//Client data mapping
    public sealed class ClientMap : ClassMap<Client>

    public ClientMap()
    
        Map(m => m.ID).Name("ID", "Id");
        Map(m => m.FirstName).Name("FirstName", "First Name");
        Map(m => m.LastName).Name("LastName", "Last Name");
        Map(m => m.DoB).Name("DateOfBirth", "DoB", "Date of Birth");
        Map(m => m.Last4SS).Name("Last 4", "XXX-XX-1234", "Last Four", "Last 4 SS");
    


//enum to reference the different mappings for the csv
public enum RecordType

    None = 0,
    ClientType,
    DemographicType,
    ReturnDataType,
    TaxYearType


//Demographic data mapping
public sealed class DemoMap : ClassMap<Demographic>

    public DemoMap()
    
        Map(m => m.ID).Name("ID", "Id");
        Map(m => m.TaxYear).Name("Tax Year", "TaxYear");
        Map(m => m.Address).Name("Street Address", "Address");
        Map(m => m.Zip).Name("ZIP", "Zip", "zip", "Postal Code");
        Map(m => m.County).Name("County", "Location");
        Map(m => m.State).Name("State", "ST");
    


//ReturnData data mapping
public sealed class DataMap : ClassMap<ReturnData>

    public DataMap()
    
        Map(m => m.ID).Name("ID", "Id");
        Map(m => m.TaxYear).Name("TaxYear", "Tax Year");
        Map(m => m.FederalReturn).Name("FedReturn", "Federal");
        Map(m => m.TotalRefund).Name("Total Refund", "TotalRefund");
        Map(m => m.EITC).Name("EITC");
        Map(m => m.CTC).Name("CTC");
        Map(m => m.Dependents).Name("dependents");
        Map(m => m.SurveyScore).Name("Questions");
    

【问题讨论】:

【参考方案1】:

如果您的 3 种不同的记录类型包含在每一行中,那么您正在执行的操作将起作用。您可以注册所有地图,并为该行中包含的每个记录数据获取一条记录。

void Main()

    var clients = new List<Client>();
    var demographics = new List<Demographic>();

    using (var reader = new StringReader("ClientId,First Name,DemoId,Tax Year\n1,John,4,2021"))
    using (var csv = new CsvReader(reader, CultureInfo.InvariantCulture))
    
        csv.Context.RegisterClassMap<ClientMap>();
        csv.Context.RegisterClassMap<DemoMap>();

        while (csv.Read())
        
            clients.Add(csv.GetRecord<Client>());
            demographics.Add(csv.GetRecord<Demographic>());
        
    


public class Client

    public int ID  get; set; 
    public string FirstName  get; set; 


public class Demographic

    public int ID  get; set; 
    public int TaxYear  get; set; 


public sealed class ClientMap : ClassMap<Client>

    public ClientMap()
    
        Map(m => m.ID).Name("ClientId");
        Map(m => m.FirstName).Name("FirstName", "First Name");
    


public sealed class DemoMap : ClassMap<Demographic>

    public DemoMap()
    
        Map(m => m.ID).Name("DemoId");
        Map(m => m.TaxYear).Name("TaxYear", "Tax Year");
    

但是,听起来每个文件只包含一种类型的记录。因此,您需要一种方法来检测文件包含的记录类型并仅获取该记录类型。您可以使用某种标识符(例如记录类型的编号)开始每个文件,然后打开通过该标识符获取的记录类型。

void Main()

    var clients = new List<Client>();
    var demographics = new List<Demographic>();
    
    var files = new List<string>()
    
        "1\nId,First Name\n1,Jordan\n2,Jennifer",
        "2\nId,Tax Year\n1,2021\n2,2020"
    ;

    var config = new CsvConfiguration(CultureInfo.InvariantCulture)
    
        IgnoreBlankLines = true,
        UseNewObjectForNullReferenceMembers = false
    ;

    foreach (var file in files)
    
        using (var reader = new StringReader(file))
        using (var csv = new CsvReader(reader, config))
        
            csv.Context.RegisterClassMap<ClientMap>();
            csv.Context.RegisterClassMap<DemoMap>();

            csv.Read();

            switch (csv.GetField<RecordType>(0))
            
                case RecordType.ClientType:
                    clients.AddRange(csv.GetRecords<Client>().ToList());
                    break;
                case RecordType.DemographicType:
                    demographics.AddRange(csv.GetRecords<Demographic>().ToList());
                    break;
                default:
                    throw new Exception("Unable to determine record type");
            
        
    


public class Client

    public int ID  get; set; 
    public string FirstName  get; set; 


public class Demographic

    public int ID  get; set; 
    public int TaxYear  get; set; 


public sealed class ClientMap : ClassMap<Client>

    public ClientMap()
    
        Map(m => m.ID).Name("ID", "Id");
        Map(m => m.FirstName).Name("FirstName", "First Name");
    


public sealed class DemoMap : ClassMap<Demographic>

    public DemoMap()
    
        Map(m => m.ID).Name("ID", "Id");
        Map(m => m.TaxYear).Name("TaxYear", "Tax Year");
    


public enum RecordType

    None = 0,
    ClientType,
    DemographicType,
    ReturnDataType,
    TaxYearType

【讨论】:

我正在处理来自上传的 IFormFiles,我将如何解决这个问题而不是使用您在此处使用的 StringReader? 和以前一样。使用using var sreader = new StreamReader(file.OpenReadStream()); 而不是using (var reader = new StringReader(file))。我刚刚使用了StringReader,以便更轻松地创建一个示例,您可以快速测试而无需拥有文件。

以上是关于CSVHelper 使用每个文件上的多个映射解析多个 CSV的主要内容,如果未能解决你的问题,请参考以下文章

使用 CsvHelper 将 CSV 文件中的编号列映射到数组

C# CSVhelper 无法使用自定义映射编写

CsvHelper 动态列映射

使用CsvHelper写入数据时出现异常

如何使用 csvhelper 将数据插入位列?

为啥'while(csv.Read())' 在 CsvHelper 中不能工作两到三遍?