.NET how to output csv from enumeration of anonymous type?
Using FileHelpers, I decorated a class with [DelimitedRecord(",")]
and was going to output an enumeration of objects of开发者_如何学运维 that type as CSV. But, it didn't work because my class inherits from ActiveRecordLinqBase<T>
, which caused some problems.
So, I was wondering if I could just select an enumeration of anonymous types and somehow have filehelpers generate csv from that. I don't like having to define a class just for FileHelpers to output csv.
I would be open to using another csv library, but FileHelpers is proven.
EDIT
@Foovanadil: This would be the sort of thing I am trying to do:
CreateCSV(MyCollection.Select(x=>new{
x.Prop1,
x.Prop2,
x.Prop3
}));
Gives you:
Prop1,Prop2,Prop3
val1a,val2a,val3a
val1b,val2b,val3b
etc.....
LINQ To CSV worked great for me.
Here's an example of how I am using it:
protected void Page_Load(object sender, EventArgs e)
{
var courseId = int.Parse(Request["id"]);
var course = Course.Queryable.Single(x => x.Id == courseId);
Response.ContentType = "text/csv";
Response.AddHeader("Content-Disposition", string.Format("attachment;filename=\"{0}.csv\";", course.Code));
var csvContext = new LINQtoCSV.CsvContext();
var writer = new System.IO.StreamWriter(Response.OutputStream);
csvContext.Write(course.Registrations.Select(x => new
{
x.StudentId,
x.Name,
x.EmailAddress,
x.MoodleUsername,
x.Age,
x.Is65OrOlder,
x.CertificationAndRank,
x.Citizenship,
x.DateOfBirth,
x.DepartmentName,
x.StationNumber,
x.EmploymentStatus,
x.HighestEducationLevel
}), writer);
writer.Dispose();
}
UPDATE
There are some downsides to the approach above:
- The column order in the csv file is unpredictable. It doesn't follow the order of the property definitions in the anonymous type.
- Column headers come from the property names which aren't always what is is desired.
So, I decided I would create a class just for the CSV records, which didn't end up being any more work than the anonymous type did. I used Auto Mapper to flatten my source class and populate the property values of the CSV class. I also decided to compare FileHelpers to Linq To CSV. Linq To CSV was the obvious winner, in my situation:
- L2CSV allowed you apply an attribute to each property in the class for defining the column order, column header title, and conversion formats.
- FHs would only let you supply a conversion format for each field. The column order depended on the order of the properties as they are defined in the class.
- FHs would not infer a column header from the property name nor let you supply one. You could supply a literal string as the header for the CSV file, which is no good: The delimiter is built into the literal string; the column titles' order is not synced with the order of the properties.
I hope these findings are useful. Here is my new code:
// CSV Class
public class CsvRegistration
{
[CsvColumn(FieldIndex = 0)]
public string Name { get; set; }
[CsvColumn(FieldIndex = 1, Name = "Student Id")]
public int StudentId { get; set; }
[CsvColumn(FieldIndex = 2, Name = "Email Address")]
public string EmailAddress { get; set; }
[CsvColumn(FieldIndex = 3, Name = "Moodle Username")]
public string MoodleUsername { get; set; }
[CsvColumn(FieldIndex = 4, Name = "Dept. Name")]
public string DepartmentName { get; set; }
[CsvColumn(FieldIndex = 5, Name = "Station #")]
public string StationNumber { get; set; }
[CsvColumn(FieldIndex = 6, Name = "Highest Education Level")]
public string HighestEducationLevel { get; set; }
[CsvColumn(FieldIndex = 7, Name = "Certification/Rank")]
public string CertificationAndRank { get; set; }
[CsvColumn(FieldIndex = 8, Name = "Employment Status")]
public string EmploymentStatus { get; set; }
[CsvColumn(FieldIndex = 9, Name = "Registration Date")]
public DateTime RegistrationDate { get; set; }
[CsvColumn(FieldIndex = 10, Name = "Date of Birth")]
public DateTime DateOfBirth { get; set; }
[CsvColumn(FieldIndex = 11)]
public int Age { get; set; }
[CsvColumn(FieldIndex = 12)]
public string Citizenship { get; set; }
[CsvColumn(FieldIndex = 13)]
public string Race { get; set; }
[CsvColumn(FieldIndex = 14)]
public string Ethnicity { get; set; }
[CsvColumn(FieldIndex = 15, Name = "Home Address")]
public string HomeAddressLine1 { get; set; }
[CsvColumn(FieldIndex = 16, Name = "City")]
public string HomeAddressCity { get; set; }
[CsvColumn(FieldIndex = 17, Name = "State")]
public string HomeAddressState { get; set; }
[CsvColumn(FieldIndex = 18, Name = "Zip")]
public string HomeAddressZip { get; set; }
[CsvColumn(FieldIndex = 19, Name = "County")]
public string HomeAddressCounty { get; set; }
[CsvColumn(FieldIndex = 20, Name = "Home Phone")]
public string HomePhone { get; set; }
[CsvColumn(FieldIndex = 21, Name = "Work Phone")]
public string WorkPhone { get; set; }
}
// ASPX page to serve csv file
protected void Page_Load(object sender, EventArgs e)
{
var courseId = int.Parse(Request["id"]);
var course = Course.Queryable.Single(x => x.Id == courseId);
Response.ContentType = "text/csv";
Response.AddHeader("Content-Disposition", string.Format("attachment;filename=\"{0}.csv\";", course.Code));
using (var writer = new System.IO.StreamWriter(Response.OutputStream))
{
var registrations = Mapper.Map<IEnumerable<Registration>, IEnumerable<CsvRegistration>>(course.Registrations);
var cc = new LINQtoCSV.CsvContext();
cc.Write(registrations, writer);
}
}
Your CreateCSV list would look something like this:
static StringBuilder CreateCSV<T>( IEnumerable<T> data )
{
StringBuilder builder = new StringBuilder();
var properties = typeof( T ).GetProperties();
foreach ( var prop in properties )
{
builder.Append( prop.Name ).Append( ", " );
}
builder.Remove( builder.Length - 2, 2 ).AppendLine();
foreach ( var row in data )
{
foreach ( var prop in properties )
{
builder.Append( prop.GetValue( row, null ) ).Append( ", " );
}
builder.Remove( builder.Length - 2, 2 ).AppendLine();
}
return builder;
}
精彩评论