2013-11-06 5 views
7

Я пытаюсь вытащить массив данных с большим объемом (1,4 миллиона записей) с сервера SQL и дамп в файл в приложении WinForms. Я попытался сделать это с помощью подкачки, так что я не слишком много занимаюсь памятью, но процесс продолжает расти, так как он работает с памятью. Примерно на 25% он занимал 600 000 К. Неужели я делаю пейджинг неправильно? Могу ли я получить некоторые предложения о том, как увеличить использование памяти?Как уменьшить объем памяти с большими наборами данных в EF5?

var query = (from organizations in ctxObj.Organizations 
       where organizations.org_type_cd == 1 
       orderby organizations.org_ID 
       select organizations); 
int recordCount = query.Count(); 
int skipTo = 0; 
int take = 1000; 
if (recordCount > 0) 
{ 
    while (skipTo < recordCount) 
    { 
     if (skipTo + take > recordCount) 
      take = recordCount - skipTo; 

     foreach (Organization o in query.Skip(skipTo).Take(take)) 
     { 
      writeRecord(o); 
     } 
     skipTo += take; 
    } 
} 
+2

Эта строка 'int recordCount = query.Count();' переносит весь «набор данных» в память. После этого пейджинг не выполняет ничего полезного. –

+1

@KeithPayne Я думаю, что счетчик не возвращает все записи, но выполняет SQL COUNT – Gregoire

+3

@KeithPayne, вызов 'Count' не выводит его в память. Запустите профиль SQL, и вы увидите, что он выполняет скаляр, вычисляя количество в БД, используя SQL. –

ответ

5

Избавьтесь от пейджинговой и использовать AsNoTracking.

Код проверки

static void Main(string[] args) 
     { 
      var sw = new Stopwatch(); 
      sw.Start(); 
      using (var context = new MyEntities()) 
      { 
       var query = (from organizations in context.LargeSampleTable.AsNoTracking() 
          where organizations.ErrorID != null 
          orderby organizations.ErrorID 
          select organizations);//large sample table, 146994 rows 

       foreach (MyObject o in query) 
       { 
        writeRecord(o); 
       } 

      } 
      sw.Stop(); 

      Console.WriteLine("Completed after: {0}", sw.Elapsed); 
      Console.ReadLine(); 
     } 

     private static void writeRecord(ApplicationErrorLog o) 
     { 
      ; 
     } 

Test Case Результат:

Потребление

памяти снижена: 96%
Время выполнения уменьшено: 50%

В terpretation

AsNoTracking обеспечивает преимущества использования памяти по очевидным причинам, нам не нужно поддерживать ссылки на объекты, когда мы загружаем их в память.Объекты GC лигируются почти сразу. Комбинируйте lazy evaluation и AsNoTracking, и нет необходимости в пейджинге, а разрушение контекста может быть отложено.

Хотя это единственный тест, большое количество строк и исключение большинства внешних факторов делают это хорошим представлением для общего случая.

+1

Это похоже на исправление. Благодаря! –

0

Почему бы вам просто не использовать стандартный класс System.Data.SqlClient.SqlConnection? Вы можете прочитать результаты командной строки за строкой, используя класс SqlDataReader и записать каждую строку в файл. У вас есть полный контроль над тем, чтобы ваш код ссылался только на одну строку записей за раз.

using (var writer = new System.IO.StreamWriter(fileName)) 
using (var conn = new SqlConnection(connectionString)) 
{ 
    using (var cmd = new SqlCommand()) 
    { 
     cmd.CommandText = "SELECT * FROM Organizations WHERE org_type_cd = 1 ORDER BY org_ID"; 

     using (var reader = cmd.ExecuteReader()) 
     { 
      while (reader.Read()) 
      { 
       int id = (int)reader["org_ID"]; 
       int org_type_cd = (int)reader["org_type_cd"]; 

       writer.WriteLine(...); 
      } 
     } 
    } 
} 

Entity Framework не предназначен для решения проблемы ни одной конкретной организации передачи данных. Он предназначен для упрощения написания простых операций CRUD. Работа с миллионами строк является хорошим вариантом для более специализированного решения.

8

Контекст объекта будет хранить объекты в памяти до тех пор, пока он не будет удален. Я бы рекомендовал избавиться от контекста после каждой партии, чтобы предотвратить рост памяти.

Вы также можете использовать AsNoTracking() (http://msdn.microsoft.com/en-us/library/gg679352(v=vs.103).aspx), так как вы не сохраняете обратно в базу данных.

+0

Этот ответ был хороший, но @ P.Brian.Mackey привел пример и дополнительную информацию, поэтому я принял его ответ. –

1

Несколько вещей.

  1. Выполнение запроса Count(). Затем вы запускаете его во второй раз, чтобы получить результаты. Вам не нужно это делать.

  2. Память, которую вы видите, связана с загрузкой объектов в память. Если вам нужно только подмножество полей, проецируйте на анонимный тип (или более простой именованный тип). Это позволит избежать отслеживания изменений и накладных расходов.

Используется таким образом, EF может быть хорошим типизированным API для облегченных SQL-запросов.

Что-то вроде этого следует сделать трюк:

var query = from organizations in ctxObj.Organizations 
      where organizations.org_type_cd == 1 
      orderby organizations.org_ID 
      select new { o.Id, o.Name }; 

foreach (var org in query) 
{ 
    write(org.Id, org.Name); 
} 
+0

И положите ToArray() после выбора, чтобы как можно быстрее закрыть свой datareader – Gregoire

+1

Просто примечание: я не думаю, что Count() перечисляет в этом случае - кажется логичным, что он просто переводит это в 'SELECT COUNT (*) '(ведь провайдер определяет, как должно интерпретироваться дерево выражений), что должно быть справедливым бит быстрее, чем перечисление всего набора данных. Он все еще работает * что-то *, хотя – Charleh

+0

@Charleh. Вы правы. Он выполняет два запроса, но первый запрос - это просто счет. Таким образом, он не загружает никаких объектов в управляемую память. Он возвращает ровно 1 запись. –

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