NHibernate OutOfMemoryException, запрашивающий большой байт []
Я пытаюсь использовать Fluent NHibernate для переноса базы данных, для которой требуется массажирование базы данных. Исходная база данных - это база данных MS Access, и текущая таблица, на которую я застрял, - это поле с полем OLE Object. Целевая база данных - это база данных MS SQL Server Express.
В объекте я просто имел это поле, определенное как byte[]
, однако при загрузке, даже если просто загружать это одиночное поле для одной записи, я попадал в System.OutOfMemoryException
byte[] test = aSession.Query<Entities.Access.Revision>().Where(x => x.Id == 5590).Select(x => x.FileData).SingleOrDefault<byte[]>();
Затем я попытался реализовать тип blob, указанный здесь, но теперь при запуске я получаю сообщение об ошибке:
"Невозможно передать объект типа 'System.Byte []' для ввода типа 'TestProg.DatabaseConverter.Entities.Blob'." }
Я не могу себе представить, что Ole Object больше 100 МБ, но не смог проверить. Есть ли хороший способ использования Fluent NHibernate для копирования этого из одной базы данных и сохранения его в другой или мне нужно посмотреть другие параметры?
Мой обычный цикл для обработки:
IList<Entities.Access.Revision> result;
IList<int> recordIds = aSession.Query<Entities.Access.Revision>().Select(x => x.Id).ToList<int>();
foreach (int recordId in recordIds)
{
result = aSession.Query<Entities.Access.Revision>().Where(x => x.Id == recordId).ToList<Entities.Access.Revision>();
Save(sqlDb, result);
}
Функция сохранения просто копирует свойства из одного в другой и для некоторых объектов используется для управления данными или предоставления обратной связи пользователю, связанному с проблемами данных. Я использую сеансы без состояния для обеих баз данных.
-
От дальнейшего тестирования объекты, которые, по-видимому, висят, составляют около 60-70 мб. В настоящее время я тестирую захват данных с помощью OleDbDataReader с помощью GetBytes.
-
Обновление (24 ноября): Мне еще предстоит найти способ заставить это работать с NHibernate. Я получил эту работу с регулярными объектами команды db. Я поставил код для функции, которую я сделал ниже, для кого-то любопытного, кто это нашел. Это код из моего конвертера базы данных, поэтому объекты с префиксом 'a' являются объектами базы данных доступа и 's' являются sql-тегами.
public void MigrateBinaryField(int id, string tableName, string fieldName)
{
var aCmd = new OleDbCommand(String.Format(@"SELECT ID, {0} FROM {1} WHERE ID = {2}", fieldName, tableName, id), aConn);
using (var reader = aCmd.ExecuteReader(System.Data.CommandBehavior.SequentialAccess))
{
while (reader.Read())
{
if (reader[fieldName] == DBNull.Value)
return;
long read = 0;
long offset = 0;
// Can't .WRITE a NULL column so need to set an initial value
var sCmd = new SqlCommand(string.Format(@"UPDATE {0} SET {1} = @data WHERE OldId = @OldId", tableName, fieldName), sConn);
sCmd.Parameters.AddWithValue("@data", new byte[0]);
sCmd.Parameters.AddWithValue("@OldId", id);
sCmd.ExecuteNonQuery();
// Incrementally store binary field to avoid OutOfMemoryException from having entire field loaded in memory
sCmd = new SqlCommand(string.Format(@"UPDATE {0} SET {1}.WRITE(@data, @offset, @len) WHERE OldId = @OldId", tableName, fieldName), sConn);
while ((read = reader.GetBytes(reader.GetOrdinal(fieldName), offset, buffer, 0, buffer.Length)) > 0)
{
sCmd.Parameters.Clear();
sCmd.Parameters.AddWithValue("@data", buffer);
sCmd.Parameters.AddWithValue("@offset", offset);
sCmd.Parameters.AddWithValue("@len", read);
sCmd.Parameters.AddWithValue("@OldId", id);
sCmd.ExecuteNonQuery();
offset += read;
}
}
}
}
Ответы
Ответ 1
Это похоже на результаты, которые я видел при использовании .NET поверх других фреймворков.
Драйвер родной базы данных под ADO.NET под NHibernate (здесь два предвзятых места) потребует заблокированный блок памяти назначения, который нельзя перенести в память, когда драйвер заполняет его. Поскольку сборщик мусора .NET может случайным образом перемещать блоки памяти на отдельный поток, чтобы скомбинировать кучи, NHibernate, лежащий в основе слоя базы данных .NET, должен создать блок памяти без управления для приема данных, что эффективно удваивает объем памяти требуется для загрузки записи.
Кроме того, я не проверил этот следующий момент, но NHibernate должен попытаться кэшировать блоки записей, поскольку он обходит некоторые из операций запроса реляционной базы данных. Это позволяет NHibernate делать меньше запросов к базе данных, что является оптимальным для небольших размеров записей, но требует много записей (в том числе много блоков) для размещения в памяти одновременно.
В качестве первого шага к разрешению убедитесь, что процесс действительно запустил машину из памяти (или если она 32-разрядная, убедитесь, что она нажимает ограничение 2 ГБ). Если да, попытайтесь определить базовую линию - если она обрабатывает записи с различными размерами блобов, какова минимальная и максимальная память, которые она использует? Из этого вы можете оценить, сколько памяти потребуется для этой большой записи (или блока кэша, который содержит эту запись!)
64-разрядная и более физическая память может быть решением грубой силы, если вы еще не используете 64-разрядную версию, и если больше аппаратного обеспечения - даже вариант.
Еще одно возможное решение - проверить, имеет ли NHibernate настраиваемые параметры или свойства для кэширования данных. Например, проверьте, можете ли вы установить свойство, которое ограничивает количество записей, загружаемых за раз, или сообщите, чтобы он ограничивал свой кеш до определенного размера в байтах.
Более эффективное решение - использовать код ADO.NET для blobs; это может быть лучшим решением, особенно если вы ожидаете даже больших капель, чем этот конкретный 60-70MB blob. MS Access обычно допускает несколько подключений только для чтения, поэтому это должно работать до тех пор, пока NHibernate не устанавливает базу данных для блокировки других подключений.
Ответ 2
Я сильно подозреваю, что это накопление из-за кэша сеанса NHibernate.
Попробуйте прочитать каждый BLOB-объект в отдельном сеансе или хотя бы периодически очищать/очищать его, добавляя в цикл счетчик "i" и условие, подобное
if (i % 10 == 0)
{
aSession.Flush();
aSession.Clear();
}