如何解决CsvHelper、SqlBulkCopy、制作拙劣的 CSV 文件
重新表述之前的问题,因为它过于笼统。我正在尝试将 CsvHelper 与 sqlBulkcopy 和制作不良的 CSV 文件一起使用。
问题是如何将格式不佳的 Csv 文件(没有 col 标头、类型转换、引用删除等)读取到可以传递给 sqlBulkcopy 的数据表中?
- 最初的尝试是使用 CsvDataReader。不幸的是一些 列(日期、日期时间)需要重新格式化。使用时 CsvDataReader 格式丢失。
- 可以使用 GetRecord
var record = csv.GetRecord<Foo>()
干净地解析文件,但由于 Csv 文件结构,从那里进入数据表一直具有挑战性。它还基本上让我映射文件布局 4 次(<Foo>
、<FooMAP>
、数据表创建,并在填充数据表行时再次映射(这是我努力处理返回的行)。立>
我喜欢类和类映射方法,但在我的使用中,列名真正重要的唯一地方是数据表和 sqlBulkcopy。我是否可以不只是构建具有正确结构的数据表,然后使用 GetField 之类的东西通过索引构建数据行列并进行任何必要的类型转换?我尝试了多次迭代,但编译只会进入 lala land。
我确信这可以更有效地完成,但我的 C# 技能有限,因此寻求有关执行此操作的最佳方法的一些建议。不是要求用勺子喂食,但确实需要一些细节(对不起和 tia)。
下面是一个数据文件的片段,并没有组织好代码示例。每天早上将收到多个文件,行数为 +50M(+10GB)。这将只需要每次调用处理 1 个文件。他们来自一个合作伙伴,不幸的是,在试图让他们重新格式化时,我的手被束缚了。
CSV 文件示例
A,STRINGER,THAT,ISN'T,A,HEADER,RECORD,OR,SAME,COLUMN,COUNT,"""ROW"" 1","20000101",1,"DCBDA","AC",20011213,+45821.41,23,"ROW" 2","DBDDA ","AC ","0002K5A9V8U8V357JK","AGDDA",20120117,-821.41," ","2016-12-21-08.53.05.857000"
"0002K5A9V8UAU499IO","20000102","ARDDA",-175000.00,"2016-12-21-08.53.06.024000"
"0002K5A9V8UCT5DBHS","20000103","RGDDA",+4821.41,"2016-12-21-08.53.06.030000"
Code Snip(有点粗糙但很容易在没有数据表的情况下工作)
public class CSVReader
// Read a CSV file returning a DataTable.
// Primary intent is for use with sqlBulkcopy but can be used for more.
{
static void Main()
{
string rfile = @"C:\BAL-CsvReader2.txt";
var fileType = @"BAL";
string connString = @"xx";
var destTable = @"[DBO].[BAL1]";
var batchSize = 5;
List<string> ErrRecords = new();
var isBadRecord = false;
var goodCount = 0;
var badCount = 0;
var rconfig = new CsvHelper.Configuration.CsvConfiguration(CultureInfo.InvariantCulture)
{
BufferSize = 1024,Delimiter = ",",AllowComments = true,HasHeaderRecord = false,HeaderValidated = null,IgnoreBlankLines = true,MissingFieldFound = null,Comment = '#',Escape = '"',BadDataFound = x =>
{
isBadRecord = true;
ErrRecords.Add(x.RawRecord);
++badCount;
isBadRecord = false;
}
};
var dt = new DataTable();
using (var reader = new StreamReader(rfile))
{
reader.ReadLine(); //Get past non-header header
using (var csv = new CsvReader(reader,rconfig))
{
csv.Context.RegisterClassMap<BALMap>();
var balRecords = new List<BAL>();
dt.Columns.Add(new DataColumn("BALID",typeof(string)));
dt.Columns.Add(new DataColumn("NUMBERA",typeof(string)));
dt.Columns.Add(new DataColumn("NUMBERB",typeof(int)));
dt.Columns.Add(new DataColumn("APPLICATIONCODE",typeof(string)));
dt.Columns.Add(new DataColumn("STATUS",typeof(string)));
dt.Columns.Add(new DataColumn("BALDATE",typeof(DateTime)));
dt.Columns.Add(new DataColumn("TRIALBALANCE",typeof(decimal)));
dt.Columns.Add(new DataColumn("PENDING",typeof(decimal)));
dt.Columns.Add(new DataColumn("CREDITS",typeof(decimal)));
dt.Columns.Add(new DataColumn("PENDCRED",typeof(decimal)));
dt.Columns.Add(new DataColumn("BATCH",typeof(int)));
dt.Columns.Add(new DataColumn("MCODE",typeof(string)));
dt.Columns.Add(new DataColumn("RECORDTIMESTAMP",typeof(DateTime)));
foreach (DataColumn col in dt.Columns)
{
dt.Columns[col.ColumnName].Allowdbnull = true;
}
// Data doesn't have headers.
var valueTypes = new Type[dt.Columns.Count];
for (int i = 0; i < valueTypes.Length; i++)
{
var dc = dt.Columns[i];
var type = dc.DataType;
if (dc.Allowdbnull && type.IsValueType)
type = typeof(Nullable<>).MakeGenericType(type);
valueTypes[i] = type;
}
var valueBuffer = new object[valueTypes.Length];
dt.BeginLoadData();
while (csv.Read())
{
var record = csv.GetRecord<BAL>();
if (!isBadRecord)
{
// Using this doesn't work as it pulls from the CSV reader rather than record so it doesn't get the date/time formating which then causes it to fail.
for (int i = 0; i < valueBuffer.Length; i++)
valueBuffer[i] = csv.GetField(valueTypes[i],i);
dt.LoadDaTarow(valueBuffer,true);
// dt.Rows.Add(row);
// bulkcopy.WritetoServer(dt);
balRecords.Add(record);
++goodCount;
}
}
dt.EndLoadData();
isBadRecord = false;
}
}
}
};
public class BAL // BAL SOURCE
{
[Index(0)]
public string BALID { get; set; }
[Index(1)]
public string NUMBERA { get; set; }
[Index(2)]
public int? NUMBERB { get; set; }
[Index(3)]
public string APPLICATIONCODE { get; set; }
[Index(4)]
public string STATUS { get; set; }
[Index(5)]
public DateTime? BALDATE { get; set; }
[Index(6)]
public decimal? TRIALBALANCE { get; set; }
[Index(7)]
public decimal? PENDING { get; set; }
[Index(8)]
public decimal? CREDITS { get; set; }
[Index(9)]
public decimal? PENDCRED { get; set; }
[Index(10)]
public int? BATCH { get; set; }
[Index(11)]
public string MCODE { get; set; }
[Index(12)]
public DateTime? RECORDTIMESTAMP { get; set; }
}
public sealed class BALMap : ClassMap<BAL>
{
public BALMap()
{
// AutoMap(CultureInfo.InvariantCulture);
Map(m => m.BALID).Index(0);
Map(m => m.NUMBERA).Index(1);
Map(m => m.NUMBERB).Index(2);
Map(m => m.APPLICATIONCODE).Index(3);
Map(m => m.STATUS).Index(4);
Map(m => m.BALDATE).Index(5).TypeConverterOption.Format("yyyyMMdd");
Map(m => m.TRIALBALANCE).Index(6);
Map(m => m.PENDING).Index(7);
Map(m => m.CREDITS).Index(8);
Map(m => m.PENDCRED).Index(9);
Map(m => m.BATCH).Index(10);
Map(m => m.MCODE).Index(11);
Map(m => m.RECORDTIMESTAMP).Index(12).TypeConverterOption.Format("yyyy-MM-dd-hh.mm.ss.ffffff");
}
}
更新:1 仍然没有想出如何使用 GetField 遍历行(我的愚蠢挑战 - 下面的第一个片段),但已经想出了如何使用类动态创建数据(下面的第二个片段)。
while (csv.Read())
{
var row = dt.NewRow();
object[] values = new object[props.Count];
foreach (var item in props)
{
for (int i = 0; i < values.Length; i++)
{
**values[i] = csv.GetField<Foo>(i);**
}
dt.Rows.Add(row);
PropertyDescriptorCollection props = TypeDescriptor.GetProperties(typeof(Foo));
DataTable dt = new DataTable();
for (int i = 0; i < props.Count; i++)
{
PropertyDescriptor prop = props[i];
if (prop.PropertyType.IsGenericType && prop.PropertyType.GetGenericTypeDeFinition() == typeof(Nullable<>))
dt.Columns.Add(prop.Name,prop.PropertyType.GetGenericArguments()[0]);
else
dt.Columns.Add(prop.Name,prop.PropertyType);
}
版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。