c # tabla de datos a csv

113

¿Alguien podría decirme por qué el siguiente código no funciona? Los datos se guardan en el archivo csv, sin embargo, no se separan. Todo existe dentro de la primera celda de cada fila.

StringBuilder sb = new StringBuilder();

foreach (DataColumn col in dt.Columns)
{
    sb.Append(col.ColumnName + ',');
}

sb.Remove(sb.Length - 1, 1);
sb.Append(Environment.NewLine);

foreach (DataRow row in dt.Rows)
{
    for (int i = 0; i < dt.Columns.Count; i++)
    {
        sb.Append(row[i].ToString() + ",");
    }

    sb.Append(Environment.NewLine);
}

File.WriteAllText("test.csv", sb.ToString());

Gracias.

Darren Young
fuente
Puede consultar este gist.github.com/riyadparvez/4467668
usuario
Desarrollé la extensión de alto rendimiento. verifique esta respuesta
Nigje

Respuestas:

229

La siguiente versión más corta se abre bien en Excel, tal vez su problema fue la coma final

.net = 3.5

StringBuilder sb = new StringBuilder(); 

string[] columnNames = dt.Columns.Cast<DataColumn>().
                                  Select(column => column.ColumnName).
                                  ToArray();
sb.AppendLine(string.Join(",", columnNames));

foreach (DataRow row in dt.Rows)
{
    string[] fields = row.ItemArray.Select(field => field.ToString()).
                                    ToArray();
    sb.AppendLine(string.Join(",", fields));
}

File.WriteAllText("test.csv", sb.ToString());

.net> = 4.0

Y como Tim señaló, si estás en .net> = 4, puedes hacerlo aún más corto:

StringBuilder sb = new StringBuilder(); 

IEnumerable<string> columnNames = dt.Columns.Cast<DataColumn>().
                                  Select(column => column.ColumnName);
sb.AppendLine(string.Join(",", columnNames));

foreach (DataRow row in dt.Rows)
{
    IEnumerable<string> fields = row.ItemArray.Select(field => field.ToString());
    sb.AppendLine(string.Join(",", fields));
}

File.WriteAllText("test.csv", sb.ToString());

Como sugirió Christian, si desea manejar caracteres especiales que se escapan en los campos, reemplace el bloque de bucle por:

foreach (DataRow row in dt.Rows)
{
    IEnumerable<string> fields = row.ItemArray.Select(field => 
      string.Concat("\"", field.ToString().Replace("\"", "\"\""), "\""));
    sb.AppendLine(string.Join(",", fields));
}

Y la última sugerencia, podría escribir el contenido csv línea por línea en lugar de como un documento completo, para evitar tener un documento grande en la memoria.

vc 74
fuente
2
No es necesario copiar el ItemArraya uno nuevo String[], puede omitir .ToArray()con .NET 4 y usar la String.Joinsobrecarga que toma un IEnumerable<T>(editado).
Tim Schmelter
2
@TimSchmelter, sí, pero estas sobrecargas se introdujeron en .net4, el código no se compilará si el OP usa .net <4
vc 74
18
Este método no tiene en cuenta una coma dentro de un valor de columna.
Christian
2
En su lugar, IEnumerable <string> fields = row.ItemArray.Select (field => field.ToString (). Replace ("\" "," \ "\" ")); sb.AppendLine (" \ "" + string.Join ("\", \ "", campos) + "\" ");
Christian
2
@ Si8 ¿Qué quieres decir? Esta respuesta usa solo componentes de base de datos y &nbspes típica de documentos HTML / XML. No es el código anterior el que lo produce a menos que la tabla contenga &nbsp;explícitamente
vc 74
36

Envolví esto en una clase de extensión, que te permite llamar:

myDataTable.WriteToCsvFile("C:\\MyDataTable.csv");

en cualquier DataTable.

public static class DataTableExtensions 
{
    public static void WriteToCsvFile(this DataTable dataTable, string filePath) 
    {
        StringBuilder fileContent = new StringBuilder();

        foreach (var col in dataTable.Columns) 
        {
            fileContent.Append(col.ToString() + ",");
        }

        fileContent.Replace(",", System.Environment.NewLine, fileContent.Length - 1, 1);

        foreach (DataRow dr in dataTable.Rows) 
        {
            foreach (var column in dr.ItemArray) 
            {
                fileContent.Append("\"" + column.ToString() + "\",");
            }

            fileContent.Replace(",", System.Environment.NewLine, fileContent.Length - 1, 1);
        }

        System.IO.File.WriteAllText(filePath, fileContent.ToString());
    }
}
Paul Grimshaw
fuente
24

Una nueva función de extensión basada en la respuesta de Paul Grimshaw. Lo limpié y agregué la capacidad de manejar datos inesperados. (Datos vacíos, citas incrustadas y comas en los encabezados ...)

También devuelve una cadena que es más flexible. Devuelve Null si el objeto de la tabla no contiene ninguna estructura.

    public static string ToCsv(this DataTable dataTable) {
        StringBuilder sbData = new StringBuilder();

        // Only return Null if there is no structure.
        if (dataTable.Columns.Count == 0)
            return null;

        foreach (var col in dataTable.Columns) {
            if (col == null)
                sbData.Append(",");
            else
                sbData.Append("\"" + col.ToString().Replace("\"", "\"\"") + "\",");
        }

        sbData.Replace(",", System.Environment.NewLine, sbData.Length - 1, 1);

        foreach (DataRow dr in dataTable.Rows) {
            foreach (var column in dr.ItemArray) {
                if (column == null)
                    sbData.Append(",");
                else
                    sbData.Append("\"" + column.ToString().Replace("\"", "\"\"") + "\",");
            }
            sbData.Replace(",", System.Environment.NewLine, sbData.Length - 1, 1);
        }

        return sbData.ToString();
    }

Lo llamas de la siguiente manera:

var csvData = dataTableOject.ToCsv();
AnthonyVO
fuente
1
Este es el mejor del resto aquí. Bien hecho. Gracias
Fandango68
Fantástica solución. Se agregaron comentarios a nivel local, pero se pudo usar de inmediato sin tener que escalar la montaña. Gracias.
j.hull
¡Me encantó esto! Lo usé como un método no estático y simplemente pasé mi DataTable como parámetro. Funcionó muy bien, gracias.
Kid Koder
9

Si su código de llamada hace referencia al System.Windows.Formsensamblado, puede considerar un enfoque radicalmente diferente. Mi estrategia es usar las funciones que ya proporciona el marco para lograr esto en muy pocas líneas de código y sin tener que recorrer columnas y filas. Lo que hace el siguiente código es crear programáticamente un DataGridViewsobre la marcha y establecerlo DataGridView.DataSourceen DataTable. A continuación, selecciono programáticamente todas las celdas (incluido el encabezado) en DataGridViewy llamo DataGridView.GetClipboardContent(), colocando los resultados en Windows Clipboard. Luego, 'pego' el contenido del portapapeles en una llamada a File.WriteAllText(), asegurándome de especificar el formato del 'pegar' como TextDataFormat.CommaSeparatedValue.

Aquí está el código:

public static void DataTableToCSV(DataTable Table, string Filename)
{
    using(DataGridView dataGrid = new DataGridView())
    {
        // Save the current state of the clipboard so we can restore it after we are done
        IDataObject objectSave = Clipboard.GetDataObject();

        // Set the DataSource
        dataGrid.DataSource = Table;
        // Choose whether to write header. Use EnableWithoutHeaderText instead to omit header.
        dataGrid.ClipboardCopyMode = DataGridViewClipboardCopyMode.EnableAlwaysIncludeHeaderText;
        // Select all the cells
        dataGrid.SelectAll();
        // Copy (set clipboard)
        Clipboard.SetDataObject(dataGrid.GetClipboardContent());
        // Paste (get the clipboard and serialize it to a file)
        File.WriteAllText(Filename,Clipboard.GetText(TextDataFormat.CommaSeparatedValue));              

        // Restore the current state of the clipboard so the effect is seamless
        if(objectSave != null) // If we try to set the Clipboard to an object that is null, it will throw...
        {
            Clipboard.SetDataObject(objectSave);
        }
    }
}

Tenga en cuenta que también me aseguro de preservar el contenido del portapapeles antes de comenzar y restaurarlo una vez que termine, para que el usuario no reciba un montón de basura inesperada la próxima vez que intente pegar. Las principales advertencias para este enfoque son 1) Su clase tiene que hacer referencia System.Windows.Forms, lo que puede no ser el caso en una capa de abstracción de datos, 2) Su ensamblado tendrá que estar dirigido a .NET 4.5 framework, ya que DataGridView no existe en 4.0, y 3) El método fallará si el portapapeles está siendo utilizado por otro proceso.

De todos modos, este enfoque puede no ser el adecuado para su situación, pero es interesante de todos modos y puede ser otra herramienta en su caja de herramientas.

Adam White
fuente
1
no es necesario utilizar el Portapapeles stackoverflow.com/questions/40726017/… . .GetClipboardContenttambién maneja algunos casos extremos de valores que contienen ,. ", \t(convierte la pestaña en espacio)
Slai
2
Esto es bueno, pero ¿qué pasa si alguien está usando la máquina al mismo tiempo y coloca algo en el Portapapeles en el momento crítico?
Ayo Adesina
7

Hice esto recientemente, pero incluí comillas dobles alrededor de mis valores.

Por ejemplo, cambie estas dos líneas:

sb.Append("\"" + col.ColumnName + "\","); 
...
sb.Append("\"" + row[i].ToString() + "\","); 
Ben Jakuben
fuente
Gracias por la sugerencia, pero ¿todos los datos todavía están dentro de la primera celda de cada fila?
Darren Young
7

Intente cambiar sb.Append(Environment.NewLine);a sb.AppendLine();.

StringBuilder sb = new StringBuilder();          
foreach (DataColumn col in dt.Columns)         
{             
    sb.Append(col.ColumnName + ',');         
}          

sb.Remove(sb.Length - 1, 1);         
sb.AppendLine();          

foreach (DataRow row in dt.Rows)         
{             
    for (int i = 0; i < dt.Columns.Count; i++)             
    {                 
        sb.Append(row[i].ToString() + ",");             
    }              

    sb.AppendLine();         
}          

File.WriteAllText("test.csv", sb.ToString());
Neil Knight
fuente
Eso entonces dará dos devoluciones de carraige.
Darren Young
@alexl: Eso es lo que pensaba originalmente, pero estaba fuera de mi cabeza hasta que VS se encendió: o)
Neil Knight
5

Intenta poner en ;lugar de,

Espero eso ayude

alexl
fuente
5

¿Leer esto y esto ?


Una mejor implementación sería

var result = new StringBuilder();
for (int i = 0; i < table.Columns.Count; i++)
{
    result.Append(table.Columns[i].ColumnName);
    result.Append(i == table.Columns.Count - 1 ? "\n" : ",");
}

foreach (DataRow row in table.Rows)
{
    for (int i = 0; i < table.Columns.Count; i++)
    {
        result.Append(row[i].ToString());
        result.Append(i == table.Columns.Count - 1 ? "\n" : ",");
    }
}
 File.WriteAllText("test.csv", result.ToString());
naveen
fuente
5

El error es el separador de listas.

En lugar de escribir sb.Append(something... + ',')deberías poner algo comosb.Append(something... + System.Globalization.CultureInfo.CurrentCulture.TextInfo.ListSeparator);

Debe poner el carácter separador de lista configurado en su sistema operativo (como en el ejemplo anterior), o el separador de lista en la máquina cliente donde se va a ver el archivo. Otra opción sería configurarlo en app.config o web.config como parámetro de tu aplicación.

Martín Delafuente
fuente
5

4 líneas de código:

public static string ToCSV(DataTable tbl)
{
    StringBuilder strb = new StringBuilder();

    //column headers
    strb.AppendLine(string.Join(",", tbl.Columns.Cast<DataColumn>()
        .Select(s => "\"" + s.ColumnName + "\"")));

    //rows
    tbl.AsEnumerable().Select(s => strb.AppendLine(
        string.Join(",", s.ItemArray.Select(
            i => "\"" + i.ToString() + "\"")))).ToList();

    return strb.ToString();
}

Tenga en cuenta que el ToList()al final es importante; Necesito algo para forzar una evaluación de expresión. Si estuviera jugando al golf en código, podría usar Min()en su lugar.

También tenga en cuenta que el resultado tendrá una nueva línea al final debido a la última llamada a AppendLine(). Puede que no quieras esto. Simplemente puede llamar TrimEnd()para eliminarlo.

user2023861
fuente
3

Aquí hay una mejora de la publicación de vc-74 que maneja las comas de la misma manera que lo hace Excel. Excel pone comillas alrededor de los datos si los datos tienen una coma, pero no las cita si los datos no tienen una coma.

    public static string ToCsv(this DataTable inDataTable, bool inIncludeHeaders = true)
    {
        var builder = new StringBuilder();
        var columnNames = inDataTable.Columns.Cast<DataColumn>().Select(column => column.ColumnName);
        if (inIncludeHeaders)
            builder.AppendLine(string.Join(",", columnNames));
        foreach (DataRow row in inDataTable.Rows)
        {
            var fields = row.ItemArray.Select(field => field.ToString().WrapInQuotesIfContains(","));
            builder.AppendLine(string.Join(",", fields));
        }

        return builder.ToString();
    }

    public static string WrapInQuotesIfContains(this string inString, string inSearchString)
    {
        if (inString.Contains(inSearchString))
            return "\"" + inString+ "\"";
        return inString;
    }
Rhyous
fuente
2

Para escribir en un archivo, creo que el siguiente método es el más eficiente y sencillo: (puede agregar comillas si lo desea)

public static void WriteCsv(DataTable dt, string path)
{
    using (var writer = new StreamWriter(path)) {
        writer.WriteLine(string.Join(",", dt.Columns.Cast<DataColumn>().Select(dc => dc.ColumnName)));
        foreach (DataRow row in dt.Rows) {
            writer.WriteLine(string.Join(",", row.ItemArray));
        }
    }
}
Estudiante222
fuente
2

Para imitar CSV de Excel:

public static string Convert(DataTable dt)
{
    StringBuilder sb = new StringBuilder();

    IEnumerable<string> columnNames = dt.Columns.Cast<DataColumn>().
                                        Select(column => column.ColumnName);
    sb.AppendLine(string.Join(",", columnNames));

    foreach (DataRow row in dt.Rows)
    {
        IEnumerable<string> fields = row.ItemArray.Select(field =>
        {
            string s = field.ToString().Replace("\"", "\"\"");
            if(s.Contains(','))
                s = string.Concat("\"", s, "\"");
            return s;
        });
        sb.AppendLine(string.Join(",", fields));
    }

    return sb.ToString().Trim();
}
James Carter
fuente
1
StringBuilder sb = new StringBuilder();
        SaveFileDialog fileSave = new SaveFileDialog();
        IEnumerable<string> columnNames = tbCifSil.Columns.Cast<DataColumn>().
                                          Select(column => column.ColumnName);
        sb.AppendLine(string.Join(",", columnNames));

        foreach (DataRow row in tbCifSil.Rows)
        {
            IEnumerable<string> fields = row.ItemArray.Select(field =>string.Concat("\"", field.ToString().Replace("\"", "\"\""), "\""));
            sb.AppendLine(string.Join(",", fields));
        }

        fileSave.ShowDialog();
        File.WriteAllText(fileSave.FileName, sb.ToString());
Nam Nguyễn
fuente
¡Bienvenido a StackOverflow! Las respuestas son mejores cuando incluyen una descripción del fragmento de código. Personalmente, he descubierto que cuando los nombres de las variables se alinean entre la pregunta y la respuesta, son más útiles para mí.
AWinkle
1
public void ExpoetToCSV(DataTable dtDataTable, string strFilePath)
{

    StreamWriter sw = new StreamWriter(strFilePath, false);
    //headers   
    for (int i = 0; i < dtDataTable.Columns.Count; i++)
    {
        sw.Write(dtDataTable.Columns[i].ToString().Trim());
        if (i < dtDataTable.Columns.Count - 1)
        {
            sw.Write(",");
        }
    }
    sw.Write(sw.NewLine);
    foreach (DataRow dr in dtDataTable.Rows)
    {
        for (int i = 0; i < dtDataTable.Columns.Count; i++)
        {
            if (!Convert.IsDBNull(dr[i]))
            {
                string value = dr[i].ToString().Trim();
                if (value.Contains(','))
                {
                    value = String.Format("\"{0}\"", value);
                    sw.Write(value);
                }
                else
                {
                    sw.Write(dr[i].ToString().Trim());
                }
            }
            if (i < dtDataTable.Columns.Count - 1)
            {
                sw.Write(",");
            }
        }
        sw.Write(sw.NewLine);
    }
    sw.Close();
}
Ghebrehiywet
fuente
1

Posiblemente, la forma más fácil será utilizar:

https://github.com/ukushu/DataExporter

especialmente en el caso de sus datos de tabla de datos que contienen /r/ncaracteres o símbolo separador dentro de sus celdas de tabla de datos. Casi todas las demás respuestas no funcionarán con estas celdas.

solo necesitas escribir el siguiente código:

Csv csv = new Csv("\t");//Needed delimiter 

var columnNames = dt.Columns.Cast<DataColumn>().
    Select(column => column.ColumnName).ToArray();

csv.AddRow(columnNames);

foreach (DataRow row in dt.Rows)
{
    var fields = row.ItemArray.Select(field => field.ToString()).ToArray;
    csv.AddRow(fields);   
}

csv.Save();
Andrés
fuente
0

En caso de que alguien más se tropiece con esto, estaba usando File.ReadAllText para obtener datos CSV y luego lo modifiqué y lo escribí con File.WriteAllText . Las \ r \ n CRLF estaban bien, pero las pestañas \ t se ignoraron cuando Excel las abrió. (Todas las soluciones en este hilo hasta ahora usan un delimitador de coma, pero eso no importa). El Bloc de notas mostró el mismo formato en el archivo resultante que en la fuente. Un Diff incluso mostró que los archivos eran idénticos. Pero tuve una pista cuando abrí el archivo en Visual Studio con un editor binario. El archivo de origen era Unicode pero el destino era ASCII . Para solucionarlo, modifiqué ReadAllText y WriteAllText con el tercer argumento establecido como System.Text.Encoding.Unicode , y desde allí Excel pudo abrir el archivo actualizado.

TonyG
fuente
0

FYR

private string ExportDatatableToCSV(DataTable dtTable)
{
    StringBuilder sbldr = new StringBuilder();
    if (dtTable.Columns.Count != 0)
    {
        foreach (DataColumn col in dtTable.Columns)
        {
            sbldr.Append(col.ColumnName + ',');
        }
        sbldr.Append("\r\n");
        foreach (DataRow row in dtTable.Rows)
        {
            foreach (DataColumn column in dtTable.Columns)
            {
                sbldr.Append(row[column].ToString() + ',');
            }
            sbldr.Append("\r\n");
        }
    }
    return sbldr.ToString();
}
Julia
fuente
0

Aquí está mi solución, basada en respuestas anteriores de Paul Grimshaw y Anthony VO . Envié el código en un proyecto C # en Github .

Mi principal contribución es eliminar la creación y manipulación explícita de un StringBuildery, en cambio, trabajar solo con IEnumerable. Esto evita la asignación de un gran búfer en la memoria.

public static class Util
{
    public static string EscapeQuotes(this string self) {
        return self?.Replace("\"", "\"\"") ?? "";
    }

    public static string Surround(this string self, string before, string after) {
        return $"{before}{self}{after}";
    }

    public static string Quoted(this string self, string quotes = "\"") {
        return self.Surround(quotes, quotes);
    }

    public static string QuotedCSVFieldIfNecessary(this string self) {
        return (self == null) ? "" : self.Contains('"') ? self.Quoted() : self; 
    }

    public static string ToCsvField(this string self) {
        return self.EscapeQuotes().QuotedCSVFieldIfNecessary();
    }

    public static string ToCsvRow(this IEnumerable<string> self){
        return string.Join(",", self.Select(ToCsvField));
    }

    public static IEnumerable<string> ToCsvRows(this DataTable self) {          
        yield return self.Columns.OfType<object>().Select(c => c.ToString()).ToCsvRow();
        foreach (var dr in self.Rows.OfType<DataRow>())
            yield return dr.ItemArray.Select(item => item.ToString()).ToCsvRow();
    }

    public static void ToCsvFile(this DataTable self, string path) {
        File.WriteAllLines(path, self.ToCsvRows());
    }

}

Este enfoque se combina muy bien con la conversión IEnumerablea DataTable como se solicita aquí .

cdiggins
fuente
0
        DataTable dt = yourData();
        StringBuilder csv = new StringBuilder();
        int dcCounter = 0;

        foreach (DataColumn dc in dt.Columns)
        {
            csv.Append(dc);
            if (dcCounter != dt.Columns.Count - 1)
            {
                csv.Append(",");
            }
            dcCounter++;
        }
        csv.AppendLine();

        int numOfDc = dt.Columns.Count;
        foreach (DataRow dr in dt.Rows)
        {
            int colIndex = 0;
            while (colIndex <= numOfDc - 1)
            {
                var colVal = dr[colIndex].ToString();
                if (colVal != null && colVal != "")
                {
                    DateTime isDateTime;
                    if (DateTime.TryParse(colVal, out isDateTime))
                    {
                        csv.Append(Convert.ToDateTime(colVal).ToShortDateString());
                    }
                    else
                    {
                        csv.Append(dr[colIndex]);
                    }
                }
                else
                {
                    csv.Append("N/A");
                }
                if (colIndex != numOfDc - 1)
                {
                    csv.Append(",");
                }
                colIndex++;
            }
            csv.AppendLine();

También necesitaba anular algunos datos, por lo que hay algunas declaraciones "si más". Necesitaba asegurarme de que si un campo estaba vacío para ingresar "N / A" en su lugar, o si un campo de fecha tenía el formato "01/01/1900: 00", se guardaría como "01/01/1900" en lugar.

BondAddict
fuente
0
StringBuilder sb = new StringBuilder();

        foreach (DataColumn col in table.Columns)
        {
            sb.Append(col.ColumnName + ";");
        }

        foreach (DataRow row in table.Rows)
        {
            sb.AppendLine();
            foreach (DataColumn col in table.Columns)
            {
                sb.Append($@"{Convert.ToString(row[col])}" + ";");
            }
        }
        File.WriteAllText(path, sb.ToString());
Inuria
fuente
-1

Si todos los datos aún están en la primera celda, significa que la aplicación con la que abrió el archivo espera otro delimitador. MSExcel puede manejar la coma como delimitador a menos que especifique lo contrario.

Akram Agbarya
fuente