【问题标题】:Populate database table from CSV从 CSV 填充数据库表
【发布时间】:2022-01-04 08:31:28
【问题描述】:

我正在尝试从 csv 文件中填充数据表读取值。数据表的格式应与数据库中对应的表相匹配。

csv 文件有很多列 (~80),所以我不想输入所有内容。 csv 文件中的列名与 db 中的列名不完全匹配。此外,必须手动添加两个额外的列,其中 csv 中不存在数据。

问题是将csv文件中的字符串数据转换为数据表中的正确类型。

目前我有

  1. 我从数据库中读取表格模板并使用它来创建我的新数据表。

  2. 我创建了一个映射,将 csv 文件中的列位置映射到数据库中的列位置。

  3. 我尝试将 csv 文件中的值插入到数据表中。这是我的代码失败的地方,因为数据的类型不正确。如上所述,由于有很多不同的列,我不想手动进行转换,而是从表格模板中推断出类型。此外,某些列可以包含空值。

我的代码

public static DataTable ReadAssets(string strFilePath, DateTime reportingDate, Enums.ReportingBases reportingBasis, char sep=',')
{
    //Reads the table template from the database
    DataTable dt = DbInterface.Db.GetTableTemplate("reports.Assets");

    var dbColumnNames = dt.Columns.Cast<DataColumn>().Select(x => x.ColumnName).ToList();

    //These columns are not present in the csv data and so they have to be added manually
    int posReportingDate = dbColumnNames.IndexOf("ReportingDate");
    int posReportingBasis = dbColumnNames.IndexOf("ReportingBasis");

    //read the csv and populate the table
    using (StreamReader sr = new (strFilePath))
    {
        string[] csvColumnNames = sr.ReadLine().Split(sep);

        //creates an <int, int> dictionary that maps the columns
        var columnMap = CreateColumnMap(dbColumnNames.ToArray(), csvColumnNames);
            
        while (!sr.EndOfStream)
        {
            string[] csvRow = sr.ReadLine().Split(sep);
            DataRow dr = dt.NewRow();

            dr[posReportingDate] = reportingDate;
            dr[posReportingBasis] = reportingBasis.ToString();

            foreach(var posPair in columnMap)
            {
                //This is where the code fails.... I need a conversion to the correct type here.
                dr[posPair.Value] = csvRow[posPair.Key];
            }

            dt.Rows.Add(dr);
        }
    }
    return dt;
}

【问题讨论】:

  • 我认为你可以使用dr[posPair.Value] = Convert.ChangeType(csvRow[posPair.Key], dbColumn.DataType); 之类的东西。可以从DataColumn获取数据类型。
  • @ekke 嗨,谢谢。那几乎做到了。它似乎无法处理可空类型...

标签: c# csv datatable


【解决方案1】:

我维护了几个可以帮助解决这种情况的库:Sylvan.Data and Sylvan.Data.Csv。它们都是开源的,经过 MIT 许可,可在 nuget.org 上获得。我的库允许将模式应用于 CSV 数据,并附加额外的列。这样做允许使用SqlBulkCopy 有效地将数据直接加载到数据库中。我的 CSV 解析器也恰好是 fastest in the .NET ecosystem

举个例子,给定如下目标SQL表:

create table MyTable (
Name varchar(32),
Value int,
ValueDate datetime,
InsertDate datetime,
RowNum int
)

一个 CSV 文件,data.csv,包含以下内容:

a,b,c
a,1,2022-01-01
b,2,2022-01-02

这是一个完整的 C# 6 示例程序,它将 CSV 数据和“额外”列批量复制到数据库表中。

using Sylvan.Data; // v0.1.1
using Sylvan.Data.Csv; // v1.1.11
using System.Data.SqlClient;

const string SourceCsvFile = "data.csv";
const string TargetTableName = "MyTable";

var conn = new SqlConnection();
conn.ConnectionString = new SqlConnectionStringBuilder
{
    DataSource = ".",
    InitialCatalog = "Test",
    IntegratedSecurity = true
}.ConnectionString;
conn.Open();

// read schema for the target table
var cmd = conn.CreateCommand();
cmd.CommandText = $"select top 0 * from {TargetTableName}";

var reader = cmd.ExecuteReader();
var schema = reader.GetColumnSchema();
reader.Close();

// apply the database schema to the CSV data
var opts = new CsvDataReaderOptions { Schema = new CsvSchema(schema) };
var csvReader = CsvDataReader.Create(SourceCsvFile, opts);

// attach additional external columns to the CSV data
var data = csvReader.WithColumns(
    new CustomDataColumn<DateTime>("ImportDate", r => DateTime.UtcNow),
    new CustomDataColumn<int>("RowNum", r => csvReader.RowNumber)
);

// bulk copy the data into the target table
var bc = new SqlBulkCopy(conn);
bc.DestinationTableName = TargetTableName;
bc.WriteToServer(data);

希望您发现这是一个优雅的解决方案。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2013-01-17
    • 2011-02-22
    • 2017-04-17
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-08-26
    • 1970-01-01
    相关资源
    最近更新 更多