2009-05-20 20 views
28

Attualmente dispone di un DataTable, ma desidera inviarlo in streaming all'utente tramite un WebHandler. FileHelpers ha CommonEngine.DataTableToCsv(dt, "file.csv"). Tuttavia lo salva in un file. Come posso invece salvarlo su uno stream? So come farlo quando so che le colonne sono avanzate o non cambiano, ma voglio generare le intestazioni delle colonne direttamente dalla tabella dei dati.Converti DataTable in stream CSV

Se so che le colonne ho appena creare la classe:

[DelimitedRecord(",")] 
public class MailMergeFields 
{ 
    [FieldQuoted()] 
    public string FirstName; 
    [FieldQuoted()] 
    public string LastName; 
} 

quindi utilizzare FileHelperEngine e aggiungere i record:

FileHelperEngine engine = new FileHelperEngine(typeof(MailMergeFields)); 

MailMergeFields[] merge = new MailMergeFields[dt.Rows.Count + 1]; 

// add headers 
merge[0] = new MailMergeFields(); 
merge[0].FirstName = "FirstName"; 
merge[0].LastName = "LastName"; 

int i = 1;    
// add records 
foreach (DataRow dr in dt.Rows) 
{ 
    merge[i] = new MailMergeFields(); 
    merge[i].FirstName = dr["Forename"]; 
    merge[i].LastName = dr["Surname"]; 
    i++; 
} 

Infine scrivere in un flusso:

TextWriter writer = new StringWriter(); 
engine.WriteStream(writer, merge); 
context.Response.Write(writer.ToString()); 

Purtroppo, dato che non conosco le colonne in mano, non posso creare la classe in anticipo.

+0

Check questo https://gist.github.com/riyadparvez/4467668 – user

+0

La biblioteca Helper file è open source. Perché non ci credi e aggiungi il tuo metodo? – Keltex

+0

@user: questo elenco contiene un bug in cui le voci con virgole non verranno gestite correttamente. Vedi http://stackoverflow.com/q/769621/1461424 – Krumia

risposta

65

Si può solo scrivere qualcosa velocemente da soli:

public static class Extensions 
{ 
    public static string ToCSV(this DataTable table) 
    { 
     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" : ","); 
      } 
     } 

     return result.ToString(); 
    } 
} 

E per prova:

public static void Main() 
    { 
     DataTable table = new DataTable(); 
     table.Columns.Add("Name"); 
     table.Columns.Add("Age"); 
     table.Rows.Add("John Doe", "45"); 
     table.Rows.Add("Jane Doe", "35"); 
     table.Rows.Add("Jack Doe", "27"); 
     var bytes = Encoding.GetEncoding("iso-8859-1").GetBytes(table.ToCSV()); 
     MemoryStream stream = new MemoryStream(bytes); 

     StreamReader reader = new StreamReader(stream); 
     Console.WriteLine(reader.ReadToEnd()); 
    } 

MODIFICA: Per i vostri commenti:

Dipende da come vuoi che il tuo csv sia formattato, ma in generale se il testo contiene caratteri speciali, vuoi racchiuderlo tra virgolette doppie cioè: "mio, testo". È possibile aggiungere il controllo nel codice che crea il CSV per verificare la presenza di caratteri speciali e, se lo è, racchiude il testo tra virgolette doppie. Per quanto riguarda .NET 2.0, basta crearlo come metodo helper nella classe o rimuovere la parola this nella dichiarazione del metodo e chiamarlo in questo modo: Extensions.ToCsv (table);

+18

E quando i dati includono una citazione, una nuova riga o una virgola? – SamWM

+0

Anche usando .NET 2.0 – SamWM

+0

Questo funziona per me. Ottimizzato un po 'per i miei bisogni. Excel visualizza le nuove righe come? sebbene (abbia lo stesso problema quando esporta da SQL Server) – SamWM

2

Se è possibile trasformare il vostro DataTable in un IEnumerable questo dovrebbe funzionare per voi ...

Response.Clear(); 
    Response.Buffer = true; 

    Response.AddHeader("content-disposition", "attachment;filename=FileName.csv"); 
    Response.Charset = ""; 
    Response.ContentType = "application/text"; 
    Response.Output.Write(ExampleClass.ConvertToCSV(GetListOfObject(), typeof(object))); 
    Response.Flush(); 
    Response.End(); 



public static string ConvertToCSV(IEnumerable col, Type type) 
     { 
      StringBuilder sb = new StringBuilder(); 
      StringBuilder header = new StringBuilder(); 

      // Gets all properies of the class 
      PropertyInfo[] pi = type.GetProperties(); 

      // Create CSV header using the classes properties 
      foreach (PropertyInfo p in pi) 
      { 
       header.Append(p.Name + ","); 
      } 

      sb.AppendLine(header.ToString().Remove(header.Length)); 

      foreach (object t in col) 
      { 
       StringBuilder body = new StringBuilder(); 

       // Create new item 
       foreach (PropertyInfo p in pi) 
       { 
        object o = p.GetValue(t, null); 
        body.Append(o.ToString() + ","); 
       } 

       sb.AppendLine(body.ToString().Remove(body.Length)); 
      } 
      return sb.ToString(); 
     } 
+1

Grazie, fyi - header.Length e body.Length dovrebbe essere -1 – downatone

1

Puoi provare a utilizzare qualcosa di simile. In questo caso ho utilizzato una stored procedure per ottenere più tabelle di dati ed esportarle tutte utilizzando CSV.

using System; 
using System.Text; 
using System.Data; 
using System.Data.SqlClient; 
using System.IO; 

namespace bo 
{ 
class Program 
{ 
    static private void CreateCSVFile(DataTable dt, string strFilePath) 
    { 
     #region Export Grid to CSV 
     // Create the CSV file to which grid data will be exported. 
     StreamWriter sw = new StreamWriter(strFilePath, false); 
     int iColCount = dt.Columns.Count; 

     // First we will write the headers. 

     //DataTable dt = m_dsProducts.Tables[0]; 
     for (int i = 0; i < iColCount; i++) 
     { 
      sw.Write(dt.Columns[i]); 
      if (i < iColCount - 1) 
      { 
       sw.Write(";"); 
      } 
     } 
     sw.Write(sw.NewLine); 

     // Now write all the rows. 
     foreach (DataRow dr in dt.Rows) 
     { 
      for (int i = 0; i < iColCount; i++) 
      { 
       if (!Convert.IsDBNull(dr[i])) 
       { 
        sw.Write(dr[i].ToString()); 
       } 
       if (i < iColCount -1) 
       { 
        sw.Write(";"); 
       } 
      } 
      sw.Write(sw.NewLine); 
     } 
     sw.Close(); 

     #endregion 
    } 
    static void Main(string[] args) 
    { 
     string strConn = "connection string to sql"; 
     string direktorij = @"d:"; 
     SqlConnection conn = new SqlConnection(strConn); 
     SqlCommand command = new SqlCommand("sp_ado_pos_data", conn); 
     command.CommandType = CommandType.StoredProcedure; 
     command.Parameters.Add('@skl_id', SqlDbType.Int).Value = 158; 
     SqlDataAdapter adapter = new SqlDataAdapter(command); 
     DataSet ds = new DataSet(); 
     adapter.Fill(ds); 
     for (int i = 0; i < ds.Tables.Count; i++) 
     { 
      string datoteka = (string.Format(@"{0}tablea{1}.csv", direktorij, i)); 
      DataTable tabela = ds.Tables[i]; 
      CreateCSVFile(tabela,datoteka); 
      Console.WriteLine("Generišem tabelu {0}", datoteka); 
     } 
     Console.ReadKey(); 
    } 
    } 
} 
0

La risposta di BFree ha funzionato per me. Avevo bisogno di inviare il flusso direttamente al browser. Che immagino sia un'alternativa comune. Ho aggiunto il seguente alla principale di BFree) Codice (per fare questo:

//StreamReader reader = new StreamReader(stream); 
//Console.WriteLine(reader.ReadToEnd()); 

string fileName = "fileName.csv"; 
HttpContext.Current.Response.ContentType = "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"; 
HttpContext.Current.Response.AddHeader("content-disposition", string.Format("attachment;filename={0}", fileName)); 
stream.Position = 0; 
stream.WriteTo(HttpContext.Current.Response.OutputStream); 
1

Ho usato il seguente codice, saccheggiato dal blog di qualcuno (pls perdonare la mancanza della citazione). Si occupa di quotazioni, newline e virgola in un modo ragionevolmente elegante citando ogni valore di campo.

/// <summary> 
    /// Converts the passed in data table to a CSV-style string.  
    /// </summary> 
    /// <param name="table">Table to convert</param> 
    /// <returns>Resulting CSV-style string</returns> 
    public static string ToCSV(this DataTable table) 
    { 
     return ToCSV(table, ",", true); 
    } 

    /// <summary> 
    /// Converts the passed in data table to a CSV-style string. 
    /// </summary> 
    /// <param name="table">Table to convert</param> 
    /// <param name="includeHeader">true - include headers<br/> 
    /// false - do not include header column</param> 
    /// <returns>Resulting CSV-style string</returns> 
    public static string ToCSV(this DataTable table, bool includeHeader) 
    { 
     return ToCSV(table, ",", includeHeader); 
    } 

    /// <summary> 
    /// Converts the passed in data table to a CSV-style string. 
    /// </summary> 
    /// <param name="table">Table to convert</param> 
    /// <param name="includeHeader">true - include headers<br/> 
    /// false - do not include header column</param> 
    /// <returns>Resulting CSV-style string</returns> 
    public static string ToCSV(this DataTable table, string delimiter, bool includeHeader) 
    { 
     var result = new StringBuilder(); 

     if (includeHeader) 
     { 
      foreach (DataColumn column in table.Columns) 
      { 
       result.Append(column.ColumnName); 
       result.Append(delimiter); 
      } 

      result.Remove(--result.Length, 0); 
      result.Append(Environment.NewLine); 
     } 

     foreach (DataRow row in table.Rows) 
     { 
      foreach (object item in row.ItemArray) 
      { 
       if (item is DBNull) 
        result.Append(delimiter); 
       else 
       { 
        string itemAsString = item.ToString(); 
        // Double up all embedded double quotes 
        itemAsString = itemAsString.Replace("\"", "\"\""); 

        // To keep things simple, always delimit with double-quotes 
        // so we don't have to determine in which cases they're necessary 
        // and which cases they're not. 
        itemAsString = "\"" + itemAsString + "\""; 

        result.Append(itemAsString + delimiter); 
       } 
      } 

      result.Remove(--result.Length, 0); 
      result.Append(Environment.NewLine); 
     } 

     return result.ToString(); 
    } 
12

Update 1

ho modificato in modo da utilizzare al posto StreamWriter, aggiungere un opzione per controllare se avete bisogno di intestazioni di colonna nell'output.

public static bool DataTableToCSV(DataTable dtSource, StreamWriter writer, bool includeHeader) 
{ 
    if (dtSource == null || writer == null) return false; 

    if (includeHeader) 
    { 
     string[] columnNames = dtSource.Columns.Cast<DataColumn>().Select(column => "\"" + column.ColumnName.Replace("\"", "\"\"") + "\"").ToArray<string>(); 
     writer.WriteLine(String.Join(",", columnNames)); 
     writer.Flush(); 
    } 

    foreach (DataRow row in dtSource.Rows) 
    { 
     string[] fields = row.ItemArray.Select(field => "\"" + field.ToString().Replace("\"", "\"\"") + "\"").ToArray<string>(); 
     writer.WriteLine(String.Join(",", fields)); 
     writer.Flush(); 
    } 

    return true; 
} 

Come si può vedere, è possibile scegliere l'uscita dal StreamWriter iniziale, se si utilizza StreamWriter (Stream BaseStream), è possibile scrivere CSV in MemeryStream, FileStream, ecc

Origine

ho un DataTable facile funzione di csv, mi serve bene:

public static void DataTableToCsv(DataTable dt, string csvFile) 
    { 
     StringBuilder sb = new StringBuilder(); 

     var columnNames = dt.Columns.Cast<DataColumn>().Select(column => "\"" + column.ColumnName.Replace("\"", "\"\"") + "\"").ToArray(); 
     sb.AppendLine(string.Join(",", columnNames)); 

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

     File.WriteAllText(csvFile, sb.ToString(), Encoding.Default); 
    } 
1
public void CreateCSVFile(DataTable dt, string strFilePath,string separator) 
     {    
      #region Export Grid to CSV 
      // Create the CSV file to which grid data will be exported. 

      StreamWriter sw = new StreamWriter(strFilePath, false); 
      int iColCount = dt.Columns.Count; 
      for (int i = 0; i < iColCount; i++) 
      {  
       sw.Write(dt.Columns[i]);  
       if (i < iColCount - 1) 
       { 
        sw.Write(separator); 
       } 
      }  

      sw.Write(sw.NewLine); 

      // Now write all the rows. 
      foreach (DataRow dr in dt.Rows) 
      { 
       for (int i = 0; i < iColCount; i++) 
       { 
        if (!Convert.IsDBNull(dr[i])) 
        { 
         sw.Write(dr[i].ToString()); 
        } 

        if (i < iColCount - 1) 
        { 
         sw.Write(separator); 
        } 
       } 
       sw.Write(sw.NewLine); 
      } 

      sw.Close(); 
      #endregion 
     } 
2

Non so se questo convertito da VB a C# ok, ma se non si vuole virgolette intorno i numeri, si potrebbe confrontare il tipo di dati come segue ..

public string DataTableToCSV(DataTable dt) 
{ 
    StringBuilder sb = new StringBuilder(); 
    if (dt == null) 
     return ""; 

    try { 
     // Create the header row 
     for (int i = 0; i <= dt.Columns.Count - 1; i++) { 
      // Append column name in quotes 
      sb.Append("\"" + dt.Columns[i].ColumnName + "\""); 
      // Add carriage return and linefeed if last column, else add comma 
      sb.Append(i == dt.Columns.Count - 1 ? "\n" : ","); 
     } 


     foreach (DataRow row in dt.Rows) { 
      for (int i = 0; i <= dt.Columns.Count - 1; i++) { 
       // Append value in quotes 
       //sb.Append("""" & row.Item(i) & """") 

       // OR only quote items that that are equivilant to strings 
       sb.Append(object.ReferenceEquals(dt.Columns[i].DataType, typeof(string)) || object.ReferenceEquals(dt.Columns[i].DataType, typeof(char)) ? "\"" + row[i] + "\"" : row[i]); 

       // Append CR+LF if last field, else add Comma 
       sb.Append(i == dt.Columns.Count - 1 ? "\n" : ","); 
      } 
     } 
     return sb.ToString; 
    } catch (Exception ex) { 
     // Handle the exception however you want 
     return ""; 
    } 

} 
2

Se si desidera per lo streaming il CSV verso l'utente senza creare un file quindi ho trovato il seguente il metodo più semplice. È possibile utilizzare qualsiasi estensione/metodo per creare la funzione ToCsv() (che restituisce una stringa in base al DataTable specificato).

 var report = myDataTable.ToCsv(); 
     var bytes = Encoding.GetEncoding("iso-8859-1").GetBytes(report); 

     Response.Buffer = true; 
     Response.Clear(); 
     Response.AddHeader("content-disposition", "attachment; filename=report.csv"); 
     Response.ContentType = "text/csv"; 
     Response.BinaryWrite(bytes); 
     Response.End(); 
Problemi correlati