c # tabela de dados para csv

113

Alguém poderia me dizer por que o código a seguir não está funcionando. Os dados são salvos no arquivo csv, porém os dados não são separados. Tudo existe na primeira célula de cada linha.

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

Obrigado.

Darren Young
fonte
Você pode verificar este gist.github.com/riyadparvez/4467668
usuário
Desenvolvi Extention de alto desempenho. verifique esta resposta
Nigje

Respostas:

229

A seguinte versão mais curta abre bem no Excel, talvez o seu problema seja a vírgula 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

E, como Tim apontou, se você estiver em .net> = 4, você pode torná-lo ainda mais curto:

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

Conforme sugerido por Christian, se você quiser lidar com caracteres especiais escapando em campos, substitua o bloco de loop por:

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

E a última sugestão, você pode escrever o conteúdo csv linha por linha em vez de como um documento inteiro, para evitar ter um grande documento na memória.

vc 74
fonte
2
Não há necessidade de copiar o ItemArraypara um novo String[], você pode omitir .ToArray()com o .NET 4 e usar a String.Joinsobrecarga que leva um IEnumerable<T>(editado).
Tim Schmelter
2
@TimSchmelter, sim, mas essas sobrecargas foram introduzidas em .net4, o código não será compilado se o OP usar .net <4
vc 74
18
Este método não leva em consideração uma vírgula dentro do valor da coluna.
Christian
2
Em vez disso, IEnumerable <string> fields = row.ItemArray.Select (field => field.ToString (). Replace ("\" "," \ "\" ")); sb.AppendLine (" \ "" + string.Join ("\", \ "", campos) + "\" ");
Cristão
2
@ Si8 O que você quer dizer? Essa resposta usa apenas componentes db e &nbspé típica de documentos HTML / XML. Não é o código acima que o produz, a menos que a tabela contenha &nbsp;explicitamente
vc 74
36

Eu envolvi isso em uma classe de extensão, que permite que você chame:

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

em qualquer 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
fonte
24

Uma nova função de extensão baseada na resposta de Paul Grimshaw. Eu limpei e adicionei a capacidade de lidar com dados inesperados. (Dados vazios, citações incorporadas e vírgulas nos títulos ...)

Ele também retorna uma string que é mais flexível. Ele retorna Nulo se o objeto de tabela não contém nenhuma estrutura.

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

Você o chama da seguinte maneira:

var csvData = dataTableOject.ToCsv();
AnthonyVO
fonte
1
Este é o melhor do resto aqui. Bem feito. Obrigado
Fandango68
Solução fantástica. Adicionados comentários localmente, mas foi possível usar fora da caixa sem ter que escalar a montanha. Obrigado.
j.hull
Amei isso! Usei-o como um método não estático e apenas passei minha DataTable como parâmetro. Funcionou muito bem, obrigado.
Kid Koder
9

Se o seu código de chamada estiver fazendo referência ao System.Windows.Formsassembly, você pode considerar uma abordagem radicalmente diferente. Minha estratégia é usar as funções já fornecidas pelo framework para fazer isso em poucas linhas de código e sem ter que percorrer colunas e linhas. O que o código abaixo faz é criar um programaticamente DataGridViewem tempo real e definir o DataGridView.DataSourcecomo o DataTable. Em seguida, seleciono programaticamente todas as células (incluindo o cabeçalho) na DataGridViewchamada e DataGridView.GetClipboardContent(), colocando os resultados no Windows Clipboard. Então, eu 'colo' o conteúdo da área de transferência em uma chamada para File.WriteAllText(), certificando-me de especificar a formatação de 'colar' comoTextDataFormat.CommaSeparatedValue .

Aqui está o 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);
        }
    }
}

Observe que também me certifico de preservar o conteúdo da área de transferência antes de começar e restaurá-lo assim que terminar, para que o usuário não receba um monte de lixo inesperado na próxima vez que tentar colar. As principais ressalvas a esta abordagem são 1) Sua classe deve fazer referênciaSystem.Windows.Forms , o que pode não ser o caso em uma camada de abstração de dados, 2) Sua montagem terá que ser direcionada para o framework .NET 4.5, já que DataGridView não existe no 4.0, e 3) O método falhará se a área de transferência estiver sendo usada por outro processo.

De qualquer forma, essa abordagem pode não ser a certa para sua situação, mas é interessante mesmo assim, e pode ser outra ferramenta em sua caixa de ferramentas.

Adam White
fonte
1
usar a área de transferência não é necessário stackoverflow.com/questions/40726017/… . .GetClipboardContenttambém lida com alguns casos extremos de valores que contêm ,. ", \t(converte tab em espaço)
Slai
2
Isso é bom, mas e se alguém estiver usando a máquina ao mesmo tempo e colocar algo na área de transferência no momento crítico.
Ayo Adesina
7

Eu fiz isso recentemente, mas incluí aspas em torno dos meus valores.

Por exemplo, altere estas duas linhas:

sb.Append("\"" + col.ColumnName + "\","); 
...
sb.Append("\"" + row[i].ToString() + "\","); 
Ben Jakuben
fonte
Obrigado pela sugestão, mas todos os dados ainda estão dentro da primeira célula de cada linha?
Darren Young
7

Tente mudar sb.Append(Environment.NewLine);para 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
fonte
Isso dará dois retornos de carro.
Darren Young
@alexl: Isso é o que eu estava fazendo originalmente, mas estava fora do topo da minha cabeça até que o VS disparou: o)
Neil Knight de
5

Tente colocar em ;vez de,

Espero que ajude

alexl
fonte
5

Leia isso e isso ?


Uma implementação melhor seria

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
fonte
5

O erro é o separador de lista.

Em vez de escrever, sb.Append(something... + ',')você deve colocar algo comosb.Append(something... + System.Globalization.CultureInfo.CurrentCulture.TextInfo.ListSeparator);

Você deve colocar o caractere separador de lista configurado em seu sistema operacional (como no exemplo acima), ou o separador de lista na máquina cliente onde o arquivo será visualizado. Outra opção seria configurá-lo no app.config ou web.config como um parâmetro do seu aplicativo.

Martín Delafuente
fonte
5

4 linhas 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();
}

Observe que o ToList()no final é importante; Preciso de algo para forçar uma avaliação de expressão. Se eu estivesse jogando golfe com código, poderia usar em seu Min()lugar.

Observe também que o resultado terá uma nova linha no final devido à última chamada para AppendLine(). Você pode não querer isso. Você pode simplesmente ligar TrimEnd()para removê-lo.

user2023861
fonte
3

Aqui está um aprimoramento da postagem do vc-74 que lida com vírgulas da mesma forma que o Excel. O Excel coloca aspas em torno dos dados se os dados tiverem vírgula, mas não aspas se os dados não tiverem vírgula.

    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
fonte
2

Para gravar em um arquivo, acho que o método a seguir é o mais eficiente e direto: (você pode adicionar aspas, se quiser)

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));
        }
    }
}
Student222
fonte
2

Para imitar o CSV do 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
fonte
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
fonte
Bem-vindo ao StackOverflow! As respostas são melhores quando incluem uma descrição do snippet de código. Eu pessoalmente descobri que quando os nomes das variáveis ​​se alinham entre a pergunta e a resposta, eles são mais úteis para mim.
AWinkle de
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
fonte
1

Possivelmente, a maneira mais fácil será usar:

https://github.com/ukushu/DataExporter

especialmente no caso de seus dados de tabela de dados contendo /r/ncaracteres ou símbolo separador dentro de suas células de tabela de dados . Quase todas as outras respostas não funcionarão com essas células.

você só precisa escrever o seguinte 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();
Andrew
fonte
0

No caso de alguém tropeçar nisso, eu estava usando File.ReadAllText para obter dados CSV e, em seguida, modifiquei e escrevi de volta com File.WriteAllText . Os \ r \ n CRLFs funcionavam bem, mas as guias \ t foram ignoradas quando o Excel o abriu. (Todas as soluções neste tópico até agora usam um delimitador de vírgula, mas isso não importa.) O bloco de notas mostrou o mesmo formato no arquivo resultante e na origem. A Diff até mostrou os arquivos como idênticos. Mas tive uma ideia quando abri o arquivo no Visual Studio com um editor binário. O arquivo de origem era Unicode, mas o destino era ASCII . Para corrigir, modifiquei ReadAllText e WriteAllText com o terceiro argumento definido como System.Text.Encoding.Unicode e, a partir daí, o Excel foi capaz de abrir o arquivo atualizado.

TonyG
fonte
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
fonte
0

Aqui está minha solução, com base nas respostas anteriores de Paul Grimshaw e Anthony VO . Enviei o código em um projeto C # no Github .

Minha principal contribuição é eliminar explicitamente a criação e a manipulação de um StringBuildere, em vez disso, trabalhar apenas com IEnumerable. Isso evita a alocação de um grande buffer na memória.

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

}

Essa abordagem combina muito bem com a conversão IEnumerablepara DataTable, conforme solicitado aqui .

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

Eu também precisei fazer alguma substituição de dados, por isso existem algumas instruções "if else". Eu precisava ter certeza de que se um campo estivesse vazio para inserir "N / A", ou se um campo de Data fosse formatado como "01/01/1900: 00", ele seria salvo como "01/01/1900" em vez de.

BondAddict
fonte
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
fonte
-1

se todos os dados ainda estiverem na primeira célula, significa que o aplicativo com o qual você abriu o arquivo está esperando outro delimitador. MSExcel pode tratar a vírgula como delimitador, a menos que você especifique o contrário.

Akram Agbarya
fonte