【问题标题】:How to add CsvHelper records to DataTable to use for SqlBulkCopy to the database如何将 CsvHelper 记录添加到 DataTable 以用于 SqlBulkCopy 到数据库
【发布时间】:2022-05-03 21:04:15
【问题描述】:

我正在尝试使用 CsvHelper 读取 CSV 文件,将每条记录加载到 DataTable 中,然后使用 SqlBulkCopy 将数据插入到数据库表中。使用当前代码,在向 DataTable 添加行时出现异常。例外情况是:“无法将 'MvcStockAnalysis.Models.StockPrice' 类型的对象转换为 'System.IConvertible' 类型。无法存储在日期列中。预期类型为 DateTime。”

示例 CSV 文件来自 yahoo Finance。例如:http://ichart.yahoo.com/table.csv?s=MMM&a=0&b=1&c=2010&d=0&e=17&f=2014&g=d&ignore=.csv

CSV 文件包含以下标题: 日期 开盘 高 低 收盘 成交量 调整收盘

我正在将 CSV 文件读入的模型:

namespace MvcStockAnalysis.Models
{
    using System;
    using System.Collections.Generic;

    public partial class StockPrice
    {
        public int Id { get; set; }
        public System.DateTime Date { get; set; }
        public int CompanyId { get; set; }
        public double High { get; set; }
        public double Low { get; set; }
        public double Close { get; set; }
        public double AdjClose { get; set; }
        public double Open { get; set; }
        public double Volume { get; set; }

        public virtual Company Company { get; set; }
    }
}

CSV 文件到 StockPrice 类的映射使用以下内容:

public class StockPriceClassMap : CsvClassMap<StockPrice>
{
    public override void CreateMap()
    {
        Map(m => m.Date).Name("Date");
        Map(m => m.Close).Name("Close");
        Map(m => m.AdjClose).Name("Adj Close");
        Map(m => m.High).Name("High");
        Map(m => m.Low).Name("Low");
        Map(m => m.Open).Name("Open");
        Map(m => m.Volume).Name("Volume");
    }
}

尝试将 CsvHelper 记录添加到 DataTable 的代码如下:

var connectionstring = ConfigurationManager.ConnectionStrings["MvcStockAnalysis.Models.MvcStockAnalysisContext"];
var connection = new SqlConnection();
connection.ConnectionString = connectionstring.ToString();
var destinationTableName = "StockPrices";
var company = db.Company
            .Where(c => c.Symbol == "MMM")
            .FirstOrDefault();

try
{
    string path = HttpContext.Server.MapPath("~/App_Data/" + company.Symbol + @".csv");

    if (System.IO.File.Exists(path))
    {     

        using (StreamReader sr = new StreamReader(path))
        {
            using (var csv = new CsvReader(sr))
            {
                DataTable dt = new DataTable("StockPrices");
                csv.Configuration.HasHeaderRecord = true;
                csv.Configuration.RegisterClassMap<StockPriceClassMap>();

                dt.Columns.Add(new DataColumn("Date", typeof(DateTime)));
                dt.Columns.Add(new DataColumn("Close", typeof(Double)));
                dt.Columns.Add(new DataColumn("AdjClose", typeof(Double)));
                dt.Columns.Add(new DataColumn("High", typeof(Double)));
                dt.Columns.Add(new DataColumn("Low", typeof(Double)));
                dt.Columns.Add(new DataColumn("Open", typeof(Double)));
                dt.Columns.Add(new DataColumn("Volume", typeof(Double)));
                dt.Columns.Add(new DataColumn("CompanyId", typeof(Double)));
                var records = csv.GetRecords<StockPrice>().ToList();
                foreach (var record in records)
                {                                    
                    record.CompanyId = company.Id;
                    dt.Rows.Add(record);
                }
                // add dt to the database
                using (var bulkCopy = new SqlBulkCopy(connection.ConnectionString))
                {
                    // DataTable column names match my SQL Column names, so I simply made this loop. 
                    foreach (DataColumn col in dt.Columns)
                    {
                        bulkCopy.ColumnMappings.Add(col.ColumnName, col.ColumnName);
                    }
                    bulkCopy.DestinationTableName = destinationTableName;
                    bulkCopy.WriteToServer(dt);
                }
            }
        }
    }
    connection.Close();
}
catch (Exception e)
{
    Console.Write(e.Message);
}

如何将 CsvHelper 记录添加到 DataTable 以用于 SqlBulkCopy 到数据库?

【问题讨论】:

    标签: .net csv datatable sqlbulkcopy csvhelper


    【解决方案1】:

    如果我没记错的话,你应该可以用更少的代码来完成它。你也不必在进入DataTable之前进入另一个班级。

    while( csv.Read() )
    {
        var row = dt.NewRow();
        foreach( DataColumn column in dt.Columns )
        {
            row[column.ColumnName] = csv.GetField( column.DataType, column.ColumnName );
        }
        dt.Rows.Add( row );
    }
    

    【讨论】:

    • 赞成因为...你知道...你是图书馆的作者
    • 如果有办法从 Map 中获取 IndexOf(colName),当 DataTable 列顺序与 CSV 顺序不匹配时会有所帮助。
    • @JoePhillips 那又怎样?图书馆的作者不能回答问题吗?
    • @StefanFachmann 是的,这就是我投票的原因。重新阅读我的评论
    【解决方案2】:

    Josh 去年增加了对读取标题的支持,以下块可能对那些只想使用 CSV 文档架构构建 DataTable 的人有用。我想将此作为对 Josh 答案的评论发布,因为它只是一个小的修改,但由于我无法在评论中格式化代码块,因此作为答案发布。

        private DataTable BuildDataTable()
        {
            var dt = new DataTable();
            using (var textReader = new StreamReader(_path))
            {
                using (var csv = new CsvReader(textReader))
                {
                    csv.ReadHeader();
                    foreach (var header in csv.FieldHeaders)
                    {
                        dt.Columns.Add(header);
                    }
                    while (csv.Read())
                    {
                        var row = dt.NewRow();
                        foreach (DataColumn column in dt.Columns)
                        {
                            row[column.ColumnName] = csv.GetField(column.DataType, column.ColumnName);
                        }
                        dt.Rows.Add(row);
                    }
                }
            }
            return dt;
        }
    

    【讨论】:

    【解决方案3】:

    我喜欢@JoshClose 的回答,但我发现while( csv.Read() )csv.GetRecords&lt;{Class}&gt;().ToList() 慢得多。当返回的值应该是 DBNull 时,它也不能正确处理许多可空类型,如 int?。我的回答是让 CsvHelper 导入动态记录列表,然后使用几个辅助方法自动映射到 DataTable。

    var records = csv.GetRecords<dynamic>().ToList();
    
    foreach ( record in records )
    {
        var row = dt.NewRow();
    
        var recordDictionary = DynamicToDictionary( record );
    
        foreach( DataColumn column in dt.Columns )
        {
            row[column.ColumnName] = GetColumnValue( column, recordDictionary );
        }
    
        dt.Rows.Add( row );
    }
    

    DynamicToDictionary 方法处理区分大小写和标题空白。我将动态对象转换为忽略大小写敏感并删除标题空白的 Dictionary 对象。如果这不是问题,可以跳过这一步并将动态对象直接传递给GetColumnValue

    public Dictionary<string, object> DynamicToDictionary(dynamic dynObj)
    {
        var dictionary = new Dictionary<string, object>(StringComparer.OrdinalIgnoreCase);
    
        foreach (var kvp in (IDictionary<string, object>) dynObj)
        {
            var obj = kvp.Value;
    
            // Remove white space.
            var name = new string(kvp.Key.ToCharArray().Where(c => !char.IsWhiteSpace(c)).ToArray());
    
            dictionary.Add(name, obj);
        }
    
        return dictionary;
    }
    

    GetColumnValue 方法查找动态记录值并将其转换为适当的 DataTable 列值。

    public object GetColumnValue(DataColumn column, IDictionary<string, object> dynamicDictionary)
    {
        object value;
    
        // Return DBNull if the column name isn't found.
        if (!dynamicDictionary.TryGetValue(column.ColumnName, out value))
        {
            return DBNull.Value;
        }
    
        // Null values come in as empty strings.
        if (column.AllowDBNull && column.DataType != typeof(string) && (string)value == "")
        {
            return DBNull.Value;
        }
    
        if (column.DataType == typeof(bool))
        {
            return (string)value != "0" && ((string)value).ToLower() != "false";
        }
    
        return value;
    }
    

    【讨论】:

      【解决方案4】:

      我能够通过添加一个 DataTable 行并明确填写它来实现此功能,而不是尝试将 CsvHelper 记录添加为一行。

      我使用了以下部分而不是上面显示的类似部分:

      foreach (var record in records)
      {
          DataRow row = dt.NewRow();
          record.CompanyId = company.Id;
          row["Date"] = record.Date;
          row["Close"] = record.Close;
          row["AdjClose"] = record.AdjClose;
          row["High"] = record.High;
          row["Low"] = record.Low;
          row["Open"] = record.Open;
          row["Volume"] = record.Volume;
          row["CompanyId"] = record.CompanyId;
          dt.Rows.Add(row);
      }
      

      如果你能在没有那么多硬编码的情况下解决问题,我会接受你的答案作为答案。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2019-09-16
        • 1970-01-01
        相关资源
        最近更新 更多