Mi imbatto nella cosa più strana che non riesco a capire. Ho una tabella SQL con un mucchio di report memorizzati in un campo ntext. Quando ho copiato e incollato il valore di uno di loro nel blocco note e l'ho salvato (usato Visual Studio per prendere il valore da un report più piccolo in una riga differente), il file txt non elaborato era di circa 5Mb. Quando provo a ottenere questi stessi dati usando SqlDataReader e lo converto in una stringa, ottengo un'eccezione di memoria insufficiente. Ecco come sto provando a farlo:Memoria insufficiente durante la lettura di una stringa da 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();
Ho cercato di creare un oggetto e uno StringBuilder per afferrare i dati, ma ho ancora la stessa di un'eccezione di memoria. Ho anche provato a utilizzare reader.GetValue (0) .ToString() e senza alcun risultato. La query restituisce solo 1 riga e quando viene eseguita in SQL Management Studio è la più felice possibile.
L'eccezione generata è:
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
avevo provato con altri numeri di riga che sembravano funzionare, ma che era un falso positivo, come quelli di test ID avuto alcun dato. Ho estratto alcuni altri ID di test dopo aver esaminato la tabella che contiene report che sono quasi identici e ottengo la stessa eccezione. Forse è come la stringa è codificata? I dati memorizzati nella tabella sono una stringa codificata JSON che è stata generata da una classe davvero gnarly che ho creato da qualche altra parte, nel caso ciò sia d'aiuto.
Ecco il blocco di codice precedente:
// 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 è un dizionario che ottiene il CompiledReportTimeTypeID corretta sulla base di un parametro di stringa che viene alimentato all'inizio del metodo. ReportTime è un DateTime che viene inserito in precedenza.
Modifica: Ho intenzione di rilasciare la tabella e ricrearla con il campo ReportData come nvarchar (MAX) anziché ntext, solo per escludere un problema di tipo di dati SQL. È una soluzione lunga e aggiornerò di nuovo con quello che trovo.
Edit2: La modifica del campo nella tabella in nvarchar (max) non ha avuto alcun effetto. Ho anche provato a usare output = cmd.ExecuteScalar(). ToString(), senza alcun impatto. Sto cercando di vedere se c'è una dimensione massima per SqlDataReader. Quando ho copiato il valore del testo da SQL Mgmt Studio, era solo 43Kb quando salvato nel blocco note. Per verificarlo, ho tirato un report con un ID funzionante noto (un report più piccolo) e quando ho copiato il valore direttamente da Visual Studio e l'ho scaricato nel blocco note era di circa 5 MB! Ciò significa che questi grandi report sono probabilmente nell'intervallo ~ 20 MB seduto in un campo nvarchar (massimo).
Edit3: Ho riavviato tutto, per includere il mio server IIS di dev, il server SQL e il mio portatile di sviluppo. Ora sembra che funzioni. Questa non è la risposta al motivo per cui questo è successo però. Lascio questa domanda aperta per le spiegazioni su quello che è successo, e segnerò una di quelle come risposta.
Edit4: Detto questo, ho eseguito un altro test senza modificare nulla e la stessa eccezione è stata restituita. Sto davvero iniziando a pensare che questo è un problema SQL. Sto aggiornando i tag su questa domanda. Ho creato un'app separata che esegue esattamente la stessa query e funziona correttamente.
Edit5: Ho implementato l'accesso sequenziale come da una delle risposte di seguito. Tutto viene letto correttamente in un flusso, ma quando provo a scriverlo su una stringa sto ancora ricevendo l'eccezione di memoria insufficiente. Indicherebbe il problema di ottenere un blocco contiguo di memoria?Ecco come ho implementato il buffer:
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: Per aggiungere a questo, quando eccezione OOM accade vedo il processo di lavoro IIS (che detiene il metodo che esegue) ha colpito quasi 700 MB. Questo è in esecuzione su IIS Express e non su IIS completo sul server di produzione. Questo dovrebbe avere qualcosa a che fare con questo? Anche quando chiamo Byte [] data = stream.ToArray() ottengo a intermittenza anche l'OOM. Penso che quello di cui ho veramente bisogno sia un modo per dare più memoria a questo processo, ma non so dove configurarlo.
Edit7: Ho appena modificato il mio server di sviluppo utilizzando IIS Express sul mio computer locale sul server Web di Visual Studio incorporato. L'eccezione di OOM ora è andata. Penso davvero che sia stato l'allocazione di un blocco contiguo di problemi di memoria, e per qualsiasi motivo IIS Express non lo rinviasse. Ora che sta funzionando bene, pubblicherò sul mio server completo su 2008R2 che esegue il normale IIS7 per vedere come va.
È necessario includere anche il messaggio di errore completo. –
Quanto è grande la stringa che viene restituita? In altre parole, quanto è grande ReportData? –
Mostra la traccia stack completa dell'eccezione. –