Gravando dados no arquivo CSV em C #

198

Estou tentando gravar em um csvarquivo linha por linha usando a linguagem C #. Aqui está a minha função

string first = reader[0].ToString();
string second=image.ToString();
string csv = string.Format("{0},{1}\n", first, second);
File.WriteAllText(filePath, csv);

Toda a função é executada dentro de um loop, e cada linha deve ser gravada no csvarquivo. No meu caso, a próxima linha substitui a linha existente e, no final, estou recebendo um único registro no arquivo csv, que é o último. Como posso escrever todas as linhas do csvarquivo?

rampuriyaaa
fonte
Em vez disso, use um StringBuildere faça um salvar?
Johan
1
Se isto não é uma tarefa que você precisa para cumprir diária, eu recomendo usar LinqPad, que vem com uma função útil para gravar dados em um arquivo CSV:Util.WriteCsv (mydatacollection, @"c:\temp\data.csv");
Marco
3
Em uma nota lateral, verifique se seus valores csv estão codificados. Ou seja, se um deles contiver uma vírgula ou um caractere de fim de linha, poderá atrapalhar seu arquivo. Eu normalmente apenas uso uma lib de terceiros para coisas de CSV.
Matthijs Wessels
@MatthijsWessels Alguma sugestão de biblioteca?
Arash Motamedi

Respostas:

311

ATUALIZAR

Nos meus dias ingênuos, sugeri fazer isso manualmente (era uma solução simples para uma pergunta simples); no entanto, devido a isso se tornar cada vez mais popular, eu recomendaria o uso da biblioteca CsvHelper que faz todas as verificações de segurança etc.

O CSV é muito mais complicado do que o que a pergunta / resposta sugere.

Resposta original

Como você já tem um loop, considere fazer o seguinte:

//before your loop
    var csv = new StringBuilder();

//in your loop
    var first = reader[0].ToString();
    var second = image.ToString();
    //Suggestion made by KyleMit
    var newLine = string.Format("{0},{1}", first, second);
    csv.AppendLine(newLine);  

//after your loop
    File.WriteAllText(filePath, csv.ToString());

Ou algo nesse sentido. Meu raciocínio é: você não precisará gravar no arquivo para cada item, apenas abrirá o fluxo uma vez e depois gravará nele.

Você pode substituir

File.WriteAllText(filePath, csv.ToString());

com

File.AppendAllText(filePath, csv.ToString());

se você deseja manter as versões anteriores do csv no mesmo arquivo

C # 6

Se você estiver usando o c # 6.0, poderá fazer o seguinte

var newLine = $"{first},{second}"

EDITAR

Aqui está um link para uma pergunta que explica o que Environment.NewLinefaz.

Johan
fonte
1
@Rajat não, porque 2 é uma nova linha
Johan
4
Você também pode se livrar de {2}e Environment.NewLinee usar AppendLineem vez deAppend
KyleMit
37
E o que acontece quando seu conteúdo CSV possui uma vírgula que precisa ser escapada? Você precisa de aspas. E o que acontece quando uma cotação precisa escapar? A criação correta de arquivos CSV é muito mais complexa do que esta resposta implica.
MgSam
1
Eu tenho um "ExceptionType": "System.UnauthorizedAccessException",
Chit Khine
3
Eu tentei sua solução, mas para mim (cultura francesa) funciona melhor usando em System.Globalization.CultureInfo.CurrentCulture.TextInfo.ListSeparatorvez de vírgula.
A.Pissicat
89

Eu recomendo que você siga a rota mais tediosa. Especialmente se o tamanho do seu arquivo for grande.

using(var w = new StreamWriter(path))
{
    for( /* your loop */)
    {
        var first = yourFnToGetFirst();
        var second = yourFnToGetSecond();
        var line = string.Format("{0},{1}", first, second);
        w.WriteLine(line);
        w.Flush();
    }
}

File.AppendAllText()abre um novo arquivo, grava o conteúdo e depois fecha o arquivo. Abrir arquivos é uma operação com muitos recursos, do que gravar dados em fluxo aberto. Abrir / fechar um arquivo dentro de um loop causará queda no desempenho.

A abordagem sugerida por Johan resolve esse problema armazenando toda a saída na memória e depois escrevendo-a uma vez. No entanto (no caso de arquivos grandes), o programa consumirá uma grande quantidade de RAM e até travará comOutOfMemoryException

Outra vantagem da minha solução é que você pode implementar a pausa \ retomando salvando a posição atual nos dados de entrada.

upd. Colocado usando no lugar certo

Pavel Murygin
fonte
1
Você deve colocar seu loop for dentro da usinginstrução Caso contrário, você vai reabrir o arquivo o tempo todo repetidamente.
Oliver
2
Boa resposta. Resposta correta se você remover "{2}" da string de formato e substituir "imagem" por "segundo" na mesma linha. Além disso, não esqueça de fechar o escritor com w.Close (); w.Flush () não é necessário.
MovAX13h
6
Esta resposta ignora a necessidade de escapar caracteres.
MgSam
Podemos também acrescentar que ajuda a definir o escritor como este: new StreamWriter (caminho, falsa, Encoding.UTF8)
Charkins12
se você tem algumas centenas de milhões de linhas ... você deve liberar todas as linhas?
Ardianto Suhendar 31/03/19
30

A gravação manual de arquivos CSV pode ser difícil, pois seus dados podem conter vírgulas e novas linhas. Sugiro que você use uma biblioteca existente.

Esta pergunta menciona algumas opções.

Existem bibliotecas de leitores / gravadores de CSV em C #?

Jeppe Andreasen
fonte
4
Esta é a única resposta razoavelmente correta aqui. É uma pena que as outras respostas ruins que tentam fazê-lo manualmente tenham muitos votos positivos.
MgSam
17

Eu uso uma solução de duas análises, pois é muito fácil manter

// Prepare the values
var allLines = (from trade in proposedTrades
                select new object[] 
                { 
                    trade.TradeType.ToString(), 
                    trade.AccountReference, 
                    trade.SecurityCodeType.ToString(), 
                    trade.SecurityCode, 
                    trade.ClientReference, 
                    trade.TradeCurrency, 
                    trade.AmountDenomination.ToString(), 
                    trade.Amount, 
                    trade.Units, 
                    trade.Percentage, 
                    trade.SettlementCurrency, 
                    trade.FOP, 
                    trade.ClientSettlementAccount, 
                    string.Format("\"{0}\"", trade.Notes),                             
                }).ToList();

// Build the file content
var csv = new StringBuilder();
allLines.ForEach(line => 
{
    csv.AppendLine(string.Join(",", line));            
});

File.WriteAllText(filePath, csv.ToString());
user3495843
fonte
1
Você pode encontrar pressão de memória com essa abordagem ao criar arquivos grandes. Se você removesse o .ToList, o allLines seria um IEnumerbale <object []>. Você pode então selecionar isso em vez de "para cada um", ou seja, allines, Select (line => csv.AppendLine (string.Join (",", line))) que forneceria a IEnumerable <string>. Agora isso pode ser passado apenas para o arquivo, mas o método WriteAllLines. Agora, isso significa que tudo é preguiçoso e você não precisa colocar tudo na memória, mas ainda assim obtém a sintaxe com a qual está feliz.
RhysC
Estou usando seu método para gravar em um arquivo CSV. É ótimo! Minha última implementação exige que eu use em File.AppendAllLines(filePath, lines, Encoding.ASCII);vez de File.WriteAllText(filePath, csv.ToString()); Então, estou fazendo algo muito desajeitado. Depois de criar o csv usando StringBuilder, eu o converto em uma List <string> para obter um IEnumerable para a chamada a AppendAllLines, que não aceita csv.ToString () como parâmetro: List<string> lines = new List<string>(); lines.Add(csv.ToString(0, csv.Length));você tem uma maneira melhor de fazer isso?
Patricia
12

Em vez de ligar todas as vezes, AppendAllText()você pode pensar em abrir o arquivo uma vez e depois escrever o conteúdo inteiro uma vez:

var file = @"C:\myOutput.csv";

using (var stream = File.CreateText(file))
{
    for (int i = 0; i < reader.Count(); i++)
    {
        string first = reader[i].ToString();
        string second = image.ToString();
        string csvRow = string.Format("{0},{1}", first, second);

        stream.WriteLine(csvRow);
    }
}
Oliver
fonte
@aMazing: Desculpe, um erro de digitação. Agora está corrigido.
1276 Oliver
Esta resposta inclui que a extensão deve ser csv. Se for xls, todos os dados aparecerão em uma coluna ... csv, aparecerão corretamente em cada coluna.
gman
10

Basta usar AppendAllText:

File.AppendAllText(filePath, csv);

A única desvantagem do AppendAllText é que ele gera erro quando o arquivo não existe, portanto, isso deve ser verificado

Desculpe, momento loiro antes de ler a documentação . De qualquer forma, o método WriteAllText substitui tudo o que foi gravado anteriormente no arquivo, se o arquivo existir.

Observe que seu código atual não está usando novas linhas apropriadas; por exemplo, no Bloco de notas, você verá tudo isso como uma linha longa. Altere o código para isso para ter novas linhas apropriadas:

string csv = string.Format("{0},{1}{2}", first, image, Environment.NewLine);
Shadow Wizard é Ear For You
fonte
2
@Rajat não, isso escreverá o novo caractere de linha logo após os dados da imagem.
Shadow Wizard é Ear For You
7

Em vez de reinventar a roda, uma biblioteca poderia ser usada. CsvHelperé ótimo para criar e ler arquivos csv. Suas operações de leitura e gravação são baseadas em fluxo e, portanto, também suportam operações com uma grande quantidade de dados.


Você pode escrever seu csv da seguinte maneira.

using(var textWriter = new StreamWriter(@"C:\mypath\myfile.csv"))
{
    var writer = new CsvWriter(textWriter, CultureInfo.InvariantCulture);
    writer.Configuration.Delimiter = ",";

    foreach (var item in list)
    {
        writer.WriteField( "a" );
        writer.WriteField( 2 );
        writer.WriteField( true );
        writer.NextRecord();
    }
}

Como a biblioteca está usando reflexão, ela pega qualquer tipo e a analisa diretamente.

public class CsvRow
{
    public string Column1 { get; set; }
    public bool Column2 { get; set; }

    public CsvRow(string column1, bool column2)
    {
        Column1 = column1;
        Column2 = column2;
    }
}

IEnumerable<CsvRow> rows = new [] {
    new CsvRow("value1", true),
    new CsvRow("value2", false)
};
using(var textWriter = new StreamWriter(@"C:\mypath\myfile.csv")
{
    var writer = new CsvWriter(textWriter, CultureInfo.InvariantCulture);
    writer.Configuration.Delimiter = ",";
    writer.WriteRecords(rows);
}

value1, true

valor2, falso


Se você quiser ler mais sobre as configurações e possibilidades das bibliotecas, faça isso aqui .

NtFreX
fonte
Talvez você precise passar CultureInfo para StreamWriter para que isso funcione.
alif
6
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.Data;
using System.Configuration;
using System.Data.SqlClient;

public partial class CS : System.Web.UI.Page
{
    protected void ExportCSV(object sender, EventArgs e)
    {
        string constr = ConfigurationManager.ConnectionStrings["constr"].ConnectionString;
        using (SqlConnection con = new SqlConnection(constr))
        {
            using (SqlCommand cmd = new SqlCommand("SELECT * FROM Customers"))
            {
                using (SqlDataAdapter sda = new SqlDataAdapter())
                {
                    cmd.Connection = con;
                    sda.SelectCommand = cmd;
                    using (DataTable dt = new DataTable())
                    {
                        sda.Fill(dt);

                        //Build the CSV file data as a Comma separated string.
                        string csv = string.Empty;

                        foreach (DataColumn column in dt.Columns)
                        {
                            //Add the Header row for CSV file.
                            csv += column.ColumnName + ',';
                        }

                        //Add new line.
                        csv += "\r\n";

                        foreach (DataRow row in dt.Rows)
                        {
                            foreach (DataColumn column in dt.Columns)
                            {
                                //Add the Data rows.
                                csv += row[column.ColumnName].ToString().Replace(",", ";") + ',';
                            }

                            //Add new line.
                            csv += "\r\n";
                        }

                        //Download the CSV file.
                        Response.Clear();
                        Response.Buffer = true;
                        Response.AddHeader("content-disposition", "attachment;filename=SqlExport.csv");
                        Response.Charset = "";
                        Response.ContentType = "application/text";
                        Response.Output.Write(csv);
                        Response.Flush();
                        Response.End();
                    }
                }
            }
        }
    }
}
Alejandro Haro
fonte
por que nenhum construtor de strings aqui?
Seabizkit
1
Qual RFC você usou para escrever isso .Replace(",", ";") + ',' :?
precisa
Desta forma, leva muito tempo se você tiver mais de 20.000 linhas e 30 colunas
Vikrant
3

Manipulação de vírgulas

Para manipular vírgulas dentro dos valores ao usar string.Format(...), o seguinte funcionou para mim:

var newLine = string.Format("\"{0}\",\"{1}\",\"{2}\"",
                              first,
                              second,
                              third                                    
                              );
csv.AppendLine(newLine);

Então, para combiná-lo com a resposta de Johan, ficaria assim:

//before your loop
var csv = new StringBuilder();

//in your loop
  var first = reader[0].ToString();
  var second = image.ToString();
  //Suggestion made by KyleMit
  var newLine = string.Format("\"{0}\",\"{1}\"", first, second);
  csv.AppendLine(newLine);  

//after your loop
File.WriteAllText(filePath, csv.ToString());

Retornando arquivo CSV

Se você simplesmente deseja retornar o arquivo em vez de gravá-lo em um local, este é um exemplo de como eu o realizei:

De um procedimento armazenado

public FileContentResults DownloadCSV()
{
  // I have a stored procedure that queries the information I need
  SqlConnection thisConnection = new SqlConnection("Data Source=sv12sql;User ID=UI_Readonly;Password=SuperSecure;Initial Catalog=DB_Name;Integrated Security=false");
  SqlCommand queryCommand = new SqlCommand("spc_GetInfoINeed", thisConnection);
  queryCommand.CommandType = CommandType.StoredProcedure;

  StringBuilder sbRtn = new StringBuilder();

  // If you want headers for your file
  var header = string.Format("\"{0}\",\"{1}\",\"{2}\"",
                             "Name",
                             "Address",
                             "Phone Number"
                            );
  sbRtn.AppendLine(header);

  // Open Database Connection
  thisConnection.Open();
  using (SqlDataReader rdr = queryCommand.ExecuteReader())
  {
    while (rdr.Read())
    {
      // rdr["COLUMN NAME"].ToString();
      var queryResults = string.Format("\"{0}\",\"{1}\",\"{2}\"",
                                        rdr["Name"].ToString(),
                                        rdr["Address"}.ToString(),
                                        rdr["Phone Number"].ToString()
                                       );
      sbRtn.AppendLine(queryResults);
    }
  }
  thisConnection.Close();

  return File(new System.Text.UTF8Encoding().GetBytes(sbRtn.ToString()), "text/csv", "FileName.csv");
}

De uma lista

/* To help illustrate */
public static List<Person> list = new List<Person>();

/* To help illustrate */
public class Person
{
  public string name;
  public string address;
  public string phoneNumber;
}

/* The important part */
public FileContentResults DownloadCSV()
{
  StringBuilder sbRtn = new StringBuilder();

  // If you want headers for your file
  var header = string.Format("\"{0}\",\"{1}\",\"{2}\"",
                             "Name",
                             "Address",
                             "Phone Number"
                            );
  sbRtn.AppendLine(header);

  foreach (var item in list)
  {
      var listResults = string.Format("\"{0}\",\"{1}\",\"{2}\"",
                                        item.name,
                                        item.address,
                                        item.phoneNumber
                                       );
      sbRtn.AppendLine(listResults);
    }
  }

  return File(new System.Text.UTF8Encoding().GetBytes(sbRtn.ToString()), "text/csv", "FileName.csv");
}

Espero que isso seja útil.

Trevor Nestman
fonte
2

Este é um tutorial simples sobre a criação de arquivos csv usando C # que você poderá editar e expandir para atender às suas próprias necessidades.

Primeiro, você precisará criar um novo aplicativo de console do Visual Studio C #. Existem etapas a seguir para fazer isso.

O código de exemplo criará um arquivo csv chamado MyTest.csv no local que você especificar. O conteúdo do arquivo deve ter 3 colunas nomeadas com texto nas 3 primeiras linhas.

https://tidbytez.com/2018/02/06/how-to-create-a-csv-file-with-c/

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.IO;

namespace CreateCsv
{
    class Program
    {
        static void Main()
        {
            // Set the path and filename variable "path", filename being MyTest.csv in this example.
            // Change SomeGuy for your username.
            string path = @"C:\Users\SomeGuy\Desktop\MyTest.csv";

            // Set the variable "delimiter" to ", ".
            string delimiter = ", ";

            // This text is added only once to the file.
            if (!File.Exists(path))
            {
                // Create a file to write to.
                string createText = "Column 1 Name" + delimiter + "Column 2 Name" + delimiter + "Column 3 Name" + delimiter + Environment.NewLine;
                File.WriteAllText(path, createText);
            }

            // This text is always added, making the file longer over time
            // if it is not deleted.
            string appendText = "This is text for Column 1" + delimiter + "This is text for Column 2" + delimiter + "This is text for Column 3" + delimiter + Environment.NewLine;
            File.AppendAllText(path, appendText);

            // Open the file to read from.
            string readText = File.ReadAllText(path);
            Console.WriteLine(readText);
        }
    }
}
Bloggins
fonte
2
Um link para uma solução é bem-vindo, mas garanta que sua resposta seja útil sem ela: adicione contexto ao link para que seus colegas usuários tenham uma idéia do que é e por que está lá, depois cite a parte mais relevante da página que você ' reencaminhando para o caso de a página de destino não estar disponível. Respostas que são pouco mais que um link podem ser excluídas.
Shree
Ah entendo. Obrigado pelo feedback. Fiz as alterações destacadas. Por favor, vote.
Bloggins 03/04/19
0

Você pode apenas precisar adicionar um feed de linha "\n\r".

OneWileyDog
fonte
0

Aqui está outra biblioteca de código aberto para criar arquivos CSV facilmente, o Cinchoo ETL

List<dynamic> objs = new List<dynamic>();

dynamic rec1 = new ExpandoObject();
rec1.Id = 10;
rec1.Name = @"Mark";
rec1.JoinedDate = new DateTime(2001, 2, 2);
rec1.IsActive = true;
rec1.Salary = new ChoCurrency(100000);
objs.Add(rec1);

dynamic rec2 = new ExpandoObject();
rec2.Id = 200;
rec2.Name = "Tom";
rec2.JoinedDate = new DateTime(1990, 10, 23);
rec2.IsActive = false;
rec2.Salary = new ChoCurrency(150000);
objs.Add(rec2);

using (var parser = new ChoCSVWriter("emp.csv").WithFirstLineHeader())
{
    parser.Write(objs);
}

Para mais informações, leia o artigo do CodeProject sobre uso.

RajN
fonte
0

Uma maneira simples de se livrar do problema de substituição é usar File.AppendTextpara acrescentar linhas no final do arquivo, como

void Main()
{
    using (System.IO.StreamWriter sw = System.IO.File.AppendText("file.txt"))
    {          
        string first = reader[0].ToString();
        string second=image.ToString();
        string csv = string.Format("{0},{1}\n", first, second);
        sw.WriteLine(csv);
    }
} 
Vinod Srivastav
fonte
0
enter code here

string string_value = string.Empty;

        for (int i = 0; i < ur_grid.Rows.Count; i++)
        {
            for (int j = 0; j < ur_grid.Rows[i].Cells.Count; j++)
            {
                if (!string.IsNullOrEmpty(ur_grid.Rows[i].Cells[j].Text.ToString()))
                {
                    if (j > 0)
                        string_value= string_value+ "," + ur_grid.Rows[i].Cells[j].Text.ToString();
                    else
                    {
                        if (string.IsNullOrEmpty(string_value))
                            string_value= ur_grid.Rows[i].Cells[j].Text.ToString();
                        else
                            string_value= string_value+ Environment.NewLine + ur_grid.Rows[i].Cells[j].Text.ToString();
                    }
                }
            }
        }


        string where_to_save_file = @"d:\location\Files\sample.csv";
        File.WriteAllText(where_to_save_file, string_value);

        string server_path = "/site/Files/sample.csv";
        Response.ContentType = ContentType;
        Response.AppendHeader("Content-Disposition", "attachment; filename=" + Path.GetFileName(server_path));
        Response.WriteFile(server_path);
        Response.End();
daemon
fonte
0
public static class Extensions
{
    public static void WriteCSVLine(this StreamWriter writer, IEnumerable<string> fields)
    {
        const string q = @"""";
        writer.WriteLine(string.Join(",",
            fields.Select(
                v => (v.Contains(',') || v.Contains('"') || v.Contains('\n') || v.Contains('\r')) ? $"{q}{v.Replace(q, q + q)}{q}" : v
                )));
    }

    public static void WriteFields(this StreamWriter writer, params string[] fields) => WriteFields(writer, (IEnumerable<string>)fields);
}

Isso deve permitir que você escreva um arquivo csv de maneira bastante simples. Uso:

StreamWriter writer = new StreamWriter("myfile.csv");
writer.WriteCSVLine(new[]{"A", "B"});
trinalbadger587
fonte