Entity Framework 6: есть ли способ перебирать таблицу, не удерживая каждую строку в памяти

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

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

Или есть способ получить DataReader из EF без прямого использования SQL?

Более подробный пример:

Используя EF, я могу кодировать:

foreach (Quote in context.Quotes)
   sw.WriteLine(sw.QuoteId.ToString()+","+sw.Quotation);

но для достижения того же результата с DataReader мне нужно закодировать:

// get the connection to the database
SqlConnection connection = context.Database.Connection as SqlConnection;

// open a new connection to the database
connection.Open();

// get a DataReader for our table
SqlCommand command = new SqlCommand(context.Quotes.ToString(), connection);
SqlDataReader dr = command.ExecuteReader();

// get a recipient for our database fields
object[] L = new object[dr.FieldCount];

while (dr.Read())
{
    dr.GetValues(L);
    sw.WriteLine(((int)L[0]).ToString() + "," + (string)L[1]);
}

Разница заключается в том, что первому не хватает памяти (поскольку он загружает всю таблицу в память клиента), а второму выполняется до завершения (и выполняется намного быстрее), поскольку в каждый момент времени сохраняется только одна строка в памяти.

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

Отсюда мой вопрос: можем ли мы получить аналогичный результат со строго типизированными строками, возвращающимися в EF?


person Mythlandia    schedule 17.07.2014    source источник
comment
вы хотите удалить объект из модели фреймворка без предварительной его загрузки?   -  person Yuliam Chandra    schedule 17.07.2014
comment
Нет. Я хочу обрабатывать строки и отбрасывать их, как только они будут обработаны.   -  person Mythlandia    schedule 17.07.2014
comment
как с помощью средства чтения данных, так и с итерацией строки из таблицы сущностей EF будет выполняться обход базы данных и хранение данных в памяти, вы хотите обрабатывать строки без извлечения данных из базы данных в память?   -  person Yuliam Chandra    schedule 17.07.2014
comment
Нет. Я хочу обрабатывать каждую строку и отбрасывать ее после обработки. DataReader сделает свое дело, но вернет мне нетипизированную строку; Я хотел бы получить обратно данные в виде строго типизированной строки. Мне нужна каждая строка из базы данных, но только одна строка за раз. Каждая строка читается, обрабатывается и больше не представляет интереса. Используя EF, он удерживает каждую строку (даже после обработки) и выдает исключение нехватки памяти.   -  person Mythlandia    schedule 17.07.2014
comment
Если предположить, что в таблице есть PK, почему вы можете обрабатывать данные фрагментами по 5 и 10 тысяч?   -  person Mark Kram    schedule 15.10.2018


Ответы (4)


Судя по вашему последнему комментарию, я все еще в замешательстве. Взгляните на оба приведенных ниже кода.

EF

using (var ctx = new AppContext())
{
    foreach (var order in ctx.Orders)
    {
        Console.WriteLine(order.Date);
    }
}

EF Profiler

Читатель данных

var constr = ConfigurationManager.ConnectionStrings["AppContext"].ConnectionString;
using (var con = new SqlConnection(constr))
{
    con.Open();    
    var cmd = new SqlCommand("select * from dbo.Orders", con);
    var reader = cmd.ExecuteReader();
    while (reader.Read())
    {
        Console.WriteLine(reader["Date"]);
    }
}

Профилировщик считывателя данных

Несмотря на то, что EF имеет несколько начальных запросов, оба они выполняют похожий запрос, что видно из профилировщика.

person Yuliam Chandra    schedule 17.07.2014
comment
OK. Поймите путаницу. Я изменю вопрос, чтобы дать более подробную информацию. Итог: меня не беспокоит извлечение строк из базы данных. Как вы говорите, в обоих случаях будет взято одинаковое количество строк с одинаковой стоимостью БД. НО когда таблица состоит из 5 000 000 строк (в моем простом примере), базу данных можно просмотреть за секунды, но моему приложению не хватает памяти, пытаясь удержать в памяти около 5-10 ГБ данных. Мне нужно хранить только ОДНУ строку за раз локально, а не всю таблицу. - person Mythlandia; 17.07.2014
comment
возьмите добычу в этот пост Кто-то уже упоминал, что EF выбирает только необходимые записи, но я пока не могу найти ссылку. - person Yuliam Chandra; 17.07.2014
comment
Проблема не в том, сколько записей извлекает EF: я хочу, чтобы он в конечном итоге извлекал КАЖДУЮ запись в таблице. Проблема в том, что моей клиентской программе нужна только ОДНА запись за раз из таблицы; ему не нужно сохранять остальные 5 000 000 записей в клиентской программе. См. Код из пересмотренного вопроса. Нет проблем, когда в таблице 5000 записей (память просто освобождается сразу после обхода списка); проблема в том, что таблица слишком велика, чтобы сразу поместиться в памяти клиента. Хуже того - это только ТЕСТОВЫЕ данные, база сильно вырастет в размерах. - person Mythlandia; 17.07.2014

Я не проверял, но попробуйте foreach (Quote L in context.Quotes.AsNoTracking()) {...}. .AsNoTracking() не должен помещать объекты в кеш, поэтому я предполагаю, что они будут использоваться GC, когда они выходят за рамки.

Другой вариант — использовать context.Entry(quote).State = EntityState.Detached; в цикле foreach. Должен иметь тот же эффект, что и вариант 1.

Третий вариант (определенно должен работать, но требует дополнительного кодирования) будет заключаться в реализации пакетной обработки (выбрать первые N объектов, обработать, выбрать следующие верхние N). В этом случае убедитесь, что вы удаляете и создаете новый контекст на каждой итерации (чтобы GC мог его съесть :)) и используете правильный OrderBy() в запросе.

person Vadim K.    schedule 17.07.2014
comment
Это тот, который работал для меня. Таблица с 3 634 943 строками со столбцом varchar(max), в котором мне пришлось обрабатывать текст, и установка AsNoTracking помогла. Спасибо. - person Tom Padilla; 03.12.2014

Вам нужно использовать EntityDataReader, который ведет себя аналогично традиционному ADO.NET DataReader.

Проблема в том, что для этого вам нужно использовать ObjectContext вместо DbContext, что усложняет задачу.

См. этот ответ SO, а не принятый: ">Как вернуть средство чтения данных при использовании Entity Framework 4?

Даже если это относится к EF4, в EF6 все работает точно так же. Обычно ORM не предназначен для потоковой передачи данных, поэтому этот функционал так скрыт.

Вы также можете ознакомиться с этим проектом: Entity Framework (Linq to Entities) to IDataReader Adapter

person JotaBe    schedule 17.07.2014

Я сделал это по страницам. И очистка контекста после каждой загрузки страницы.

Пример: загрузить первые 50 строк. Перебрать их. Очистить контекст или создать новый.

Загрузить вторые 50 строк...

Очистить контекст = установить все его записи как отсоединенные.

person Carlos Iglesias    schedule 07.03.2020