【问题标题】:CSVHelper cannot map resultsCSVHelper 无法映射结果
【发布时间】:2020-04-10 13:01:58
【问题描述】:

我有一个文件导入,允许用户导入一个 CSV 文件,该文件允许程序将数据添加到数据库中。有多个映射文件,因为 CSV 文件的大小取决于它们要添加的数据,这就是为什么有多个映射类都继承自 CSVBooking

List<string> headers = SplitCSV(textReader.ReadLine()).ToList();
//Called again to reset pos
textReader = new StreamReader(importBookingsFilePath.Text);
var records = new List<CSVBooking>();
using (var reader = new StringReader(textReader.ReadToEnd()))
{
     var parser = new CsvParser(reader, CultureInfo.InvariantCulture);
     if (parser.Read() != null)
         using (var csv = new CsvReader(parser))
         {
             switch (headers.Count)
             {
                  case 77:
                  case 86:
                      csv.Configuration.RegisterClassMap<CSVBookingMapNonPayment>();
                      break;
                  case 78:
                  case 91:
                       csv.Configuration.RegisterClassMap<CSVBookingMapPayment>();
                       break;
                  case 79 when headers.Contains("IP") || headers.Contains("\"IP\""):
                       csv.Configuration.RegisterClassMap<CSVBookingMapNonPaymentExtraIPInfo>();
                       break;
                  case 74:
                       csv.Configuration.RegisterClassMap<CSVBookingMapNonPayment_DaySail>();
                       break;
                  case 87:
                       csv.Configuration.RegisterClassMap<CSVBookingMapNonPayment_Group>();
                       break;
                  case 93:
                       csv.Configuration.RegisterClassMap<CSVBookingMap_Payment_Crew>();
                       break;
                   default:
                       csv.Configuration.HeaderValidated = null;
                       csv.Configuration.AutoMap<CSVBooking>();
                       break;
              }
              records = csv.GetRecords<CSVBooking>().ToList();
        }
        else
        {
               MessageBox.Show($"There was an error parsing the file:\n{importBookingsFilePath.Text}", "Parsing error", MessageBoxButton.OK, MessageBoxImage.Error);
               return;
         }

switch 语句在案例 87 上运行,我提供了文件,parser.Read() 返回有效的标头和数据。

这是程序当前使用的映射文件:

class CSVBookingMapNonPayment_Group: ClassMap<CSVBooking>
{
     public CSVBookingMap_Payment_Crew()
     {
          Map(m => m.SubmissionDateTime).Index(0);
          Map(m => m.Title).Index(1);
          Map(m => m.Surname).Index(2);
          Map(m => m.Firstname).Index(3);
          Map(m => m.AKA).Index(4);
          ...
    }
}

class CSVBooking
{
    public string IP { get; set; }
    public string SubmissionID { get; set; }
    [Index(0)]
    public string SubmissionDateTime { get; set; }
    [Index(1)]
    public string Title { get; set; }
    [Index(2)]
    public string Surname { get; set; }
    [Index(3)]
    public string Firstname { get; set; }
    ...
}

【问题讨论】:

  • 我不喜欢 CsvReader 类,因为我可以在相同数量的代码中执行相同的操作,而无需使用任何特殊库。人们在使用 CsvReader 时遇到了两个问题,就像您遇到的那样。我需要查看 CSV 文件示例以提供解决方案。 45 年来一直在解析这样的文件。
  • 我们这里有一个设计问题。从长远来看,通过检查字段的数量来确定要映射的类可能会导致问题。除此之外,您正在传递基类并且它没有映射器,因此没有记录。我猜应该指定一个类型转换器。
  • @jdweng 是的,我一直在不使用任何库的情况下导入 CSV 文件,但我加入了一个开发团队,他们需要我解决 CSVHelper 的问题,出于某种原因,他们不想从那个图书馆。对于上下文,我正在导入一个 csv 文件,然后将其添加到数据库中,here's a sample file.
  • @OguzOzgul 我没有映射不同的类,它们只是映射到 CSVBooking 的不同映射类,该程序的用户需要将数据导入数据库并且映射配置允许一些变量不要设置为不是数据库中的每个字段都必须填写。我告诉它要映射到哪个类,不要使用基类作为映射。

标签: c# csvhelper


【解决方案1】:

您正在从TextReader 中读取一行,然后将相同的TextReader 传递给CsvReader。这意味着CsvReader 将开始读取TextReader 所在的位置,即第二行。它将第一行读取为标题行,并将其与注册的地图匹配。在你的情况下,那是一个数据行,而不是标题了。

我的建议是一次性注册所有地图。任何时候调用GetRecord&lt;MyType&gt;,它都会使用该类型的注册映射。如果需要,您可以在一行中读取多种类型,或者每行读取不同的类型。

在 CsvHelper 中执行您正在执行的操作的方式如下所示。

void Main()
{
    var s = new StringBuilder();
    s.AppendLine("Id,Name");
    s.AppendLine("1,one");
    s.AppendLine("2,two");
    using (var reader = new StringReader(s.ToString()))
    using (var csv = new CsvReader(reader, CultureInfo.InvariantCulture))
    {
        csv.Read();
        csv.ReadHeader();

        if (csv.Context.HeaderRecord.Length == 2)
        {
            csv.Configuration.RegisterClassMap<FooMap>();
        }
        else if (csv.Context.HeaderRecord.Length == 3)
        {
            csv.Configuration.RegisterClassMap<BarMap>();
        }

        csv.GetRecords<Foo>().ToList().Dump();
    }
}

public class Foo
{
    public int Id { get; set; }
    public string Name { get; set; }
}

public class Bar
{
    public Guid Property1 { get; set; }
    public decimal Cost { get; set; }
    public DateTimeOffset Date { get; set; }
}

public class FooMap : ClassMap<Foo>
{
    public FooMap()
    {
        Map(m => m.Id);
        Map(m => m.Name);
    }
}

public class BarMap : ClassMap<Bar>
{
    public BarMap()
    {
        Map(m => m.Property1);
        Map(m => m.Cost);
        Map(m => m.Date);
    }
}

再次,我建议只注册所有地图,而不是使用 if 语句。

您希望使用 CsvHelper 读取第一行的另一个原因是字段中可能有换行符。如果发生这种情况,通过 TextReader 读取单行将无法获得完整的 CSV 行。

【讨论】:

  • 我尝试创建一个不会从同一行继续的新 StringReader,但这并没有解决问题。我有不同的映射类,因为它们都链接回同一个类 CSVBooking,用户将拥有不同大小的 excel 文件,具体取决于他们想要添加到数据库的数据。我为 CsvHelper 制作了一个解析器,它允许我读取所有数据,但它将数据存储在字符串列表/数组中。我确保标头和数据已传递给 CsvHelper,但它仍然没有映射记录。
  • 您实现了自己的解析器?内置解析器在Read 上返回string[]
  • 不是我自己的解析器,我使用了 CsvParser,是的,它确实返回了一个字符串 []。这就是为什么我说它返回一个字符串/数组的原因,因为您可以将 ToList 放在末尾以使数据更具可读性。即使传递了标头和数据,仍然无法弄清楚为什么 CsvHelper 没有映射所有属性。
  • 您将示例更改为使用解析器和阅读器。您正在使用解析器提取第一行,因此读者对您的标题一无所知。如果您真的愿意,您可以这样做,但您需要关闭标头记录,并为所有映射使用索引。
  • 正如您在示例中看到的,我已经在各处设置了索引,但它仍然无法正常工作。不,我不使用解析器拉出第一行,我使用解析器拉出完整的阅读器,然后让 CsvHelper 使用解析器,我这样做是为了可以运行 .Read() 并查看标头结果和数据是否加载。我创建了自己的映射 CS 文件并放弃了 CSVHelper。
猜你喜欢
  • 2015-12-12
  • 1970-01-01
  • 2022-01-11
  • 2021-12-21
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多