Como criar um arquivo CSV Excel em C #? [fechadas]

132

Estou procurando uma classe para criar arquivos CSV Excel.

Recursos esperados:

  • Extremamente simples de usar
  • Escapa vírgulas e aspas para que o Excel lide com elas
  • Exporta data e horas no formato à prova de fuso horário

Você conhece alguma classe capaz disso?

Chris
fonte
12
melhor colocar a pergunta na parte PERGUNTA e, em seguida, poste sua própria resposta na parte RESPOSTA. Adicione tags e palavras-chave na pergunta para torná-lo pesquisável.
Cheeso
IMPORTANTE: você também deve adicionar aspas quando houver RETORNOS DE CARRO no "valor".
24414 Alex
Obrigado @ Chris, uma sugestão, se puder, este código pode gerar uma KeyNotFoundException, por favor, veja minha resposta.
Joseph
No entanto, se você deseja adicionar uma tabela de duas linhas em um único arquivo, isso significa que eu tenho uma tabela de duas linhas e outra tabela é de 10 linhas e ambas têm um nome de coluna exclusivo. intervalo de duas linhas que eu quero adicionar a segunda tabela.
Floki

Respostas:

92

Versão ligeiramente diferente que escrevi usando a reflexão para minhas necessidades. Eu tive que exportar uma lista de objetos para CSV. Caso alguém queira usá-lo para o 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;

        }
    }

Exemplo de uso: (atualizado por comentário)

CsvExport<BusinessObject> csv= new CsvExport<BusinessObject>(GetBusinessObjectList());
Response.Write(csv.Export());
net codificador
fonte
5
Era mais assim: List <BusinessObject> x = new List <BusinessObject> (); CsvExport <BusinessObject> x = novo CsvExport <BusinessObject> (MUsers);
escondida
5
De onde veio a sua interface INullable?
Kilhoffer
No entanto, se você deseja adicionar uma tabela de duas linhas em um único arquivo, isso significa que eu tenho uma tabela de duas linhas e outra tabela é de 10 linhas e ambas têm um nome de coluna exclusivo. intervalo de duas linhas que eu quero adicionar a segunda tabela.
Floki
2
Eu sei que a postagem original era de 2011, então não tenho certeza se era possível na versão .NET que era usada na época. Mas por que não remover o public string Export()método e alterar o outro método para public string Export(bool includeHeaderLiner = true)(com um valor de parâmetro padrão). Novamente, não tenho certeza se os parâmetros padrão estavam disponíveis em 2011, mas o código atual apenas parece ortodoxo para mim.
Kevin Cruijssen
19

Por favor me perdoe

Mas acho que um repositório público de código aberto é a melhor maneira de compartilhar código e fazer contribuições, correções e acréscimos como "eu consertei isso, consertei aquilo"

Então, eu criei um repositório git simples com o código do iniciador de tópicos e todas as adições:

https://github.com/jitbit/CsvExport

Também adicionei algumas correções úteis. Todo mundo poderia adicionar sugestões, fazer uma bifurcação para contribuir, etc.

PS. Publiquei todos os avisos de direitos autorais para Chris. @ Chris, se você é contra essa idéia - me avise, eu mato.

6 rotações
fonte
11

Outra boa solução para ler e gravar arquivos CSV é o filehelpers (código aberto).

Jelle
fonte
Nota: O suporte ao Excel é apenas para cenários básicos : O suporte implementado atualmente ao Excel é apenas para cenários básicos. Se você precisar de formatação personalizada, gráficos, etc., deverá escolher um código personalizado. É altamente recomendado o uso direto da biblioteca NPOI
AK
6

Que tal usar string.Join em vez de todos os Loops foreach?

Hinek
fonte
String.Join só funciona na string [], enquanto eu estou usando alguns dos recursos da List <string>.
31410 Chris
12
String.Join("," , List<string>)funciona também.
Dementic
6

Se alguém quiser eu converti isso para um método de extensão no 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;
    }
}
KeyboardCowboy
fonte
5

ótimo trabalho nessa aula. Simples e fácil de usar. Modifiquei a classe para incluir um título na primeira linha da exportação; achei que eu iria compartilhar:

usar:

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());
    }
}
gnomo
fonte
3

Adicionei ExportToStream para que o csv não tenha que salvar primeiro no disco rígido.

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

eu já adicionei

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);

}

O código anterior não funciona com versões antigas do .NET. Para a versão 3.5 da estrutura, use esta outra versão:

        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());
    }
Rodrigo Araujo
fonte
2

Muito obrigado por isso! Modifiquei a classe para:

  • use um delimitador de variável, em vez de codificado no código
  • substituindo todos os novosLines (\ n \ r \ n \ r) no MakeValueCsvFriendly

Código:

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());

        }

    }
Munchies
fonte
1

A classe original tem um problema e, se você quiser adicionar uma nova coluna, receberá KeyNotFoundException no método Export. Por exemplo:

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");
}

Para resolver isso, e usando a ideia do @KeyboardCowboy de usar reflexão, modifiquei o código para permitir adicionar linhas que não tenham as mesmas colunas. Você pode usar instâncias de classes anônimas. Por exemplo:

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");
}

Você pode baixar o código fonte aqui CsvExporter . Sinta-se livre para usar e modificar.

Agora, se todas as linhas que você deseja gravar forem da mesma classe, criei a classe genérica CsvWriter.cs , que possui um melhor desempenho de uso da RAM e é ideal para gravar arquivos grandes. Além disso, permite adicionar formatadores ao tipo de dados que você deseja . Um exemplo de uso:

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; }
    }
Joseph
fonte
0

Você precisa de apenas 1 função para fazer isso. Só é necessário criar uma pasta no explorador de soluções e armazenar o arquivo csv lá e depois exportá-lo para o usuário.

Como no meu caso, eu tenho uma pasta de downloads. Primeiro exporto todo o meu conteúdo para esse diretório e depois o exporto para o usuário. Para o tratamento response.end, usei o ThreadAbortException. Portanto, é uma função 100% genuína e funcional na minha solução.

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....
}
Suhaib Janjua
fonte