Escribir datos en un archivo CSV en C #

198

Estoy tratando de escribir en un csvarchivo fila por fila usando lenguaje C #. Aqui esta mi funcion

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

Toda la función se ejecuta dentro de un bucle, y cada fila debe escribirse en el csvarchivo. En mi caso, la siguiente fila sobrescribe la fila existente y, al final, obtengo un único registro en el archivo csv, que es el último. ¿Cómo puedo escribir todas las filas en el csvarchivo?

rampuriyaaa
fuente
¿Prefieres usar ay StringBuilderluego hacer uno para salvar?
Johan
1
Si no es una tarea que debe realizar a diario, le recomiendo usar LinqPad, que viene con una función práctica para escribir datos en un csv:Util.WriteCsv (mydatacollection, @"c:\temp\data.csv");
Marco
3
En una nota al margen, asegúrese de que sus valores csv estén codificados. Es decir, si uno de ellos contiene una coma o un carácter de fin de línea, podría estropear su archivo. Por lo general, solo uso una biblioteca de terceros para cosas de csv.
Matthijs Wessels
@MatthijsWessels ¿Alguna sugerencia de biblioteca?
Arash Motamedi

Respuestas:

311

ACTUALIZAR

En mis días ingenuos, sugerí hacer esto manualmente (era una solución simple a una pregunta simple), sin embargo, debido a que esto se está volviendo cada vez más popular, recomendaría usar la biblioteca CsvHelper que hace todas las comprobaciones de seguridad, etc.

CSV es mucho más complicado de lo que sugiere la pregunta / respuesta.

Respuesta original

Como ya tienes un bucle, considera hacerlo así:

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

O algo por el estilo. Mi razonamiento es: no necesitará escribir en el archivo para cada elemento, solo abrirá la secuencia una vez y luego le escribirá.

Usted puede reemplazar

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

con

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

si desea mantener versiones anteriores de csv en el mismo archivo

C # 6

Si está utilizando c # 6.0, puede hacer lo siguiente

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

EDITAR

Aquí hay un enlace a una pregunta que explica qué Environment.NewLinehace.

Johan
fuente
1
@Rajat no, porque 2 es una nueva línea
Johan
44
También puede deshacerse de {2}y Environment.NewLineusar en AppendLinelugar deAppend
KyleMit
37
¿Y qué sucede cuando su contenido CSV tiene una coma que necesita escapar? Necesitas cotizaciones. ¿Y qué sucede cuando una cita necesita escapar? La construcción correcta de los archivos CSV es mucho más compleja de lo que implica esta respuesta.
MgSam
1
Obtuve un "exceptionType": "System.UnauthorizedAccessException",
Obtuve
3
He intentado su solución, pero para mí (cultura francesa) funciona mejor usando en System.Globalization.CultureInfo.CurrentCulture.TextInfo.ListSeparatorlugar de coma.
A.Pissicat
89

Te recomiendo encarecidamente que vayas por la ruta más tediosa. Especialmente si el tamaño de su archivo es 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 un nuevo archivo, escribe el contenido y luego cierra el archivo. Abrir archivos es una operación que requiere muchos recursos, que escribir datos en una secuencia abierta. Abrir \ cerrar un archivo dentro de un bucle provocará una caída del rendimiento.

El enfoque sugerido por Johan resuelve ese problema almacenando toda la salida en la memoria y luego escribiéndola una vez. Sin embargo (en el caso de archivos grandes) su programa consumirá una gran cantidad de RAM e incluso se bloqueará conOutOfMemoryException

Otra ventaja de mi solución es que puede implementar pausa / reanudación guardando la posición actual en los datos de entrada.

upd Colocado usando en el lugar correcto

Pavel Murygin
fuente
1
Debe poner su bucle for dentro de la usingdeclaración. De lo contrario, volverá a abrir el archivo una y otra vez.
Oliver
2
Buena respuesta. Respuesta correcta si elimina "{2}" de la cadena de formato y reemplaza "imagen" con "segundo" en la misma línea. Además, no olvide cerrar el escritor con w.Close (); w.Flush () no es necesario.
movAX13h
66
Esta respuesta ignora la necesidad de escapar de los personajes.
MgSam
¿Podemos agregar también que ayuda a configurar su escritor de esta manera: nuevo StreamWriter (path, false, Encoding.UTF8)
Charkins12
si tienes unos cientos de millones de líneas ... ¿deberías enjuagar cada línea?
Ardianto Suhendar
30

Escribir archivos csv a mano puede ser difícil porque sus datos pueden contener comas y líneas nuevas. Le sugiero que use una biblioteca existente en su lugar.

Esta pregunta menciona algunas opciones.

¿Hay alguna biblioteca de lectores / escritores CSV en C #?

Jeppe Andreasen
fuente
44
Esta es la única respuesta razonablemente correcta aquí. Es una pena que las otras malas respuestas que intentan hacerlo a mano tengan tantos votos positivos.
MgSam
17

Utilizo una solución de dos análisis, ya que es muy fácil de mantener.

// 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());
usuario3495843
fuente
1
Es posible que encuentre presión de memoria con este enfoque al construir archivos grandes. Si eliminó .ToList, allLines sería un IEnumerbale <object []>. A continuación, puede seleccionar eso en lugar de "para cada uno", es decir, todos, seleccione (línea => csv.AppendLine (string.Join (",", line))) que le daría un IEnumerable <string>. Esto ahora solo se puede pasar al Archivo pero al método WriteAllLines. Esto ahora significa que todo el asunto es perezoso y no necesita extraer todo en la memoria, pero aún así obtiene la sintaxis con la que está satisfecho.
RhysC
Estoy usando su método para escribir en un archivo csv. ¡Es genial! Mi última implementación requiere que use en File.AppendAllLines(filePath, lines, Encoding.ASCII);lugar de File.WriteAllText(filePath, csv.ToString()); Entonces, estoy haciendo algo muy complicado. Después de construir el csv usando StringBuilder, lo convierto en List <string> para obtener un IEnumerable para la llamada a AppendAllLines, que no aceptará csv.ToString () como parámetro: List<string> lines = new List<string>(); lines.Add(csv.ToString(0, csv.Length));¿Tiene una mejor manera de hacer esto?
Patricia
12

En lugar de llamar cada vez AppendAllText()que pueda pensar en abrir el archivo una vez y luego escribir todo el contenido una 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
fuente
@aMazing: Lo siento, un error tipográfico. Ahora está arreglado.
Oliver
Esta respuesta incluye que la extensión debe ser csv. Si es xls, todos los datos aparecerán en una columna ... csv aparecerá correctamente en cada columna.
Gman
10

Simplemente use AppendAllText en su lugar:

File.AppendAllText(filePath, csv);

El único inconveniente de AppendAllText es que arrojará un error cuando el archivo no exista, por lo que debe verificarse

Lo siento, momento rubio antes de leer la documentación . De todos modos, el método WriteAllText sobrescribe todo lo que se escribió anteriormente en el archivo, si el archivo existe.

Tenga en cuenta que su código actual no está usando líneas nuevas adecuadas, por ejemplo, en el Bloc de notas, verá todo como una línea larga. Cambie el código a esto para tener nuevas líneas adecuadas:

string csv = string.Format("{0},{1}{2}", first, image, Environment.NewLine);
Shadow Wizard es Ear for You
fuente
2
@ Rajat no, esto escribirá el nuevo carácter de línea justo después de los datos de su imagen.
Shadow Wizard es Ear For You
7

En lugar de reinventar la rueda, se podría usar una biblioteca. CsvHelperes ideal para crear y leer archivos csv. Sus operaciones de lectura y escritura se basan en la transmisión y, por lo tanto, también admiten operaciones con una gran cantidad de datos.


Puede escribir su csv de la siguiente manera.

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 la biblioteca está utilizando la reflexión, tomará cualquier tipo y lo analizará directamente.

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

valor1, verdadero

valor2, falso


Si desea leer más sobre las configuraciones y posibilidades de las bibliotecas, puede hacerlo aquí .

NtFreX
fuente
Es posible que deba pasar CultureInfo a StreamWriter para que esto 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
fuente
¿Por qué no hay un generador de cadenas aquí?
Seabizkit
1
¿Qué RFC usaste para escribir esto .Replace(",", ";") + ',' ?
vt100
Este camino lleva demasiado tiempo si tiene más de 20000 filas y 30 columnas
Vikrant
3

Manejo de comas

Para manejar comas dentro de valores cuando se usa string.Format(...), lo siguiente me ha funcionado:

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

Entonces, para combinarlo con la respuesta de Johan, se vería así:

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

Devolver archivo CSV

Si simplemente desea devolver el archivo en lugar de escribirlo en una ubicación, este es un ejemplo de cómo lo logré:

De un procedimiento almacenado

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 una 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 esto sea útil.

Trevor Nestman
fuente
2

Este es un tutorial simple sobre cómo crear archivos csv con C # que podrá editar y ampliar para satisfacer sus propias necesidades.

Primero necesitará crear una nueva aplicación de consola de Visual Studio C #, hay pasos a seguir para hacerlo.

El código de ejemplo creará un archivo csv llamado MyTest.csv en la ubicación que especifique. El contenido del archivo debe ser 3 columnas con nombre con texto en las primeras 3 filas.

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
fuente
2
Un enlace a una solución es bienvenido, pero asegúrese de que su respuesta sea útil sin él: agregue contexto alrededor del enlace para que sus otros usuarios tengan una idea de qué es y por qué está allí, luego cite la parte más relevante de la página volver a vincular en caso de que la página de destino no esté disponible. Se pueden eliminar las respuestas que son poco más que un enlace.
Shree
Ah, ya veo. Gracias por la respuesta. He realizado los cambios resaltados. Por favor vota arriba.
Bloggins
0

Es posible que solo tenga que agregar un avance de línea "\n\r".

OneWileyDog
fuente
0

Aquí hay otra biblioteca de código abierto para crear archivos CSV fácilmente, 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 obtener más información, lea el artículo de CodeProject sobre el uso.

RajN
fuente
0

Una forma sencilla de deshacerse del problema de sobrescritura es usar File.AppendTextpara agregar una línea al final del archivo 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
fuente
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();
demonio
fuente
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);
}

Esto debería permitirle escribir un archivo csv de manera bastante simple. Uso:

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