簡體   English   中英

從IEnumerable轉換的有效方法 <T> 到數據表

[英]An effecient way to convert from IEnumerable<T> to DataTable

在將其標記為重復之前,我已經看到了許多答案,例如“ 將IEnumerable轉換為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); “超時已過期” bcp.WriteToServer(reader); 感謝所有在約30秒后提供的幫助,您對此有何想法? 也許以某種方式增加了超時之前的時間長度?

而不是通過DataTable,我將為您的集合實現IDataReader並將其提供給SqlBulkCopy。 如果正確完成並使用惰性IEnumerable,它將比數據表路由更快,使用更少的內存。 Mark Gravell已經編寫了一個用於將IEnumerables轉換為IDataReader的庫,我建議您在滾動自己的庫之前先進行檢查。

可以在NuGet上找到FastMember, 網址為: https ://www.nuget.org/packages/FastMember/,原始資源位於: https : //code.google.com/p/fast-member/,並帶有示例線程在這里: 來自列表<>的SqlBulkCopy

更新:您可能還需要更改命令超時,並在sqlbulkcopy上設置批處理大小,如下所示:

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

bcp.BatchSize = 100000;

由於性能問題,很難提供特定的修復程序,但是從BeginEdit()和EndEdit()調用開始,我將刪除您實際上不需要的所有內容。 您正在創建一個新行,除非您為問題中未描述的內容需要一個顯式的行狀態,否則這些將做您可能不需要的額外工作。

可以嘗試的另一件事是將集合拆分為多個塊,然后使用Parallel.For / Foreach為每個塊執行數據表創建,然后使用DataTable.Merge()將它們合並在一起並返回結果。

不要轉換。 DataTalble正在超時,它是內存消耗。 您可以使用TVP(表值參數)快速加載。 它就像一個反向數據讀取器。 對於來自IEnumable(不是DataTable)的數據,請使用SqlDataRecord。
只是一個鏈接-在TVP SqlDataRecord上搜索

從本質上講,到目前為止,我有相當大的IEnumerable(大約16-17百萬個項目),在使用擴展方法嘗試轉換為數據表之前,我並沒有遇到任何問題:

根據文檔 ,數據表中行的上限為16,777,216

DataTable可以存儲的最大行數為16,777,216。 有關更多信息,請參見將數據添加到數據表。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM