Недостаточно памяти при создании большого количества объектов С#

Я обрабатываю 1 миллион записей в своем приложении, которые я извлекаю из базы данных MySQL. Для этого я использую Linq для получения записей и использования .Skip() и .Take() для обработки 250 записей за раз. Для каждой полученной записи мне нужно создать от 0 до 4 элементов, которые я затем добавляю в базу данных. Таким образом, среднее количество общих элементов, которые должны быть созданы, составляет около 2 миллионов.

IQueryable<Object> objectCollection = dataContext.Repository<Object>();
int amountToSkip = 0;
IList<Object> objects = objectCollection.Skip(amountToSkip).Take(250).ToList();
while (objects.Count != 0)
        {
            using (dataContext = new LinqToSqlContext(new DataContext()))
            {
                foreach (Object objectRecord in objects)
                {
                    // Create 0 - 4 Random Items
                    for (int i = 0; i < Random.Next(0, 4); i++)
                    {
                        Item item = new Item();
                        item.Id = Guid.NewGuid();
                        item.Object = objectRecord.Id;
                        item.Created = DateTime.Now;
                        item.Changed = DateTime.Now;
                        dataContext.InsertOnSubmit(item);
                    }
                }
                dataContext.SubmitChanges();
            }
            amountToSkip += 250;
            objects = objectCollection.Skip(amountToSkip).Take(250).ToList();
        }

Теперь возникает проблема при создании элементов. При запуске приложения (и даже без использования dataContext) память постоянно увеличивается. Это похоже на то, что предметы никогда не попадают. Кто-нибудь замечает, что я делаю неправильно?

Спасибо заранее!

Ответы

Ответ 1

Хорошо, я только что обсуждал эту ситуацию с моим коллегой, и мы пришли к следующему решению, которое работает!

int amountToSkip = 0;
var finished = false;
while (!finished)
{
      using (var dataContext = new LinqToSqlContext(new DataContext()))
      {
           var objects = dataContext.Repository<Object>().Skip(amountToSkip).Take(250).ToList();
           if (objects.Count == 0)
                finished = true;
           else
           {
                foreach (Object object in objects)
                {
                    // Create 0 - 4 Random Items
                    for (int i = 0; i < Random.Next(0, 4); i++)
                    {
                        Item item = new Item();
                        item.Id = Guid.NewGuid();
                        item.Object = object.Id;
                        item.Created = DateTime.Now;
                        item.Changed = DateTime.Now;
                        dataContext.InsertOnSubmit(item);
                     }
                 }
                 dataContext.SubmitChanges();
            }
            // Cumulate amountToSkip with processAmount so we don't go over the same Items again
            amountToSkip += processAmount;
        }
}

В этой реализации мы каждый раз используем кеш Skip() и Take() и, таким образом, не утечка памяти!

Ответ 2

Ahhh, хорошая старая утечка памяти InsertOnSubmit. Я столкнулся с этим и много раз ударился головой о стену, пытаясь загрузить данные из больших CVS файлов, используя LINQ to SQL. Проблема в том, что даже после вызова SubmitChanges, DataContext продолжает отслеживать все объекты, которые были добавлены с помощью InsertOnSubmit. Решение будет SubmitChanges после определенного количества объектов, а затем создайте новый DataContext для следующей партии. Когда старый DataContext собирает мусор, так и все вставленные объекты, которые его отслеживают (и что вам больше не требуется).

"Но подождите!" вы говорите: "Создание и удаление многих DataContext будет иметь огромные накладные расходы!". Нет, если вы создадите одно соединение с базой данных и передаете его каждому конструктору DataContext. Таким образом, единое соединение с базой данных поддерживается повсюду, а объект DataContext в противном случае является легким объектом, который представляет собой небольшой рабочий блок и должен быть отброшен после его завершения (в вашем примере, отправив определенное количество записей).

Ответ 3

Мое лучшее предположение: IQueryable может вызвать утечку памяти. Может быть, нет подходящей реализации для MySQL методов Take/Skip, и он выполняет пейджинг в памяти? Незнакомые вещи произошли, но ваша петля выглядит хорошо. Все ссылки должны выходить за рамки и собирать мусор.

Ответ 4

Ну да.

Итак, в конце этого цикла вы попытаетесь иметь 2 миллиона элементов в своем списке, нет? Мне кажется, что ответ тривиален: Храните меньше предметов или получите больше памяти.

- Изменить:

Возможно, я прочитал это неправильно, мне, вероятно, нужно будет скомпилировать и протестировать его, но я не могу этого сделать сейчас. Я оставлю это здесь, но я могу ошибаться, я недостаточно тщательно рассмотрел его, чтобы быть окончательным, но ответ может оказаться полезным, или нет. (Судя по нисходящему, я не думаю: P)

Ответ 5

Попробовали ли вы объявить элемент вне цикла следующим образом:

IQueryable<Object> objectCollection = dataContext.Repository<Object>();
int amountToSkip = 0;
IList<Object> objects = objectCollection.Skip(amountToSkip).Take(250).ToList();
Item item = null;
while (objects.Count != 0)
        {
            using (dataContext = new LinqToSqlContext(new DataContext()))
            {
                foreach (Object objectRecord in objects)
                {
                    // Create 0 - 4 Random Items
                    for (int i = 0; i < Random.Next(0, 4); i++)
                    {
                        item = new Item();
                        item.Id = Guid.NewGuid();
                        item.Object = objectRecord.Id;
                        item.Created = DateTime.Now;
                        item.Changed = DateTime.Now;
                        dataContext.InsertOnSubmit(item);
                    }
                }
                dataContext.SubmitChanges();
            }
            amountToSkip += 250;
            objects = objectCollection.Skip(amountToSkip).Take(250).ToList();
        }