Я сталкиваюсь с самой странной вещью, которую я не могу понять. У меня есть таблица SQL с кучей отчетов, хранящихся в поле ntext. Когда я скопировал и вставил значение одного из них в блокнот и сохранил его (использовал Visual Studio для захвата значения из меньшего отчета в другой строке), необработанный txt файл был около 5 Мб. Когда я пытаюсь получить эти же данные с помощью SqlDataReader и преобразовать его в строку, я получаю исключение из памяти. Вот как я пытаюсь это сделать:
string output = "";
string cmdtext = "SELECT ReportData FROM Reporting_Compiled WHERE CompiledReportTimeID = @CompiledReportTimeID";
SqlCommand cmd = new SqlCommand(cmdtext, conn);
cmd.Parameters.Add(new SqlParameter("CompiledReportTimeID", CompiledReportTimeID));
SqlDataReader reader = cmd.ExecuteReader();
while (reader.Read())
{
output = reader.GetString(0); // <--- exception happens here
}
reader.Close();
Я попытался создать объект и построитель строк для захвата данных, но я все равно получаю исключение из памяти. Я также пробовал использовать reader.GetValue(0).ToString(), но безрезультатно. Запрос возвращает только 1 строку, и когда я запускаю ее в SQL Management Studio, она будет такой же счастливой, как может быть.
Исключение составляет:
System.OutOfMemoryException was unhandled by user code
Message=Exception of type 'System.OutOfMemoryException' was thrown.
Source=mscorlib
StackTrace:
at System.String.CreateStringFromEncoding(Byte* bytes, Int32 byteLength, Encoding encoding)
at System.Text.UnicodeEncoding.GetString(Byte[] bytes, Int32 index, Int32 count)
at System.Data.SqlClient.TdsParserStateObject.ReadString(Int32 length)
at System.Data.SqlClient.TdsParser.ReadSqlStringValue(SqlBuffer value, Byte type, Int32 length, Encoding encoding, Boolean isPlp, TdsParserStateObject stateObj)
at System.Data.SqlClient.TdsParser.ReadSqlValue(SqlBuffer value, SqlMetaDataPriv md, Int32 length, TdsParserStateObject stateObj)
at System.Data.SqlClient.SqlDataReader.ReadColumnData()
at System.Data.SqlClient.SqlDataReader.ReadColumn(Int32 i, Boolean setTimeout)
at System.Data.SqlClient.SqlDataReader.GetString(Int32 i)
at Reporting.Web.Services.InventoryService.GetPrecompiledReportingData(DateTime ReportTime, String ReportType) in C:\Projects\Reporting\Reporting.Web\Services\InventoryService.svc.cs:line 3244
at SyncInvokeGetPrecompiledReportingData(Object , Object[] , Object[] )
at System.ServiceModel.Dispatcher.SyncMethodInvoker.Invoke(Object instance, Object[] inputs, Object[]& outputs)
at System.ServiceModel.Dispatcher.DispatchOperationRuntime.InvokeBegin(MessageRpc& rpc)
InnerException:
null
Я тестировал с другими номерами строк, которые, казалось, работали, но это было ложным положительным, так как у идентификатора теста не было данных. Я вытащил еще один идентификатор теста, посмотрев на таблицу, содержащую отчеты, которые почти идентичны, и я получаю то же исключение. Может быть, как кодируется строка? Данные, хранящиеся в таблице, представляют собой закодированную в JSON строку, которая была создана из действительно gnarly класса, который я сделал где-то еще, в случае, если это помогает.
Вот предыдущий кодовый блок:
// get the report time ID
int CompiledReportTimeTypeID = CompiledReportTypeIDs[ReportType];
int CompiledReportTimeID = -1;
cmdtext = "SELECT CompiledReportTimeID FROM Reporting_CompiledReportTime WHERE CompiledReportTimeTypeID = @CompiledReportTimeTypeID AND CompiledReportTime = @ReportTime";
cmd = new SqlCommand(cmdtext, conn);
cmd.Parameters.Add(new SqlParameter("CompiledReportTimeTypeID", CompiledReportTimeTypeID));
cmd.Parameters.Add(new SqlParameter("ReportTime", ReportTime));
reader = cmd.ExecuteReader();
while (reader.Read())
{
CompiledReportTimeID = Convert.ToInt32(reader.GetValue(0));
}
reader.Close();
CompiledReportTypeIDs - это словарь, который получает правильный CompiledReportTimeTypeID на основе строкового параметра, который подается в начале метода. ReportTime - это DateTime, который загружается ранее.
Изменить: Я собираюсь отказаться от таблицы и воссоздать ее с полем ReportData как nvarchar (MAX) вместо ntext, чтобы исключить проблему с типом данных SQL. Это длинный выстрел, и я снова обновлю, что я нахожу.
Edit2: Изменение поля в таблице на nvarchar (max) не повлияло. Я также попытался использовать output = cmd.ExecuteScalar(). ToString() также без какого-либо воздействия. Я пытаюсь посмотреть, есть ли максимальный размер для SqlDataReader. Когда я скопировал значение текста из SQL Mgmt Studio, это было всего 43 КБ, если сохранить его в блокноте. Чтобы проверить это, я вытащил отчет с известным рабочим идентификатором (меньший отчет), и когда я скопировал значение прямо из Visual Studio и сбросил его в блокнот, это было около 5 МБ! Это означает, что эти большие отчеты, вероятно, находятся в диапазоне ~ 20 МБ, сидя в поле nvarchar (max).
Edit3: Я перезагрузил все, чтобы включить мой сервер IIS, сервер SQL и мой ноутбук. Теперь это работает. Это не ответ, почему это произошло. Я оставляю этот вопрос открытым для объяснений относительно того, что произошло, и я отмечу один из них в качестве ответа.
Edit4: Сказав это, я провел еще один тест, не меняя вещи, и то же исключение вернулось. Я действительно начинаю думать, что это проблема SQL. Я обновляю теги по этому вопросу. Я сделал отдельное приложение, которое запускает тот же самый запрос и работает нормально.
Edit5: Я выполнил последовательный доступ в соответствии с одним из ответов ниже. Все правильно считывается в поток, но когда я пытаюсь записать его в строку, я все равно получаю исключение из памяти. Означает ли это проблему получения непрерывного блока памяти? Вот как я реализовал буферизацию:
reader = cmd.ExecuteReader(CommandBehavior.SequentialAccess);
long startIndex = 0;
long retval = 0;
int bufferSize = 100;
byte[] buffer = new byte[bufferSize];
MemoryStream stream = new MemoryStream();
BinaryWriter writer = new BinaryWriter(stream);
while (reader.Read())
{
// Reset the starting byte for the new CLOB.
startIndex = 0;
// Read bytes into buffer[] and retain the number of bytes returned.
retval = reader.GetBytes(0, startIndex, buffer, 0, bufferSize);
// Continue while there are bytes beyond the size of the buffer.
while (retval == bufferSize)
{
writer.Write(buffer);
writer.Flush();
// Reposition start index to end of last buffer and fill buffer.
startIndex += bufferSize;
retval = reader.GetBytes(0, startIndex, buffer, 0, bufferSize);
}
//output = reader.GetString(0);
}
reader.Close();
stream.Position = 0L;
StreamReader sr = new StreamReader(stream);
output = sr.ReadToEnd(); <---- Exception happens here
//output = new string(buffer);
Edit6: Чтобы добавить к этому, когда произойдет исключение OOM, я вижу, что рабочий процесс IIS (который поддерживает запущенный метод) поражает почти 700 МБ. Это работает на IIS Express, а не на полном IIS на производственном сервере. Будет ли это иметь к этому какое-либо отношение? Также, когда я вызываю Byte [] data = stream.ToArray(), я периодически получаю OOM. Я думаю, что мне действительно нужен способ предоставить больше памяти этому процессу, но я не знаю, где его настроить.
Edit7: Я только что изменил мой сервер-dev с помощью IIS Express на моем локальном компьютере на встроенный веб-сервер Visual Studio. Исключение OOM теперь исчезло. Я действительно думаю, что это было выделение непрерывного блока памяти, и по какой-либо причине IIS Express не разветвлял его. Теперь, когда он работает нормально, я опубликую на моем полномасштабном сервере в 2008R2, запускающем обычный IIS7, чтобы посмотреть, как это происходит.