【问题标题】:An effecient way to convert from IEnumerable<T> to DataTable从 IEnumerable<T> 转换为 DataTable 的有效方法
【发布时间】: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 而不是通过数据表。

标签: c# .net linq datatable


【解决方案1】:

我会为您的集合实现一个 IDataReader 并将其提供给 SqlBulkCopy,而不是通过 DataTable。如果做得正确,并使用惰性 IEnumerable,它会比数据表路由更快,并且使用更少的内存。 Mark Gravell 已经编写了这样一个库,用于将 IEnumerables 转换为 IDataReader,我建议您在自己编写之前检查一下。

可以在 NuGet 上找到 FastMember:https://www.nuget.org/packages/FastMember/ 原始来源在这里找到:https://code.google.com/p/fast-member/ 在这个线程中有一个例子:SqlBulkCopy from a List<>

更新: 您可能还需要更改命令超时,并在 sqlbulkcopy 上设置批处理大小,如下所示:

using (SqlCommand cmd = new SqlCommand(modelManager.GetInsertSproc(), sqlconn) { 
  CommandType = CommandType.StoredProcedure, CommandTimeout=300 })

bcp.BatchSize = 100000;

【讨论】:

  • 这是一个不同的问题,因为您在批量插入之后运行的命令可能会超时。只需在cmd.ExecuteNonQuery() 之前添加cmd.CommandTimeout=300;
  • 或者您也可以在 using 语句中将 CommandType = CommandType.StoredProcedure 更改为 CommandType = CommandType.StoredProcedure, CommandTimeout=300。 300 只是一个示例,但这是 5 分钟超时。
  • 谢谢你试试这些,但是在发生超时的WriteToServer上调试它时
  • 就回答我原来的问题而言,这是最好的答案。但是,要查看我的问题的最终解决方案,请参阅我的答案
  • 将您的更改添加到我的答案中,使其完整。希望它在未来对其他人有所帮助!
【解决方案2】:

对于性能问题,很难提供特定的修复,但我会删除您实际上不需要的任何内容,从 BeginEdit() 和 EndEdit() 调用开始。您正在创建一个新行,除非您需要为问题中未描述的内容提供明确的行状态,否则这些正在做您可能不需要的额外内容。

可以尝试的另一件事是将集合拆分为块,然后使用 Parallel.For/Foreach 为每个块执行数据表创建,然后使用 DataTable.Merge() 将它们重新合并在一起并返回结果。

【讨论】:

    【解决方案3】:

    不要转换。 DataTalble 正在超时并且是内存占用。您可以使用 TVP(表值参数)来非常快速地加载。它就像一个反向数据阅读器。对于来自 IEnumable(不是 DataTable),请使用 SqlDataRecord。
    just one link - search on TVP SqlDataRecord

    【讨论】:

    • 来投反对票有什么问题?我用它来非常快速地加载数百万条记录。
    【解决方案4】:

    基本上我有一个相当大的 IEnumerable(大约 16 - 17 百万 项目)到目前为止,我对此并没有任何问题, 直到我尝试使用扩展方法转换为数据表:

    根据documentation,DataTable 中行的上限为16,777,216

    DataTable 可以存储的最大行数为 16,777,216。 有关详细信息,请参阅向 DataTable 添加数据。

    【讨论】:

    • 感谢您提供此信息,但我仍然遇到我的问题,只有 500 万
    猜你喜欢
    • 2011-03-24
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2010-10-04
    • 1970-01-01
    • 1970-01-01
    • 2011-09-30
    • 2018-02-04
    相关资源
    最近更新 更多