none
StreamWriter- OutMemoryException RRS feed

  • Domanda

  • Ciao a tutti,
    ho problemi con la scrittura su un file, ma il problema è causato dalla mole di dati.

    Usando questa porzione di codice, io creo un file e ci appendo dentro i dati passati tramite datatable.

    In realtà da come si vede dal codice _dsAzienda è un dataset che contiente n datatable e parto da quello per ciclare datatable, datarow e le colonne tramite ItemArray:

                StreamWriter sr = new StreamWriter(FilePath);
                bool notFirstField = false;
                foreach (DataTable table in _dsAzienda.Tables)
                {
                    foreach (DataRow row in table.Rows)
                    {
                        row.ItemArray.ToList()
                            .ForEach(rec =>
                            {
                                if (notFirstField) sr.Write(delimitatore);
                                if ((rec == null || string.IsNullOrWhiteSpace(rec.ToString())))
                                    sr.Write("0");
                                else
                                    sr.Write(rec.ToString());
                                notFirstField = true;
                            });
                    }
                }
                sr.Write(Environment.NewLine);
                sr.Flush();
                sr.Close();

    Il problema è che per una quantità di dati all'interno dei datatable non molto grandi la procedura è ok, mentre per una mole di dati molto pesante mi và in OutOfMemoryException.

    Dove sbaglio o come posso risolvere il mio problema?

    Se è possibile ottimizzare la scrittura i Vs. consiglio sono ben accetti.

    Grazie 1000

    martedì 13 marzo 2012 09:58

Risposte

  • ciao

    ti consiglio di non utilizzare i dataset che consumano molto spazio in memoria essendo degli XML cosa che usata in ottica molto multiutente crea un grande consumo di RAM

    in alternativa, usa ADO connesso (sqlcommand, sqldatareader, etc), ciclando sul dataReader.Read() e poi sul dataReader stesso per prenderti tutte le colonne (in pratica l'approccio stream della lettura da DB)

    oltre la sostituzione completa, puoi anche tentare di fare la lettura di un DataTable per volta ansichè caricare sempre tutto il DataSet

    successivamente puoi esportare con il tuo codice con i consigli già ricevuti

    io personalmente lo aggiornerei con qualcosa di simile (riadatta per il tuo uso):

                
                using(var sr = new StreamWriter(FilePath))
                foreach (DataTable table in _dsAzienda.Tables)
                    foreach (DataRow row in table.Rows)
                        sr.WriteLine(string.Join(delimitatore, 
                            row.ItemArray.Select(x=> x != null && !string.IsNullOrEmpty(x.ToString()) ? x.ToString() : "0")));
    
    

    a presto


    Antonio Esposito [MCT, MCPD, MCTS, MCP]
    dotnetlombardia.org | blog | web | @tonyexpo
    Italy
     

    • Contrassegnato come risposta Richard4You mercoledì 14 marzo 2012 09:44
    martedì 13 marzo 2012 13:24
    Postatore

Tutte le risposte

  • Di quanti dati stiamo parlando?

    Qual è la riga che ti lancia l'eccezione?


    Marco Minerva [MCPD]
    Blog: http://blogs.ugidotnet.org/marcom
    Twitter: @marcominerva

    martedì 13 marzo 2012 10:03
    Moderatore
  • Sembrerebbe questa riga, anche se ho rilanciato l'elaborazione sul server e ti farò sapere:

    sr.Write(rec.ToString());

    Forse devo inserire il .Flush() nel ciclo, potrebbe servire ad alleggerire la memoria?

    Grazie

    martedì 13 marzo 2012 10:41
  • Ciao Richard4You,

    You wrote on 13/03/2012 :

    Dove sbaglio o come posso risolvere il mio problema?

    sei su un server web? quella procedura viene chiamata più volte magari in un ciclo?

    .m



    blog @ //milestone.topics.it
    martedì 13 marzo 2012 10:50
  • OK, intanto mi dici anche quanti sono i dati che scrivi?

    Marco Minerva [MCPD]
    Blog: http://blogs.ugidotnet.org/marcom
    Twitter: @marcominerva

    martedì 13 marzo 2012 10:55
    Moderatore
  • No sono all'interno di un server aziendale no web.

    La procedura è abbastanza consolidata, ricontrollando anche un'ennesima volta sono sicuro al 100% che rischi di loop non ce ne sono.

    Ho impostato l'istruzione sr.Flush(); nel ciclo tra i datatable e lo sto facendo girare! Può servire questo flush?

    martedì 13 marzo 2012 10:58
  • Ciao Richard4You,

    You wrote on 13/03/2012 :

    No sono all'interno di un server aziendale no web.

    cosa vuol dire? che tipo di applicazione è?

    La procedura è abbastanza consolidata, ricontrollando anche un'ennesima volta sono sicuro al 100% che rischi di loop non ce ne sono.

    Ho impostato l'istruzione sr.Flush(); nel ciclo tra i datatable e lo sto facendo girare! Può servire questo flush?

    secondo me no :-)

    .m



    blog @ //milestone.topics.it
    martedì 13 marzo 2012 11:00
  • Rispondo qui sia a Marco che a Mauro.

    L'applicazione che esegue quella operazione è un'applicazione WinForm (.NET C#) installata sulla stessa macchina dove risiede il database.

    Per quanto riguada la mole di dati, dal risultato della query vi indico la quantità di righe per tabella (per le colonne sono una 30^ in media per ogni tabella).

    Risultato della query:

    11129    table1
    11129    table2
    11129    table3
    11129    table4
    11129    table5
    11129    table6
    11129    table7
    11129    table8
    11129    table9
    11129    table10
    92919    table11
    11384    table12
    18585    table13
    8790    table14

    Chiaramente il grazie è sottinteso ad ognio mio post.

    martedì 13 marzo 2012 11:18
  • Ciao Richard4You,

    You wrote on 13/03/2012 :

    L'applicazione che esegue quella operazione è un'applicazione WinForm (.NET C#) installata sulla stessa macchina dove risiede il database.

    racchiudu lo StreamWriter in un blocco "using" e vediamo se è una mera questione di handle su disco non rilasciati e di conseguenza di pinning.

    .m



    blog @ //milestone.topics.it
    martedì 13 marzo 2012 11:21
  • Potresti provare a scrivere ogni DataTable separatamente.

    Ovvero, apri lo StreamWriter, scrivi il primo DataTable, fai il Close e Dispose dello stream, dopodiché lo riapri in append, vi scrivi il secondo DataTable, lo chiudi, e così via.


    Marco Minerva [MCPD]
    Blog: http://blogs.ugidotnet.org/marcom
    Twitter: @marcominerva

    martedì 13 marzo 2012 11:22
    Moderatore
  • Proverò separatamente le Vs. soluzioni e Vi farò sapere.

    Grazie e spero di darVi buone notizie.

    martedì 13 marzo 2012 11:25
  • ciao

    ti consiglio di non utilizzare i dataset che consumano molto spazio in memoria essendo degli XML cosa che usata in ottica molto multiutente crea un grande consumo di RAM

    in alternativa, usa ADO connesso (sqlcommand, sqldatareader, etc), ciclando sul dataReader.Read() e poi sul dataReader stesso per prenderti tutte le colonne (in pratica l'approccio stream della lettura da DB)

    oltre la sostituzione completa, puoi anche tentare di fare la lettura di un DataTable per volta ansichè caricare sempre tutto il DataSet

    successivamente puoi esportare con il tuo codice con i consigli già ricevuti

    io personalmente lo aggiornerei con qualcosa di simile (riadatta per il tuo uso):

                
                using(var sr = new StreamWriter(FilePath))
                foreach (DataTable table in _dsAzienda.Tables)
                    foreach (DataRow row in table.Rows)
                        sr.WriteLine(string.Join(delimitatore, 
                            row.ItemArray.Select(x=> x != null && !string.IsNullOrEmpty(x.ToString()) ? x.ToString() : "0")));
    
    

    a presto


    Antonio Esposito [MCT, MCPD, MCTS, MCP]
    dotnetlombardia.org | blog | web | @tonyexpo
    Italy
     

    • Contrassegnato come risposta Richard4You mercoledì 14 marzo 2012 09:44
    martedì 13 marzo 2012 13:24
    Postatore
  • Ciao Antonio,

    grazie per gli ottimi consigli. Finisco le prove (anche per curiosità) con i consigli di Marco e Mauro e poi vediamo come posso procedere.

    Per quanto riguarda l'ottimizzazione del codice da te postato, avevo già fatto una cosa del genere, utilizzando lo string.join, ma non andava bene per il mio scopo.

    In questo modo il delimitatore viene omesso tra i dati estratti fra i vari datatables e vanno quindi a concatenarsi le informazioni tra essi.

    Per fare un esempio:

    se datatable1 è : { 0,1,2,3,4 }

     e datatable 2 è : {5,6,7,8,9 }

    concatenati con lo string.join verrebbe:

    0,1,2,3,45,6,7,8,9 a me serve 0,1,2,3,4,5,6,7,8,9 ecco perchè è meno compatto ed utilizzo una variabile booleana per impostare il delimitatore.

    Però effettivamente potrebbe essere riscritta in maniera più compatta, ma in questo momento exception mi preoccupa di + :)

    Grazie

    Vi tengo aggiornati

    martedì 13 marzo 2012 13:53
  • Madoooo!!!!

    Non mi insultate vi prego!

    Dopo i validi suggerimenti di Marco e Mauro il risultato era sempre lo stesso (suggerimenti molto utili).

    Ho tolto la gestione dell'exception, per appurare su quale riga venisse lanciata l'exception e bene non è nella riga di scrittura su file ma sul caricamento di un datatable all'interno del dataset ...

    Non solo! 

    Io non vi ho detto tutto; utilizzo una List<DataSet> a livello superiore, quindi immagino che il consiglio di Antonio è la chiave per risolvere il problema.

    Ora secondo voi, senza essere mandato possibilmente a quel paese, ho possibilità di essere rimesso  sulla retta via se provo a sostituire il DataSet con una List<DataTable> o una classe ex-novo che contiene la stessa lista o non c'è niente da fare? :)

    Grazie

    martedì 13 marzo 2012 14:20
  • Tranquillo, siamo qui per darti una mano :-)

    Puoi dirci l'uso che fai dei DataTable? Cioè, carichi i dati al loro interno solo per poterli poi scrivere su file? Perché, in questo caso, potresti leggere i dati usando DataReader, Entity Framework, ecc. e scriverli direttamente su disco, senza passare per un DataTable.


    Marco Minerva [MCPD]
    Blog: http://blogs.ugidotnet.org/marcom
    Twitter: @marcominerva

    martedì 13 marzo 2012 14:30
    Moderatore
  • Esattamente! Li utilizzo solo per estrarre i dati e metterli su file. Faccio delle operazioni intermedie ma credo che si può trovare l'alternatica.

    Riesci a postarmi un esempio di estrazione su file con EF?

    martedì 13 marzo 2012 14:35
  • Ciao Antonio,

    grazie per gli ottimi consigli. Finisco le prove (anche per curiosità) con i consigli di Marco e Mauro e poi vediamo come posso procedere.

    Per quanto riguarda l'ottimizzazione del codice da te postato, avevo già fatto una cosa del genere, utilizzando lo string.join, ma non andava bene per il mio scopo.

    In questo modo il delimitatore viene omesso tra i dati estratti fra i vari datatables e vanno quindi a concatenarsi le informazioni tra essi.

    Per fare un esempio:

    se datatable1 è : { 0,1,2,3,4 }

     e datatable 2 è : {5,6,7,8,9 }

    concatenati con lo string.join verrebbe:

    0,1,2,3,45,6,7,8,9 a me serve 0,1,2,3,4,5,6,7,8,9 ecco perchè è meno compatto ed utilizzo una variabile booleana per impostare il delimitatore.

    Però effettivamente potrebbe essere riscritta in maniera più compatta, ma in questo momento exception mi preoccupa di + :)

    Grazie

    Vi tengo aggiornati

    ciao

    perfetto :)

    il mio esempio infatti non era la soluzione specifica ma solo un'indicazione che vedo hai riadattato correttamente alle tue necesità


    Antonio Esposito [MCT, MCPD, MCTS, MCP]
    dotnetlombardia.org | blog | web | @tonyexpo
    Italy
     

    martedì 13 marzo 2012 14:39
    Postatore
  • Esattamente! Li utilizzo solo per estrarre i dati e metterli su file. Faccio delle operazioni intermedie ma credo che si può trovare l'alternatica.

    Riesci a postarmi un esempio di estrazione su file con EF?

    ciao

    allora: prima cosa per fare ammenda dovrai rispondere ad almeno 10 post altrui ;)

    se fai esclusivamente estrazione di tabelle/viste/resultset-di-SP allora è molto più semplice usare ado connesso (sqlconnection,sqlcommand,sqldatareader)

    in pratica fai una sqlconnection per connetterti al DB, N sql command con dentro le varie "SELECT * FROM XXX" poi per ogni sqlcommand fai una sqldatareader con myCommand.ExecuteReader()

    qui dentro fai un while (reader.Read()) e cioè per ogni riga presente, scrivi sullo streamwriter tutte le colonne che peschi facendo semplicemente reader[i] dove i è il numero della colonna che prendi facendo un for da 0 a reader.FieldCount-1

    più lungo a dirsi ke a scriversi

    qualcosa simile a questo:

                using (var sr = new StreamWriter(FilePath))
                using (var cn = new SqlConnection("CONN"))
                using (var cm = new SqlCommand("SELECT * FROM XXX", cn))
                {
                    cn.Open();
                    using (var dr = cm.ExecuteReader())
                        while (dr.Read())
                            sr.WriteLine(string.Join(delimitatore,
                               Enumerable.Range(0, dr.FieldCount - 1).Select(i => !string.IsNullOrEmpty(dr[i].ToString()) ? dr[i].ToString() : "0")));
                }
    

    come sempre devi riadattare il mio esempio al tuo caso d'uso, come gli oggetti, le tabelle, ed il fatto che hai più tabelle e non solo 1 da esportare

    a presto


    Antonio Esposito [MCT, MCPD, MCTS, MCP]
    dotnetlombardia.org | blog | web | @tonyexpo
    Italy
     

    martedì 13 marzo 2012 14:46
    Postatore
  • Ok!

    10 post per la penale ci sto, chiaramente entro domani vero??? :) Mi sembra di sentire il mio CAPO! :)))

    In realtà l'oggetto disconnesso mi era comodo ...per caricare i datatable uso la query "SELECT * FROM XXX" poi alla fine, prima dell'estrazione dei dati, faccio l'eliminazione di alcune colonne eventualmente presenti:

    if (_dt.Columns.Contains("colXXX"))
        _dt.Columns.Remove("colXXX");
    

    cosi se la colonna c'è la rimuovo altrimenti no e cosi il dato non viene riportato nel file di output.

    Con la soluzione che mi hai prospettato, dovrei conoscere a priori tutte i campi delle tabelle da estrarre per fare le SELECT mirate, ma questo mi comporta fatica in caso di manutenzione.

    Su Antonio, se mi dai un altro input ... faccio Raise di altri 5 post! :)

    martedì 13 marzo 2012 15:10
  • ciao

    se la comodità è solo quella, puoi sempre filtrare le colonne che esporti con qualcosa tipo reader.GetName(i)!="colXXX")

    ok dai :) aspettiamo i tuoi post!


    Antonio Esposito [MCT, MCPD, MCTS, MCP]
    dotnetlombardia.org | blog | web | @tonyexpo
    Italy
     

    martedì 13 marzo 2012 15:20
    Postatore
  • Spectacolarissimo!

    Per dovere di cronaca e per i lettori interessati posto il codice modificato.

    Ho aggiunto una List<string> colonnedaescludere, che contiene l'elenco dei nomi colonna che saranno appunto esclusi :

     while (dr.Read())
     {
     //Enumerable.Range parte da 1 per escludere sempre la prima colonna (potrebbe partire da 0 se ne hai necessità))
         sr.Write(string.Join(";",
            Enumerable.Range(1, dr.FieldCount - 1)
            .Where(i => !colonnedaescludere.Contains(dr.GetName(i)))
            .Select(i => !string.IsNullOrEmpty(dr[i].ToString()) ? dr[i].ToString() : "0")));
     }
    

    per quanto riguarda l'exception sta girando quindi più tardi Vi aggiorno.

    Ma credo che ci siamo, sono ottimista!

    Grazie ragazzi!

    Per il mio contributo con i 15 post farò del mio meglio anche se non sono molto bravo! :)

    martedì 13 marzo 2012 16:46
  • perfetto!

    grazie per aver condiviso il tuo esempio

    e facci sapere come va :)

    a presto


    Antonio Esposito [MCT, MCPD, MCTS, MCP]
    dotnetlombardia.org | blog | web | @tonyexpo
    Italy
     

    martedì 13 marzo 2012 17:02
    Postatore
  • Vi aggiorno con sommo piacere.

    Ok ora il proplema è risolto nessun OutOfMemoryException ed il processo ha generato con successo un file di testo di 144MB.

    Grazie 1000 a tutti.

    N.B.: Per i post sto facendo del mio meglio!!!

    mercoledì 14 marzo 2012 09:43
  • perfetto :)

    ps: guarda ke per i post si scherzava ;) non sei obbligato, ma se dai 1 mano ci fai piacere

    a presto


    Antonio Esposito [MCT, MCPD, MCTS, MCP]
    dotnetlombardia.org | blog | web | @tonyexpo
    Italy
     

    mercoledì 14 marzo 2012 10:17
    Postatore
  • si si so che si scherzava! Ho voluto cimentarmi! :)

    Ci proverò ad aiutarVi sperando di non essere d'intralcio.

    Ciao

    mercoledì 14 marzo 2012 10:32
  • Chi prova ad aiutare gli altri non è mai d'intralcio, anzi...

    Marco Minerva [MCPD]
    Blog: http://blogs.ugidotnet.org/marcom
    Twitter: @marcominerva

    mercoledì 14 marzo 2012 10:33
    Moderatore