119 messaggi dal 26 febbraio 2007
Ciao,

Sto creando una WEBApi che ha il compito di ricevere un DataTable e trasformarlo in CSV in modo da farlo scaricare al Client che lo ha richiesto.

Non è necessario memorizzare il file sul server, perchè non dovrà essere archiviato.

(il file csv potrà diventare anche di 1Gb).

Secondo voi quale è la soluzione migliore?
Devo scorrere tutto il DataTable, creando un file e restituendolo (successivamente sarà eliminato da un processo a parte?).. oppure ci sono strade migliori?

Essendo un potenziale file di 1Gb vorrei utilizzare tutte le accortezze per le performance.


Grazie
Modificato da Federico.C il 27 febbraio 2019 12:33 -
333 messaggi dal 05 novembre 2012
Ciao,

prova a guardare qui è da adattare un pochino al tuo contesto, ma puoi farti un idea...

Prenderei in considerazione anche il concetto di comprimere il csv, essendo un file di testo puro ottimizzi di parecchio le dimensioni del file.

/Ciao

Alessio
119 messaggi dal 26 febbraio 2007
Grazie mille

avrei voluto porvare ad inserire lo stream direttamente nella response (però la Push non va con il Core e poi non gestendoil flusso degli errori, molti lo sconsigliano).


Ho scritto questa funzione, vi sembra corretta?

public IActionResult ExportCsv()
        {
            byte[] file1 = _businessLogic.GetStreamCsv().ToArray();

            using (MemoryStream ms = new MemoryStream())
            {
                using (var archive = new ZipArchive(ms, ZipArchiveMode.Create, true))
                {
                    var zipArchiveEntry = archive.CreateEntry("export.csv", CompressionLevel.Fastest);
                    using (var zipStream = zipArchiveEntry.Open())
                    {
                        zipStream.Write(file1, 0, file1.Length);
                    }
                }
                return File(ms.ToArray(), "application/zip", "export.csv.zip");
            }
        }

Modificato da Federico.C il 27 febbraio 2019 15:32 -
333 messaggi dal 05 novembre 2012
però la Push non va con il Core e poi non gestendoil flusso degli errori, molti lo sconsigliano

Non avevo capito che utilizzi asp.net core e purtroppo la gestione degli errori è il rovescio della medaglia (in caso di errore asp.net interrompe la connessione ed il browser interpreta come errore di rete generico... questo non esclude che puoi loggare l'errore specifico lato server)

Ho scritto questa funzione, vi sembra corretta?

Perchè non hai scritto il metodo asincrono?

/Ciao

Alessio
119 messaggi dal 26 febbraio 2007
Hai ragione ho cambiato il codice in questo modo, corretto?


public async Task<IActionResult> DownloadCsv([FromBody] List<string> selCols, string filter)
        {
            var memoryStream = await downloadCsvAsync(selCols, filter);

            byte[] file1 = memoryStream.ToArray();

            using (MemoryStream ms = new MemoryStream())
            {
                using (var archive = new ZipArchive(ms, ZipArchiveMode.Create, true))
                {
                    var zipArchiveEntry = archive.CreateEntry("export.csv", CompressionLevel.Fastest);
                    using (var zipStream = zipArchiveEntry.Open())
                    {
                        zipStream.Write(file1, 0, file1.Length);
                    }
                }

                return File(ms.ToArray(), "application/zip", "export.csv.zip");
            }
        }

11.792 messaggi dal 09 febbraio 2002
Contributi
Anche il metodo Write ha una variante asincrona (WriteAsync).
Prova anche ad ottimizzare la produzione dello zip perché in questo momento stai caricando tutto il file in memoria e questo significa che la tua applicazione avrà dei picchi di consumo di RAM. Se hai varie richieste contemporanee, potresti anche saturare la memoria del server o arrivarci pericolosamente vicino.

Qui c'è un pezzo di codice che mostra come creare un buffer più piccolo per leggere dallo stream sorgente e scrivere nello stream del ZipArchive.
https://social.msdn.microsoft.com/Forums/en-US/8422b9b5-558d-4f57-b9ee-fa501bf629e2/ziparchive-out-of-memory-when-archiving-a-big-file-over-350m?forum=winappswithcsharp

Non copiarlo-incollarlo ma cerca di capire come funziona e poi adattalo al tuo caso.

ciao,
Moreno

Enjoy learning and just keep making
333 messaggi dal 05 novembre 2012
Ciao,

sono preso con il lavoro, cerco di risponderti in breve...

A una rapida occhiata ok, con l'utilizzo di codice asincrono fai sicuramente una gestione più oculata dei thread, risorsa fondamentale per l'applicativo

Utilizza anche per zipSteam il metodo asincrono

Nel return utilizza direttamente il MemorySteam

ZipArchive, ho visto che hai utilizzato la classe del dotnet microsoft...qui eventualmente hai a disposizione anche DotNetZip o sdk di 7zip (non ho mai provato 7z con dotnet core ma prova a dare un occhio, con 7z hai un notevole beneficio nella dimensione del file)...fai delle prove e valuta eventuali benefici...facci sapere!

public async Task<IActionResult> DownloadCsv([FromBody] List<string> selCols, string filter)
{
    var memoryStream = await downloadCsvAsync(selCols, filter);

    byte[] file1 = memoryStream.ToArray();

    using (MemoryStream ms = new MemoryStream())
    {
        using (var archive = new ZipArchive(ms, ZipArchiveMode.Create, true))
        {
            ZipArchiveEntry zipArchiveEntry = archive.CreateEntry("export.csv", CompressionLevel.Fastest);
            using (Stream zipStream = zipArchiveEntry.Open())
            {
                await zipStream.WriteAsync(file1, 0, file1.Length);
            }
        }

        return File(ms, "application/zip", "export.csv.zip");
    }
}


UPDATE: da quando ho iniziato a rispondere a quando ho inoltrato il messaggio non ho visto che nel contempo aveva risposto anche Moreno

/Ciao
Modificato da scioCoder il 28 febbraio 2019 09:47 -

Alessio
119 messaggi dal 26 febbraio 2007
Grazie mille ad entrambi, ho modificato il codice in questo modo e funziona:

var memoryStream = await Factory.DataflowBuilderDataProv.downloadCSV(selCols, filter);
            
            using (MemoryStream ms = new MemoryStream())
            {
                using (var archive = new ZipArchive(ms, ZipArchiveMode.Create, true))
                {
                    const int bufferSize = 1024 * 1024 * 5;
                    var buffer = new byte[bufferSize];
                    int bytesRead = 0;
                    var zipArchiveEntry = archive.CreateEntry("export.csv", CompressionLevel.Fastest);
                    using (var zipStream = zipArchiveEntry.Open())
                    {
                        do
                        {
                            var rst = await memoryStream.ReadAsync(buffer, bytesRead, bufferSize);
                            bytesRead += rst;
                            if (buffer.Length == 0)
                            {
                                break;
                            }
                            try
                            {
                                await zipStream.WriteAsync(buffer, 0, rst);
                                await zipStream.FlushAsync();
                            }
                            catch (Exception ex)
                            {
                                break;
                            }
                        } while (bytesRead < memoryStream.Length);
                    }
                }
                return File(ms.ToArray(), "application/zip", "export.csv.zip");
            }



Adesso però credo che sia il caso fare la stessa cosa nel metodo downloadCSV? Perchè per creare il csv faccio così:




DataTable dt = mDataStore.GetTable(query);
                
                var memoryStream = new MemoryStream();

                var streamWriter = new StreamWriter(memoryStream);

                var columns = new List<string>();
                var maxCol = 0;
                foreach (DataColumn itemCol in dt.Columns)
                {
                    columns.Add(itemCol.ColumnName);
                    if (maxCol == 0 && maxCol + 1 == dt.Columns.Count)
                    { //Only one column create a writeline
                        await streamWriter.WriteLineAsync(itemCol.ColumnName);
                    }
                    else if (maxCol == 0 && maxCol + 1 < dt.Columns.Count) 
                    { //First Column no ','
                        await streamWriter.WriteAsync(itemCol.ColumnName);
                    }
                    else if (maxCol + 1 < dt.Columns.Count)
                    { //nColumn add a ','
                        await streamWriter.WriteAsync($", {itemCol.ColumnName}");
                    }
                    else
                    { //Last column add ',' and create a writeline
                        await streamWriter.WriteLineAsync($",{itemCol.ColumnName}");
                    }
                    maxCol++;
                }

                foreach (DataRow itemRow in dt.Rows)
                {
                    var rows = new List<string>();
                    for (var i = 0; i < maxCol; i++)
                    { 
                        if (i == 0 && i + 1 == maxCol)
                        { //Only one column create a writeline
                            await streamWriter.WriteLineAsync(Convert.ToString(itemRow[i]));
                        }
                        else if (i == 0 && i + 1 < maxCol)
                        { //Only one column create a writeline
                            await streamWriter.WriteAsync(Convert.ToString(itemRow[i]));
                        }
                        else if (i + 1 < maxCol)
                        { //nColumn add a ','
                            await streamWriter.WriteAsync($",{itemRow[i]}");
                        }
                        else
                        { //Last column add ',' and create a writeline
                            await streamWriter.WriteLineAsync($",{itemRow[i]}");
                        }
                    }
                    await streamWriter.FlushAsync();
                }
                memoryStream.Position = 0;

                return memoryStream;




Conviene implementare il buffer anche qua, giusto?
Modificato da Federico.C il 28 febbraio 2019 10:18 -
Modificato da Federico.C il 28 febbraio 2019 10:19 -

Torna al forum | Feed RSS

ASPItalia.com non è responsabile per il contenuto dei messaggi presenti su questo servizio, non avendo nessun controllo sui messaggi postati nei propri forum, che rappresentano l'espressione del pensiero degli autori.