我正在尝试使用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到数据库?
答案 0 :(得分:17)
如果我没弄错的话,你应该可以用更少的代码来做。在进入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 );
}
答案 1 :(得分:3)
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;
}
答案 2 :(得分:1)
我能够通过添加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);
}
如果你能在没有这么多硬编码的情况下解决问题,我会接受你的答案作为答案。
答案 3 :(得分:1)
我喜欢@JoshClose的答案,但我发现while( csv.Read() )
比csv.GetRecords<{Class}>().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;
}