好的,我会尽力解释这个问题。我编写了一个应用程序,它使用SQL表来定义固定宽度的数据源结构(因此,标题,起始索引,字段长度等)。当我的应用程序运行时,它会查询该表并使用包含ColumnName = header的DataColumn对象创建一个DataTable对象(称为finalDT
)。然后,我在这个表中附加了一组DataColumn对象,这些对象存在于我们使用的每个数据源中(我倾向于称之为派生列)。我还创建了一个主键字段,它是一个自动递增的整数。最初,我推出自己的解决方案来读取固定宽度文件,但我尝试将其转换为使用FileHelper。主要是,我希望合并它,以便我可以访问FileHelper可以解析的其他文件类型(CSV,Excel等)。
现在,我的问题。使用FileHelper.Dynamic,我能够使用以下方法创建FileHelperEngine对象:
private static FileHelperEngine GetFixedWidthFileClass(bool ignore)
{
singletonArguments sArgs = singletonArguments.sArgs;
singletonSQL sSQL = singletonSQL.sSQL;
List<string> remove = new List<string>();
FixedLengthClassBuilder flcb = new FixedLengthClassBuilder(sSQL.FixedDataDefinition.DataTableName);
flcb.IgnoreFirstLines = 1;
flcb.IgnoreLastLines = 1;
flcb.IgnoreEmptyLines = true;
foreach (var dcs in sSQL.FixedDataDefinition.Columns)
{
flcb.AddField(dcs.header, Convert.ToInt32(dcs.length), "String");
if (ignore && dcs.ignore)
{
flcb.LastField.FieldValueDiscarded = true; //If we want to ignore a column, this is how to do it. Would like to incorporate this.
flcb.LastField.Visibility = NetVisibility.Protected;
}
else
{
flcb.LastField.TrimMode = TrimMode.Both;
flcb.LastField.FieldNullValue = string.Empty;
}
}
return new FileHelperEngine(flcb.CreateRecordClass());
}
sSQL.FixedDataDefinition.Columns
是我如何存储固定宽度数据源文件的字段定义。然后,我通过执行以下操作生成DataTable:
DataTable dt = engine.ReadFileAsDT(file);
其中file
是固定宽度文件的完整路径,engine
是我保留上面显示的GetFixedWidthFileClass()
方法的结果的地方。好的,现在我有一个没有主键的DataTable,也没有派生列。此外,dt
中的所有字段都标记为ReadOnly = true
。事情变得一团糟。
我需要将dt
填入finalDT
并且dt
没有任何主要密钥信息就可以了。如果发生这种情况,那么我可以使用finalDT
将我的数据上传到我的SQL表中。如果这种情况不可能发生,那么我需要一种方法让finalDT
没有主键,但仍然可以上传到我的SQL表。 SqlBulkCopy
会允许吗?还有另一种方式吗?
此时,我愿意从头开始,只要我可以使用FileHelper来解析固定宽度文件并将结果存储到我的SQL表中,我只是没有看到那里的路径。 / p>
答案 0 :(得分:1)
我明白了。它不漂亮,但这是它的工作原理。基本上,我在原始帖子中设置代码的方式仍然适用,因为我在GetFixedWidthFileClass()
方法中没有更改任何内容。然后我必须添加两个方法来正确设置finalDT
:
/// <summary>
/// For a given a datasource file, add all rows to the DataSet and collect Hexdump data
/// </summary>
/// <param name="ds">
/// The <see cref="System.Data.DataSet" /> to add to
/// </param>
/// <param name="file">
/// The datasource file to process
/// </param>
internal static void GenerateDatasource(ref DataSet ds, ref FileHelperEngine engine, DataSourceColumnSpecs mktgidSpecs, string file)
{
// Some singleton class instances to hold program data I will need.
singletonSQL sSQL = singletonSQL.sSQL;
singletonArguments sArgs = singletonArguments.sArgs;
try
{
// Load a DataTable with contents of datasource file.
DataTable dt = engine.ReadFileAsDT(file);
// Clean up the DataTable by removing columns that should be ignored.
DataTableCleanUp(ref dt, ref engine);
// ReadFileAsDT() makes all of the columns ReadOnly. Fix that.
foreach (DataColumn column in dt.Columns)
column.ReadOnly = false;
// Okay, now get a Primary Key and add in the derived columns.
GenerateDatasourceSchema(ref dt);
// Parse all of the rows and columns to do data clean up and assign some custom
// values. Add custom values for jobID and serial columns to each row in the DataTable.
for (int row = 0; row < dt.Rows.Count; row++)
{
string version = string.Empty; // The file version
bool found = false; // Used to get out of foreach loops once the required condition is found.
// Iterate all configured jobs and add the jobID and serial number to each row
// based upon match.
foreach (JobSetupDetails job in sSQL.VznJobDescriptions.JobDetails)
{
// Version must match id in order to update the row. Break out once we find
// the match to save time.
version = dt.Rows[row][dt.Columns[mktgidSpecs.header]].ToString().Trim().Split(new char[] { '_' })[0];
foreach (string id in job.ids)
{
if (version.Equals(id))
{
dt.Rows[row][dt.Columns["jobid"]] = job.jobID;
lock (locklist)
dt.Rows[row][dt.Columns["serial"]] = job.serial++;
found = true;
break;
}
}
if (found)
break;
}
// Parse all columns to do data clean up.
for (int column = 0; column < dt.Columns.Count; column++)
{
// This tab character keeps showing up in the data. It should not be there,
// but customer won't fix it, so we have to.
if (dt.Rows[row][column].GetType() == typeof(string))
dt.Rows[row][column] = dt.Rows[row][column].ToString().Replace('\t', ' ');
}
}
dt.AcceptChanges();
// DataTable is cleaned up and modified. Time to push it into the DataSet.
lock (locklist)
{
// If dt is writing back to the DataSet for the first time, Rows.Count will be
// zero. Since the DataTable in the DataSet does not have the table schema and
// since dt.Copy() is not an option (ds is referenced, so Copy() won't work), Use
// Merge() and use the option MissingSchemaAction.Add to create the schema.
if (ds.Tables[sSQL.FixedDataDefinition.DataTableName].Rows.Count == 0)
ds.Tables[sSQL.FixedDataDefinition.DataTableName].Merge(dt, false, MissingSchemaAction.Add);
else
{
// If this is not the first write to the DataSet, remove the PrimaryKey
// column to avoid duplicate key values. Use ImportRow() rather then .Merge()
// since, for whatever reason, Merge() is overwriting ds each time it is
// called and ImportRow() is actually appending the row. Ugly, but can't
// figure out another way to make this work.
dt.PrimaryKey = null;
dt.Columns.Remove(dt.Columns[0]);
foreach (DataRow dr in dt.Rows)
ds.Tables[sSQL.FixedDataDefinition.DataTableName].ImportRow(dr);
}
// Accept all the changes made to the DataSet.
ds.Tables[sSQL.FixedDataDefinition.DataTableName].AcceptChanges();
}
// Clean up memory.
dt.Clear();
// Log my progress.
log.GenerateLog("0038", log.Info
, engine.TotalRecords.ToString() + " DataRows successfully added for file:\r\n\t"
+ file + "\r\nto DataTable "
+ sSQL.FixedDataDefinition.DataTableName);
}
catch (Exception e)
{
// Something bad happened here.
log.GenerateLog("0038", log.Error, "Failed to add DataRows to DataTable "
+ sSQL.FixedDataDefinition.DataTableName
+ " for file\r\n\t"
+ file, e);
}
finally
{
// Successful or not, get rid of the datasource file to prevent other issues.
File.Delete(file);
}
}
这个方法:
/// <summary>
/// Deletes columns that are not needed from a given <see cref="System.Data.DataTable" /> reference.
/// </summary>
/// <param name="dt">
/// The <see cref="System.Data.DataTable" /> to delete columns from.
/// </param>
/// <param name="engine">
/// The <see cref="FileHelperEngine" /> object containing data field usability information.
/// </param>
private static void DataTableCleanUp(ref DataTable dt, ref FileHelperEngine engine)
{
// Tracks DataColumns I need to remove from my temp DataTable, dt.
List<DataColumn> removeColumns = new List<DataColumn>();
// If a field is Discarded, then the data was not imported because we don't need this
// column. In that case, mark the column for deletion by adding it to removeColumns.
for (int i = 0; i < engine.Options.Fields.Count; i++)
if (engine.Options.Fields[i].Discarded)
removeColumns.Add(dt.Columns[i]);
// Reverse the List so changes to dt don't generate schema errors.
removeColumns.Reverse();
// Do the deletion.
foreach (DataColumn column in removeColumns)
dt.Columns.Remove(column);
// Clean up memory.
removeColumns.Clear();
}
基本上,由于在ds
方法中引用了finalDT
(GenerateDatasource
所居住的数据集),我无法使用dt.Copy()
将数据推入其中。我必须使用Merge()
来执行此操作。然后,在我希望使用Merge()
的地方,我必须使用foreach
循环和ImportRow()
,因为Merge()
会覆盖finalDT
。
我必须解决的其他问题是:
ImportRow()
时,我还需要从PrimaryKey
删除dt
,否则我会收到有关重复密钥的错误。FileHelperEngine
或FileHelpers.Dynamic.FixedLengthClassBuilder
在跳过我想忽略的列时遇到问题。它要么根本不会对它们进行确认,从而导致我的列偏移量,以及随后在数据源文件中读取数据的准确性(使用FieldHidden
选项),或者读取它们并以无论如何创建列,但不加载数据(使用FieldValueDiscarded
和Visibility.Private
或.Protected
选项)。这对我来说意味着我必须在调用dt
后重复engine.ReadFileAsDT(file)
并删除标记为Discarded
的列。dt
传递给方法(GenerateDatasourceSchema()
)以对其进行排序。该方法基本上只是添加这些列并确保PrimaryKey是第一列。其余的代码是修复我需要做的列和行。在某些情况下,我为每一行设置列的值,在其他情况下,我正在清理原始数据中的错误(因为它来自我的客户)。
它并不漂亮,我希望能够找到更好的方式。如果有人对我的表现有所了解,我很乐意听到。