2015-10-02 4 views
0

Прежде чем это станет отмеченным как дубликат, я видел много таких ответов, как этот Convert IEnumerable to DataTable, и попытался сделать что-то подобное в способе создания метода расширения. Я задаю свой вопрос, поскольку проблема, которая может возникнуть, может лежать где-то в другом месте.Эффективный способ конвертировать из IEnumerable <T> в DataTable

По существу у меня довольно большие IEnumerable (около 16 - 17 пунктов мила) до этого момента я не действительно имел никаких проблем с этим, пока я не попытался преобразования к DataTable с помощью метода расширения:

/// <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? Я конвертирую в DataTable, поэтому я могу использовать SQLBulkCopy для получения данных в DataBase.

EDIT 0: Heres где данные пропускают через из

/// <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(); 
     } 
    } 

EDIT 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 секунд, больше идей по этому поводу? Может быть, каким-то образом увеличить продолжительность до таймаута?

+0

не использовать foreach на IEnumerable коллекция. –

+0

@ M.kazemAkhgary спасибо за ответ, можете ли вы объяснить, почему? – Srb1313711

+0

@ M.kazemAggary почему нет? Что бы вы предложили вместо этого? –

ответ

4

Вместо того, чтобы проходить через DataTable, я бы выполнил IDataReader для вашей коллекции и передал его в SqlBulkCopy. Если все сделано правильно и используя ленивый IEnumerable, это будет намного быстрее и будет использовать гораздо меньше памяти, чем путь к данным. Марк Гравелл уже написал такую ​​библиотеку для преобразования IEnumerables в IDataReader, и я бы порекомендовал вам проверить это, прежде чем сворачивать свои собственные.

FastMember можно найти на NuGet здесь: https://www.nuget.org/packages/FastMember/ с оригинальным источником здесь: https://code.google.com/p/fast-member/ с примером в этой теме здесь: SqlBulkCopy from a List<>

UPDATE: Вам также может понадобиться изменить команду тайм-аут, и установить размер партии на SqlBulkCopy, как это:

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

и

bcp.BatchSize = 100000; 
+0

Спасибо за это, но, пожалуйста, см. Редактировать 1 – Srb1313711

+0

Это другой вопрос, так как это, скорее всего, команда, которую вы запускаете после того, как вставка вставки исчерпана. Просто добавьте 'cmd.CommandTimeout = 300;' before 'cmd.ExecuteNonQuery()' –

+0

Или вы можете изменить 'CommandType = CommandType.StoredProcedure' на' CommandType = CommandType.StoredProcedure, CommandTimeout = 300' в своем операторе using как Что ж. 300 - это всего лишь пример, но это 5-минутный тайм-аут. –

1

С проблемами производительности возникают трудности с конкретными исправлениями, но я бы удалил все, что вам действительно не нужно, начиная с вызовов BeginEdit() и EndEdit(). Вы создаете новую строку, если вам не требуется явное rowstate для чего-то, что не изображено в вашем вопросе, тогда они делают лишние вещи, которые вам, вероятно, не нужны.

Еще одна вещь, которую можно попробовать - это разделить коллекцию на куски, а затем использовать Parallel.For/Foreach для создания таблицы данных для каждого фрагмента, а затем использовать DataTable.Merge(), чтобы объединить их обратно и вернуть результат.

1

Не конвертировать. DataTalble выберет время и является болотом памяти. Вы можете использовать TVP (Table Value Parameter) для загрузки очень быстро. Это похоже на обратный считыватель данных. Для перехода из IEnumable (а не DataTable) используйте SqlDataRecord.
just one link - search on TVP SqlDataRecord

+1

Давай посмотрим, в чем проблема? Я использую это, чтобы загружать миллионы и миллионы записей очень быстро. – Paparazzi

0

По существу у меня довольно большой IEnumerable (около 16 - 17 мил элементов) до этого момента я не действительно имел никаких проблем с этим, , пока я не попытался преобразовать к DataTable с помощью расширения метод:

в соответствии с documentation, верхний предел строк в DataTable является 16,777,216

Максимальное количество строк, которые может хранить DataTable, составляет 16 777 216. Дополнительные сведения см. В разделе Добавление данных в DataTable.

+0

Благодарим вас за эту информацию, однако я все еще получаю свои проблемы всего с 5 миллионами – Srb1313711

Смежные вопросы