Mémoire insuffisante lors de la lecture d’une chaîne à partir de SqlDataReader

Je tombe sur la chose la plus étrange que je n’arrive pas à comprendre. J’ai une table SQL avec un tas de rapports stockés dans un champ ntext. Lorsque j’ai copié et collé la valeur de l’un d’eux dans le bloc-notes et que je l’ai enregistré (avec Visual Studio pour extraire la valeur d’un rapport plus petit dans une ligne différente), le fichier txt brut faisait environ 5 Mo. Lorsque j’essaie d’obtenir ces mêmes données à l’aide de SqlDataReader et que je les convertis en chaîne, j’obtiens une exception de mémoire insuffisante. Voici comment j’essaie de le faire:

ssortingng output = ""; ssortingng cmdtext = "SELECT ReportData FROM Reporting_Comstackd WHERE ComstackdReportTimeID = @ComstackdReportTimeID"; SqlCommand cmd = new SqlCommand(cmdtext, conn); cmd.Parameters.Add(new SqlParameter("ComstackdReportTimeID", ComstackdReportTimeID)); SqlDataReader reader = cmd.ExecuteReader(); while (reader.Read()) { output = reader.GetSsortingng(0); // <--- exception happens here } reader.Close(); 

J’ai essayé de créer un object et un constructeur de chaînes pour récupérer les données, mais j’ai toujours la même exception de mémoire insuffisante. J’ai aussi essayé d’utiliser reader.GetValue (0) .ToSsortingng () aussi sans succès. La requête ne renvoie qu’une ligne et lorsque je l’exécute dans SQL Management Studio, elle est aussi heureuse que possible.

L’exception levée est:

 System.OutOfMemoryException was unhandled by user code Message=Exception of type 'System.OutOfMemoryException' was thrown. Source=mscorlib StackTrace: at System.Ssortingng.CreateSsortingngFromEncoding(Byte* bytes, Int32 byteLength, Encoding encoding) at System.Text.UnicodeEncoding.GetSsortingng(Byte[] bytes, Int32 index, Int32 count) at System.Data.SqlClient.TdsParserStateObject.ReadSsortingng(Int32 length) at System.Data.SqlClient.TdsParser.ReadSqlSsortingngValue(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.GetSsortingng(Int32 i) at Reporting.Web.Services.InventoryService.GetPrecomstackdReportingData(DateTime ReportTime, Ssortingng ReportType) in C:\Projects\Reporting\Reporting.Web\Services\InventoryService.svc.cs:line 3244 at SyncInvokeGetPrecomstackdReportingData(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 

J’avais testé avec d’autres numéros de ligne qui semblaient fonctionner, mais c’était un faux positif car ces ID de test ne contenaient aucune donnée. Après avoir jeté un coup d’œil au tableau, j’ai tiré d’autres ID de test contenant des rapports presque identiques, et j’ai la même exception. Peut-être que c’est comment la chaîne est encodée? Les données stockées dans la table sont une chaîne codée JSON qui a été générée à partir d’une classe vraiment dérisoire que j’ai créée ailleurs, au cas où cela serait utile.

Voici le bloc de code précédent:

 // get the report time ID int ComstackdReportTimeTypeID = ComstackdReportTypeIDs[ReportType]; int ComstackdReportTimeID = -1; cmdtext = "SELECT ComstackdReportTimeID FROM Reporting_ComstackdReportTime WHERE ComstackdReportTimeTypeID = @ComstackdReportTimeTypeID AND ComstackdReportTime = @ReportTime"; cmd = new SqlCommand(cmdtext, conn); cmd.Parameters.Add(new SqlParameter("ComstackdReportTimeTypeID", ComstackdReportTimeTypeID)); cmd.Parameters.Add(new SqlParameter("ReportTime", ReportTime)); reader = cmd.ExecuteReader(); while (reader.Read()) { ComstackdReportTimeID = Convert.ToInt32(reader.GetValue(0)); } reader.Close(); 

ComstackdReportTypeIDs est un dictionnaire qui obtient le correct ComstackdReportTimeTypeID en fonction d’un paramètre de chaîne introduit au début de la méthode. ReportTime est un DateTime introduit plus tôt.

Edit: je vais supprimer la table et la recréer avec le champ ReportData sous la forme nvarchar (MAX) au lieu de ntext, simplement pour éliminer un problème de type de données SQL. C’est un long plan et je vais mettre à jour à nouveau avec ce que je trouve.

Edit2: Modifier le champ de la table en nvarchar (max) n’a eu aucun effet. J’ai aussi essayé d’utiliser output = cmd.ExecuteScalar (). ToSsortingng () également, sans impact. J’essaie de voir s’il existe une taille maximale pour SqlDataReader. Lorsque j’ai copié la valeur du texte à partir de SQL Mgmt Studio, elle n’était que de 43 Ko lors de son enregistrement dans le bloc-notes. Pour vérifier cela, j’ai créé un rapport avec un identifiant de travail connu (un rapport plus petit), et lorsque j’ai copié la valeur directement dans Visual Studio et que je l’ai transférée dans le bloc-notes, elle faisait environ 5 Mo! Cela signifie que ces gros rapports sont probablement dans la gamme ~ 20MB assis dans un champ nvarchar (max).

Edit3: J’ai tout redémarré, pour inclure mon serveur IIS dev, le serveur SQL et mon ordinateur portable dev. Maintenant, cela semble fonctionner. Ce n’est pas la réponse quant à pourquoi cela s’est produit cependant. Je laisse cette question ouverte pour des explications sur ce qui s’est passé, et je vais en marquer une comme réponse.

Edit4: Ceci étant dit, j’ai fait un autre test sans rien changer et la même exception est revenue. Je commence vraiment à penser qu’il s’agit d’un problème SQL. Je met à jour les balises sur cette question. J’ai créé une application séparée qui exécute exactement la même requête et fonctionne correctement.

Edit5: J’ai implémenté l’access séquentiel selon l’une des réponses ci-dessous. Tout est lu correctement dans un stream, mais lorsque j’essaie de l’écrire dans une chaîne, je suis toujours en train de recevoir l’exception de mémoire insuffisante. Cela indiquerait-il le problème d’obtenir un bloc de mémoire contigu? Voici comment j’ai implémenté la mise en mémoire tampon:

  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.GetSsortingng(0); } reader.Close(); stream.Position = 0L; StreamReader sr = new StreamReader(stream); output = sr.ReadToEnd(); <---- Exception happens here //output = new string(buffer); 

Edit6: Pour append à cela, quand une exception de MOO se produit, je vois que le processus de travail IIS (qui contient la méthode en cours d’exécution) atteint près de 700 Mo. Cela s’exécute sur IIS Express et non sur l’intégralité d’IIS sur le serveur de production. Cela aurait-il quelque chose à voir avec cela? De même, lorsque j’appelle Byte [] data = stream.ToArray (), j’obtiens également le MOO par intermittence. Je pense que ce dont j’ai vraiment besoin, c’est d’un moyen de donner plus de mémoire à ce processus, mais je ne sais pas où le configurer.

Edit7: Je viens de remplacer mon serveur de développement de l’utilisation d’IIS Express sur mon ordinateur local par le serveur Web intégré Visual Studio. L’exception de MOO a maintenant disparu. Je pense vraiment que c’était l’atsortingbution d’un bloc de mémoire contigu et que, pour une raison quelconque, IIS Express ne l’aurait pas été. Maintenant que tout fonctionne correctement, je vais publier sur mon serveur complet sous 2008R2, qui exécute IIS7, pour voir comment cela se passe.

Vous devriez essayer de lire les données séquentiellement en spécifiant le comportement de la commande lorsque vous exécutez le lecteur. Selon la documentation, utilisez SequentialAccess pour récupérer des valeurs volumineuses et des données binarys. Sinon, une exception OutOfMemoryException pourrait se produire et la connexion serait fermée .

Bien que l’access séquentiel soit généralement utilisé pour des données binarys volumineuses, vous pouvez également l’utiliser en fonction de la documentation MSDN pour lire de grandes quantités de données de caractères.

Lors de l’access aux données du champ BLOB, utilisez les accesseurs typés GetBytes ou GetChars de DataReader, qui remplissent un tableau avec des données. Vous pouvez également utiliser GetSsortingng pour les données de caractères; toutefois. Pour conserver les ressources système, il est possible que vous ne souhaitiez pas charger une valeur BLOB entière dans une seule variable de chaîne. Vous pouvez plutôt spécifier une taille de tampon spécifique des données à renvoyer, ainsi qu’un emplacement de départ pour le premier octet ou le premier caractère à lire à partir des données renvoyées. GetBytes et GetChars renverront une valeur longue, qui représente le nombre d’octets ou de caractères renvoyés. Si vous transmettez un tableau null à GetBytes ou GetChars, la valeur longue renvoyée sera le nombre total d’octets ou de caractères du BLOB. Vous pouvez éventuellement spécifier un index dans le tableau en tant que position de départ pour les données en cours de lecture.

Cet exemple MSDN montre comment effectuer un access séquentiel. Je crois que vous pouvez utiliser la méthode GetChars pour lire les données textuelles.

Fondamentalement, une System.OutOfMemoryException ne se produit pas uniquement lorsque la mémoire est System.OutOfMemoryException , mais lorsque vous ne pouvez pas allouer un seul bloc de mémoire contigu à un object. Vous verrez souvent cette erreur lorsque vous essayez de créer un très grand tableau, de charger un object bitmap volumineux, ou parfois lors de la création de XmlDocuments volumineux …

Array et Ssortingng doivent généralement être alloués de manière contiguë, c’est-à-dire qu’ils ne peuvent pas être divisés en morceaux ni alloués dans des espaces vides en mémoire.

Ce n’est probablement pas un problème SQL, mais plutôt un problème lié à la tentative de SqlReader d’allouer une chaîne suffisamment grande pour contenir les données dans une ligne.

Vous avez indiqué que cela fonctionnait correctement après un redémarrage. Supposons donc que votre code est fondamentalement correct (vous pouvez éventuellement l’optimiser pour exposer plutôt les données sous forme de stream au lieu de mettre en tampon le jeu d’enregistrements) et que le symptôme actuel est environnemental. Une machine fraîchement redémarrée n’a peut-être pas autant de mémoire fragmentée, mais plus vous l’utilisiez, plus la mémoire était fragmentée et l’erreur renvoyée …

Vous pourrez peut- être prouver la théorie de la mémoire contiguë en fermant autant d’autres programmes que possible et en ajoutant du code pour forcer un GC.Collect(GC.MaxGeneration) ( référence ) avant le code avec l’erreur. Ce n’est pas une garantie, car la mémoire allouée à votre processus peut toujours être fragmentée.

Je pense que le streaming de la valeur pourrait être le moyen d’empêcher l’erreur de se produire, et mieux d’éviter d’essayer de tout mettre en tampon dans une chaîne. L’inconvénient, c’est que vous garderez la connexion à la firebase database ouverte pendant que le résultat est diffusé / consommé par le rest du programme, ce qui entraîne ses propres frais généraux. Je ne sais pas ce que votre code doit faire avec le résultat, mais s’il doit fonctionner avec une instance de Ssortingng , vous devrez peut-être étendre la mémoire disponible pour le processus (plusieurs façons de l’aider, mais cela peut être hors sujet. – laisser un commentaire et je peux append à cette réponse si nécessaire)

sauvage suppose ici.

 cmd.Parameters.Add(new SqlParameter("ComstackdReportTimeID", ComstackdReportTimeID)); 

vous avez manqué le signe @. il remplace donc les deux instances de ComstackdReportTimeID par l’id et vous obtenez tous les résultats à la place en raison de l’égalité?