【发布时间】:2015-12-30 22:38:03
【问题描述】:
在这被标记为重复之前,我已经看到了很多这样的答案 Convert IEnumerable to DataTable 并尝试以创建扩展方法的方式做类似的事情。我问我的问题,因为我遇到的问题可能在其他地方。
基本上我有一个相当大的 IEnumerable(大约 16 - 17 百万个项目),到目前为止我还没有遇到任何问题,直到我尝试使用扩展方法转换为数据表:
/// <summary>
/// Converts IEnumberable to datatable. Mainly for use when using SQLBulkCopy/>
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="collection"></param>
/// <param name="customColumnOrder">Custom order for columns allows me to make sure that the order of columns will always be the same. Am open for suggestions for better ways to do this</param>
/// <returns></returns>
public static DataTable ToDataTable<T>(this IEnumerable<T> collection, List<Tuple<string, int, int>> customColumnOrder)
{
DataTable dt = new DataTable();
var type = collection.First().GetType();
foreach (var column in customColumnOrder)
{
dt.Columns.Add(column.Item1, Nullable.GetUnderlyingType(type.GetProperty(column.Item1).PropertyType) ?? type.GetProperty(column.Item1).PropertyType);
}
//Populate the table
foreach (T item in collection)
{
DataRow dr = dt.NewRow();
dr.BeginEdit();
foreach (var column in customColumnOrder)
{
dr[column.Item1] = type.GetProperty(column.Item1).GetValue(item) ?? DBNull.Value;
}
dr.EndEdit();
dt.Rows.Add(dr);
}
return dt;
}
这适用于大约 100,000 个项目的较小表格,但当它达到数百万个时就开始真正挣扎。我只是不断抽出时间。是否有更有效/通常更好的方法可以将 IEnumerable 转换为数据表? 我正在转换为 DataTable,因此我可以使用 SQLBulkCopy 将数据放入数据库中。
编辑 0: 这里是数据传递的地方
/// <summary>
/// SqlBulkCopy for saving large amounts of data
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="dataToSave"></param>
/// <param name="modelManager">Custom manager to use alongside the model</param>
/// <param name="conn">Connection string to DB</param>
public void BatchSave<T>(IEnumerable<T> dataToSave, IData modelManager, string conn)
{
var model = dataToSave.First();
using (SqlConnection sqlconn= new SqlConnection(conn))
{
sqlconn.Open();
using (SqlCommand cmd = new SqlCommand(GetCreateScript(modelManager, model), sqlconn))
{
//Create temp table to do initial insert into
cmd.ExecuteNonQuery();
SqlBulkCopy copy = new SqlBulkCopy(cmd.Connection);
copy.DestinationTableName = "#tempTableForImport";
//Convert data to DataTable
DataTable dt = dataToSave.ToDataTable(modelManager.GetDataColumnsOrder());
//Copy to temp table
copy.WriteToServer(dt);
}
using (SqlCommand cmd = new SqlCommand(modelManager.GetInsertSproc(), sqlconn) { CommandType=CommandType.StoredProcedure })
{
//Clean up data and move to final table
cmd.ExecuteNonQuery();
}
sqlconn.Close();
}
}
编辑 1: 使用建议的新修改代码,现在使用 Fastmember:
public void BatchSave<T>(IEnumerable<T> dataToSave, IData modelManager, string conn)
{
var model = dataToSave.First();
using (SqlConnection sqlconn = new SqlConnection(conn))
{
sqlconn.Open();
using (var bcp = new SqlBulkCopy(sqlconn))
{
using (var reader = ObjectReader.Create(dataToSave, modelManager.GetDataColumnsOrder().Select(s => s.Item1).ToArray() /*modelManager.GetDataColumnsOrder().Select(obj=>obj.Item1).ToString()*/))
{
using (SqlCommand cmd= new SqlCommand(GetCreateScript(modelManager, model), sqlconn))
{
cmd.ExecuteNonQuery();
bcp.DestinationTableName = "#tempTableForImport";
bcp.WriteToServer(reader);
}
using (SqlCommand cmd = new SqlCommand(modelManager.GetInsertSproc(), sqlconn) { CommandType = CommandType.StoredProcedure })
{
cmd.ExecuteNonQuery();
}
}
}
sqlconn.Close();
}
}
这加快了速度,但我仍然在此行 bcp.WriteToServer(reader); 上收到“超时已过期”。感谢大家在大约 30 秒后提供的帮助,对此还有什么想法吗?也许以某种方式增加超时前的时间长度?
【问题讨论】:
-
不要在 IEnumerable 集合上使用 foreach。
-
@M.kazemAkhgary 感谢您的回复,您能解释一下原因吗?
-
@M.kazemAkhgary 为什么不呢?你会建议什么?
-
@M.kazemAkhgary 如果这是你的推理,那么我认为你误解了一些东西。
-
我会在这个线程中查看 Mark Gravell 的帖子:stackoverflow.com/questions/3913371/sqlbulkcopy-from-a-list,它为对象实现了 IDataReader,然后可以将其馈送到 SqlBulkCopy 而不是通过数据表。