Come creare il file CSV Excel C #? [chiuso]


132

Sto cercando una classe per la creazione di file CSV Excel.

Funzionalità previste:

  • Estremamente semplice da usare
  • Sfugge alle virgole e alle virgolette, quindi Excel le gestisce bene
  • Data e data di esportazione in formato a prova di fuso orario

Conosci qualche classe capace di questo?


12
meglio porre la domanda nella parte DOMANDA e quindi pubblicare la propria risposta nella parte RISPOSTA. Assicurati di aggiungere tag e parole chiave nella domanda per renderlo ricercabile.
Cheeso,

IMPORTANTE: dovresti anche aggiungere delle virgolette quando ci sono RITORNI DI TRASPORTO nel "valore".
Alex

Grazie @Chris, un suggerimento se posso, questo codice può generare un KeyNotFoundException, per favore vedi la mia risposta.
Joseph,

Il suo miglior esempio ... ma come posso aggiungere due tabelle in un singolo file, significa che ho una tabella di due righe e l'altra tabella è di 10 righe ed entrambe hanno un nome di colonna univoco. Voglio aggiungere una tabella di due righe in alto e dopo divario di due righe voglio aggiungere una seconda tabella.
Floki,

Risposte:


92

Versione leggermente diversa che ho scritto usando la riflessione per le mie esigenze. Ho dovuto esportare un elenco di oggetti in CSV. Nel caso qualcuno voglia usarlo per il futuro.

public class CsvExport<T> where T: class
    {
        public List<T> Objects;

        public CsvExport(List<T> objects)
        {
            Objects = objects;
        }

        public string Export()
        {
            return Export(true);
        }

        public string Export(bool includeHeaderLine)
        {

            StringBuilder sb = new StringBuilder();
            //Get properties using reflection.
            IList<PropertyInfo> propertyInfos = typeof(T).GetProperties();

            if (includeHeaderLine)
            {
                //add header line.
                foreach (PropertyInfo propertyInfo in propertyInfos)
                {
                    sb.Append(propertyInfo.Name).Append(",");
                }
                sb.Remove(sb.Length - 1, 1).AppendLine();
            }

            //add value for each property.
            foreach (T obj in Objects)
            {               
                foreach (PropertyInfo propertyInfo in propertyInfos)
                {
                    sb.Append(MakeValueCsvFriendly(propertyInfo.GetValue(obj, null))).Append(",");
                }
                sb.Remove(sb.Length - 1, 1).AppendLine();
            }

            return sb.ToString();
        }

        //export to a file.
        public void ExportToFile(string path)
        {
            File.WriteAllText(path, Export());
        }

        //export as binary data.
        public byte[] ExportToBytes()
        {
            return Encoding.UTF8.GetBytes(Export());
        }

        //get the csv value for field.
        private string MakeValueCsvFriendly(object value)
        {
            if (value == null) return "";
            if (value is Nullable && ((INullable)value).IsNull) return "";

            if (value is DateTime)
            {
                if (((DateTime)value).TimeOfDay.TotalSeconds == 0)
                    return ((DateTime)value).ToString("yyyy-MM-dd");
                return ((DateTime)value).ToString("yyyy-MM-dd HH:mm:ss");
            }
            string output = value.ToString();

            if (output.Contains(",") || output.Contains("\""))
                output = '"' + output.Replace("\"", "\"\"") + '"';

            return output;

        }
    }

Esempio di utilizzo: (aggiornato per commento)

CsvExport<BusinessObject> csv= new CsvExport<BusinessObject>(GetBusinessObjectList());
Response.Write(csv.Export());

5
Era più simile a questo: List <BusinessObject> x = new List <BusinessObject> (); CsvExport <BusinessObject> x = new CsvExport <BusinessObject> (MUsers);
nascosto il

5
Da dove viene la tua interfaccia INullable?
Kilhoffer,

Il suo miglior esempio ... ma come posso aggiungere due tabelle in un singolo file, significa che ho una tabella di due righe e l'altra tabella è di 10 righe ed entrambe hanno un nome di colonna univoco. Voglio aggiungere una tabella di due righe in alto e dopo divario di due righe voglio aggiungere una seconda tabella.
Floki,

2
So che il post originale era del 2011, quindi non sono sicuro che fosse possibile nella versione .NET che è stata utilizzata allora. Ma perché non rimuovere il public string Export()metodo e cambiare l'altro metodo in public string Export(bool includeHeaderLiner = true)(con un valore di parametro predefinito). Ancora una volta, non sono sicuro che i parametri predefiniti fossero disponibili nel 2011, ma il codice attuale mi sembra solo ortodosso.
Kevin Cruijssen,

19

ti prego, perdonami

Ma penso che un repository pubblico open source sia un modo migliore per condividere codice e apportare contributi, correzioni e aggiunte come "Ho risolto questo, ho risolto quello"

Così ho creato un semplice repository git dal codice dell'argomento e da tutte le aggiunte:

https://github.com/jitbit/CsvExport

Ho anche aggiunto un paio di utili correzioni. Tutti possono aggiungere suggerimenti, inoltrarli per contribuire, ecc. Ecc. Ecc. Inviami le tue forcelle in modo che le unisca nuovamente nel repository.

PS. Ho pubblicato tutte le note sul copyright di Chris. @Chris se sei contrario a questa idea - fammi sapere, lo ucciderò.


11

Un'altra buona soluzione per leggere e scrivere file CSV è filehelpers (open source).


NB: il supporto Excel è solo per scenari di base : l'attuale supporto Excel implementato è solo per scenari di base. Se hai bisogno di formattazione personalizzata, grafici, ecc. Devi scegliere un codice personalizzato. Si consiglia vivamente di utilizzare direttamente la libreria NPOI
AK

6

Che ne dici di usare string.Join invece di tutti i loop foreach?


String.Join funziona solo su string [], mentre sto usando alcune delle funzionalità di List <string>.
Chris

12
String.Join("," , List<string>)funziona anche.
Demenza

6

Se qualcuno desidera che lo abbia convertito in un metodo di estensione su IEnumerable:

public static class ListExtensions
{
    public static string ExportAsCSV<T>(this IEnumerable<T> listToExport, bool includeHeaderLine, string delimeter)
    {
        StringBuilder sb = new StringBuilder();

        IList<PropertyInfo> propertyInfos = typeof(T).GetProperties();

        if (includeHeaderLine)
        {
            foreach (PropertyInfo propertyInfo in propertyInfos)
            {
                sb.Append(propertyInfo.Name).Append(",");
            }
            sb.Remove(sb.Length - 1, 1).AppendLine();
        }

        foreach (T obj in listToExport)
        {
            T localObject = obj;

            var line = String.Join(delimeter, propertyInfos.Select(x => SanitizeValuesForCSV(x.GetValue(localObject, null), delimeter)));

            sb.AppendLine(line);
        }

        return sb.ToString();
    }

    private static string SanitizeValuesForCSV(object value, string delimeter)
    {
        string output;

        if (value == null) return "";

        if (value is DateTime)
        {
            output = ((DateTime)value).ToLongDateString();
        }
        else
        {
            output = value.ToString();                
        }

        if (output.Contains(delimeter) || output.Contains("\""))
            output = '"' + output.Replace("\"", "\"\"") + '"';

        output = output.Replace("\n", " ");
        output = output.Replace("\r", "");

        return output;
    }
}

5

ottimo lavoro su questa classe. Semplice e facile da usare. Ho modificato la classe per includere un titolo nella prima riga dell'esportazione; ho pensato di condividere:

uso:

CsvExport myExport = new CsvExport();
myExport.addTitle = String.Format("Name: {0},{1}", lastName, firstName));

classe:

public class CsvExport
{
    List<string> fields = new List<string>();

    public string addTitle { get; set; } // string for the first row of the export

    List<Dictionary<string, object>> rows = new List<Dictionary<string, object>>();
    Dictionary<string, object> currentRow
    {
        get
        {
            return rows[rows.Count - 1];
        }
    }

    public object this[string field]
    {
        set
        {
            if (!fields.Contains(field)) fields.Add(field);
            currentRow[field] = value;
        }
    }

    public void AddRow()
    {
        rows.Add(new Dictionary<string, object>());
    }

    string MakeValueCsvFriendly(object value)
    {
        if (value == null) return "";
        if (value is Nullable && ((INullable)value).IsNull) return "";
        if (value is DateTime)
        {
            if (((DateTime)value).TimeOfDay.TotalSeconds == 0)
                return ((DateTime)value).ToString("yyyy-MM-dd");
            return ((DateTime)value).ToString("yyyy-MM-dd HH:mm:ss");
        }
        string output = value.ToString();
        if (output.Contains(",") || output.Contains("\""))
            output = '"' + output.Replace("\"", "\"\"") + '"';
        return output;

    }

    public string Export()
    {
        StringBuilder sb = new StringBuilder();

        // if there is a title
        if (!string.IsNullOrEmpty(addTitle))
        {
            // escape chars that would otherwise break the row / export
            char[] csvTokens = new[] { '\"', ',', '\n', '\r' };

            if (addTitle.IndexOfAny(csvTokens) >= 0)
            {
                addTitle = "\"" + addTitle.Replace("\"", "\"\"") + "\"";
            }
            sb.Append(addTitle).Append(",");
            sb.AppendLine();
        }


        // The header
        foreach (string field in fields)
        sb.Append(field).Append(",");
        sb.AppendLine();

        // The rows
        foreach (Dictionary<string, object> row in rows)
        {
            foreach (string field in fields)
                sb.Append(MakeValueCsvFriendly(row[field])).Append(",");
            sb.AppendLine();
        }

        return sb.ToString();
    }

    public void ExportToFile(string path)
    {
        File.WriteAllText(path, Export());
    }

    public byte[] ExportToBytes()
    {
        return Encoding.UTF8.GetBytes(Export());
    }
}


3

Ho aggiunto ExportToStream in modo che il CSV non debba prima salvare sul disco rigido.

public Stream ExportToStream()
{
    MemoryStream stream = new MemoryStream();
    StreamWriter writer = new StreamWriter(stream);
    writer.Write(Export(true));
    writer.Flush();
    stream.Position = 0;
    return stream;
}

3

ho aggiunto

public void ExportToFile(string path, DataTable tabela)
{

     DataColumnCollection colunas = tabela.Columns;

     foreach (DataRow linha in tabela.Rows)
     {

           this.AddRow();

           foreach (DataColumn coluna in colunas)

           {

               this[coluna.ColumnName] = linha[coluna];

           }

      }
      this.ExportToFile(path);

}

Il codice precedente non funziona con le vecchie versioni di .NET. Per la versione 3.5 di framework usa questa altra versione:

        public void ExportToFile(string path)
    {
        bool abort = false;
        bool exists = false;
        do
        {
            exists = File.Exists(path);
            if (!exists)
            {
                if( !Convert.ToBoolean( File.CreateText(path) ) )
                        abort = true;
            }
        } while (!exists || abort);

        if (!abort)
        {
            //File.OpenWrite(path);
            using (StreamWriter w = File.AppendText(path))
            {
                w.WriteLine("hello");
            }

        }

        //File.WriteAllText(path, Export());
    }

2

Grazie mille per quello! Ho modificato la classe in:

  • utilizzare un delimitatore di variabili, anziché codificato nel codice
  • sostituendo tutte le newLine (\ n \ r \ n \ r) in MakeValueCsvFriendly

Codice:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Data.SqlTypes;
using System.IO;
using System.Text;
using System.Text.RegularExpressions;

    public class CsvExport
    {

        public char delim = ';';
        /// <summary>
        /// To keep the ordered list of column names
        /// </summary>
        List<string> fields = new List<string>();

        /// <summary>
        /// The list of rows
        /// </summary>
        List<Dictionary<string, object>> rows = new List<Dictionary<string, object>>();

        /// <summary>
        /// The current row
        /// </summary>
        Dictionary<string, object> currentRow { get { return rows[rows.Count - 1]; } }

        /// <summary>
        /// Set a value on this column
        /// </summary>
        public object this[string field]
        {
            set
            {
                // Keep track of the field names, because the dictionary loses the ordering
                if (!fields.Contains(field)) fields.Add(field);
                currentRow[field] = value;
            }
        }

        /// <summary>
        /// Call this before setting any fields on a row
        /// </summary>
        public void AddRow()
        {
            rows.Add(new Dictionary<string, object>());
        }

        /// <summary>
        /// Converts a value to how it should output in a csv file
        /// If it has a comma, it needs surrounding with double quotes
        /// Eg Sydney, Australia -> "Sydney, Australia"
        /// Also if it contains any double quotes ("), then they need to be replaced with quad quotes[sic] ("")
        /// Eg "Dangerous Dan" McGrew -> """Dangerous Dan"" McGrew"
        /// </summary>
        string MakeValueCsvFriendly(object value)
        {
            if (value == null) return "";
            if (value is INullable && ((INullable)value).IsNull) return "";
            if (value is DateTime)
            {
                if (((DateTime)value).TimeOfDay.TotalSeconds == 0)
                    return ((DateTime)value).ToString("yyyy-MM-dd");
                return ((DateTime)value).ToString("yyyy-MM-dd HH:mm:ss");
            }
            string output = value.ToString();
            if (output.Contains(delim) || output.Contains("\""))
                output = '"' + output.Replace("\"", "\"\"") + '"';
            if (Regex.IsMatch(output,  @"(?:\r\n|\n|\r)"))
                output = string.Join(" ", Regex.Split(output, @"(?:\r\n|\n|\r)"));
            return output;
        }

        /// <summary>
        /// Output all rows as a CSV returning a string
        /// </summary>
        public string Export()
        {
            StringBuilder sb = new StringBuilder();

            // The header
            foreach (string field in fields)
                sb.Append(field).Append(delim);
            sb.AppendLine();

            // The rows
            foreach (Dictionary<string, object> row in rows)
            {
                foreach (string field in fields)
                    sb.Append(MakeValueCsvFriendly(row[field])).Append(delim);
                sb.AppendLine();
            }

            return sb.ToString();
        }

        /// <summary>
        /// Exports to a file
        /// </summary>
        public void ExportToFile(string path)
        {
            File.WriteAllText(path, Export());
        }

        /// <summary>
        /// Exports as raw UTF8 bytes
        /// </summary>
        public byte[] ExportToBytes()
        {
            return Encoding.UTF8.GetBytes(Export());

        }

    }


1

La classe originale presenta un problema, ovvero se si desidera aggiungere una nuova colonna, si riceverà KeyNotFoundException sul metodo Export. Per esempio:

static void Main(string[] args)
{
    var export = new CsvExport();

    export.AddRow();
    export["Region"] = "New York, USA";
    export["Sales"] = 100000;
    export["Date Opened"] = new DateTime(2003, 12, 31);

    export.AddRow();
    export["Region"] = "Sydney \"in\" Australia";
    export["Sales"] = 50000;
    export["Date Opened"] = new DateTime(2005, 1, 1, 9, 30, 0);
    export["Balance"] = 3.45f;  //Exception is throwed for this new column

    export.ExportToFile("Somefile.csv");
}

Per risolvere questo problema e usando l'idea di @KeyboardCowboy di usare reflection, ho modificato il codice per consentire l'aggiunta di righe che non hanno le stesse colonne. È possibile utilizzare istanze di classi anonime. Per esempio:

static void Main(string[] args)
{
    var export = new CsvExporter();

    export.AddRow(new {A = 12, B = "Empty"});
    export.AddRow(new {A = 34.5f, D = false});

    export.ExportToFile("File.csv");
}

Puoi scaricare il codice sorgente qui CsvExporter . Sentiti libero di usare e modificare.

Ora, se tutte le righe che vuoi scrivere sono della stessa classe, ho creato la classe generica CsvWriter.cs , che ha un utilizzo della RAM con prestazioni migliori e ideale per la scrittura di file di grandi dimensioni, inoltre ti consente di aggiungere formattatori al tipo di dati che desideri . Un esempio di utilizzo:

class Program
{
    static void Main(string[] args)
    {
        var writer = new CsvWriter<Person>("Persons.csv");

        writer.AddFormatter<DateTime>(d => d.ToString("MM/dd/yyyy"));

        writer.WriteHeaders();
        writer.WriteRows(GetPersons());

        writer.Flush();
        writer.Close();
    }

    private static IEnumerable<Person> GetPersons()
    {
        yield return new Person
            {
                FirstName = "Jhon", 
                LastName = "Doe", 
                Sex = 'M'
            };

        yield return new Person
            {
                FirstName = "Jhane", 
                LastName = "Doe",
                Sex = 'F',
                BirthDate = DateTime.Now
            };
        }
    }


    class Person
    {
        public string FirstName { get; set; }

        public string LastName { get; set; }

        public char Sex  { get; set; }

        public DateTime BirthDate { get; set; }
    }

0

Per eseguire questa operazione è necessaria solo 1 funzione. Non devi fare altro che creare una cartella in Esplora soluzioni e archiviare lì il file CSV, quindi esportare tale file per l'utente.

Come nel mio caso, ho una cartella download. Per prima cosa esporto tutto il mio contenuto in quella directory e poi lo esporto per l'utente. Per la gestione di response.end, ho usato ThreadAbortException. Quindi è una funzione genuina e funzionante al 100% nella mia soluzione.

protected void lnkExport_OnClick(object sender, EventArgs e)
{

    string filename = strFileName = "Export.csv";

    DataTable dt = obj.GetData();  

// call the content and load it into the datatable

    strFileName = Server.MapPath("Downloads") + "\\" + strFileName;

// creating a file in the downloads folder in your solution explorer

    TextWriter tw = new StreamWriter(strFileName);

// using the built in class textwriter for writing your content in the exporting file

    string strData = "Username,Password,City";

// above line is the header for your exported file. So add headings for your coloumns in excel(.csv) file and seperate them with ","

    strData += Environment.NewLine;

// setting the environment to the new line

    foreach (DataRow dr in dt.Rows)
    {
       strData += dr["Username"].ToString() + "," + dr["Password"].ToString() + "," +      dr["City"].ToString();
       strData += Environment.NewLine;
    }

// everytime when loop execute, it adds a line into the file
    tw.Write(strData);

// writing the contents in file
    tw.Close();

// closing the file
    Response.Redirect("Downloads/" + filename);

// exporting the file to the user as a popup to save as....
}
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.