开发者

IEnumerable<T> to a CSV file

I am getting the result from LINQ query as var of type IEnumerable<T>

I want a CSV file to be created from the result from the LINQ

I am getting the result from the following query

var r = from table in myDataTable.AsEnumerable()
        orderby table.Field<string>(para1)
        group table by new { Name = table[para1], Y = table[para2] }
        into ResultTable
        select 开发者_StackOverflow中文版new
        {
            Name = ResultTable.Key,
            Count = ResultTable.Count()
        };


Check this

 public static class LinqToCSV
    {
        public static string ToCsv<T>(this IEnumerable<T> items)
            where T : class
        {
            var csvBuilder = new StringBuilder();
            var properties = typeof(T).GetProperties();
            foreach (T item in items)
            {
                string line = string.Join(",",properties.Select(p => p.GetValue(item, null).ToCsvValue()).ToArray());
                csvBuilder.AppendLine(line);
            }
            return csvBuilder.ToString();
        }

        private static string ToCsvValue<T>(this T item)
        {
            if(item == null) return "\"\"";

            if (item is string)
            {
                return string.Format("\"{0}\"", item.ToString().Replace("\"", "\\\""));
            }
            double dummy;
            if (double.TryParse(item.ToString(), out dummy))
            {
                return string.Format("{0}", item);
            }
            return string.Format("\"{0}\"", item);
        }
    }

Full code at : Scott Hanselman's ComputerZen Blog - From Linq To CSV


IEnumerable<string> lines = r.Select(x => String.Format("{0},{1}", r.Name, r.Count));
System.IO.File.WriteAllLines(path, lines);

will produce:

name1,count1
name2,count2
...


This is screaming out for Linq2CSV:

http://www.codeproject.com/KB/linq/LINQtoCSV.aspx

which is also available on nuget:

http://nuget.org/List/Packages/LinqToCsv

Excellent library, really recommend.


its not clear what you realy want, but this can be a solution public void Read() {

    var r = from table in myDataTable.AsEnumerable()
            orderby table.Field<string>(para1)
            group table by new { Name = table[para1], Y = table[para2] }
                into ResultTable
                select new NameCount()
                {
                    Name = ResultTable.Key,
                    Count = ResultTable.Count()
                }.ToString();

    //Write all r to a File
}
public class NameCount
{
    public string Name { get; set; }
    public int Count { get; set; }
    public string ToString()
    {
        return string.Format("{0},{1}\r\n", Name, Count);
    }
}


This is a basic solution I'm using for any IEnumerable:

using System.Reflection;
using System.Text;
//***        
    public void ToCSV<T>(IEnumerable<T> items, string filePath)
    {
        var dataTable = new DataTable(typeof(T).Name);
        PropertyInfo[] props = typeof(T).GetProperties(BindingFlags.Public | BindingFlags.Instance);
        foreach (var prop in props)
            dataTable.Columns.Add(prop.Name, prop.PropertyType);

        foreach (var item in items)
        {
            var values = new object[props.Length];
            for (var i = 0; i < props.Length; i++)
            {
                values[i] = props[i].GetValue(item, null);
            }
            dataTable.Rows.Add(values);
        }

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

        //good place to validate File.Exist or catch exceptions
        System.IO.File.WriteAllText(filePath, fileContent.ToString());            
    }


I assume from your question you want a Generic method that will do this, regardless of what T is?

Something like

public void ToCSV<T>(IEnumerable<T>, TextWriter writer) . . .

The problem you can't overcome, is that T is a complex type, to comma separate each element of T you need to know about the internals of T, or T needs to know how to write it's self as a CSV row, which means you'll need an ICSVRow interface and that you'll need to constrain T to objects that implement ICSVRow. Which also means this won't work for anonymous types.

Any questions just shout.


The other solutions do not properly escape the values. @Pranay Rana's in particular includes improper escaping, which is arguably even worse.

This may be somewhat overkill but the following method will quote values when needed, optionally lets you choose a delimiter besides ",", optionally allows including headers, allows you to specify what properties are included/excluded in the CSV, and even allows you to map those properties to different text in the headers if so desired.

Furthermore, it attempts to inspect the types of the enumerable passed- so enumerables of primitive types will result in a single-row CSV, while primitives of complex types will have their properties mapped. It looks at the actual objects to do this rather than the type parameters so that, for example, an array of object[] that holds complex types will still get properties mapped.

/// <summary>
/// Converts the given enumerable into a CSV string. Optionally, specify the delimiter or include headers.
/// For enumerables of primitive types, it will convert them to a single-line CSV. Headers are not valid for this case.
/// For enumerables of complex types, it will inspect the properties and convert each item into a line of the CSV.
/// Which properties are included/excluded and the header names in the resulting CSV can be controlled.
/// Note: Headers and values will only be double-quoted if necessary as per RFC4180.
/// </summary>
/// <typeparam name="T">The type of the enumerable.</typeparam>
/// <param name="enumerable">The enumerable to turn into a CSV.</param>
/// <param name="delimiter">The delimiter.</param>
/// <param name="includeHeaders">Whether to include headers.</param>
/// <param name="propertiesToInclude">Properties from the objects given to include. If left null, all properties will be included. This does not apply for enumerables of primitive types.</param>
/// <param name="propertiesToExclude">Properties to exclude from the DataTable, if any. This does not apply for enumerables of primitive types.</param>
/// <param name="propertyNameHeaderMap">A map that will be used to translate the property names to the headers that should appear in the CSV. This does not apply for enumerables of primitive types.</param>
/// <returns>A CSV representation of the objects in the enumeration.</returns>
public static string ToCsvString<T>(
    this IEnumerable<T> enumerable, 
    char delimiter = ',', 
    bool includeHeaders = false, 
    IEnumerable<string> propertiesToInclude = null, 
    IEnumerable<string> propertiesToExclude = null, 
    Dictionary<string, string> propertyNameHeaderMap = null)
{
    if (enumerable == null) throw new ArgumentNullException(nameof(enumerable));

    var type = enumerable.FirstOrDefault()?.GetType();
    if (type == null) return "";
    
    if (type.IsSimpleType())
        return string.Join(delimiter, enumerable.Select(i => escapeCsvValue(i?.ToString(), delimiter)));

    var csvBuilder = new StringBuilder();
    var allProperties = type.GetProperties();
    var propsToIncludeSet = (propertiesToInclude ?? allProperties.Select(p => p.Name))
        .Except(propertiesToExclude ?? Enumerable.Empty<string>())
        .ToHashSet();
    var properties = allProperties
        .Where(p => propsToIncludeSet.Contains(p.Name))
        .ToList();

    if (includeHeaders)
    {
        var headerNames = properties
            .Select(p => escapeCsvValue(propertyNameHeaderMap == null ? p.Name : propertyNameHeaderMap.GetValueOrDefault(p.Name) ?? $"{nameof(propertyNameHeaderMap)} was missing a value for property {p.Name}", delimiter));

        csvBuilder.AppendLine(string.Join(delimiter, headerNames));
    }

    foreach (var item in enumerable)
    {
        var vals = properties.Select(p => escapeCsvValue(p.GetValue(item, null)?.ToString(), delimiter));
        var line = string.Join(delimiter, vals);
        csvBuilder.AppendLine(line);
    }
    return csvBuilder.ToString();

    //Function to escape a value for use in CSV. Per RFC4180, if the delimiter, newline, or double quote is present in the value it must be double quoted. If the value contains double quotes they must be escaped.
    static string escapeCsvValue(string s, char delimiter)
    {
        return s == null ? null 
            : s.Any(c => c == delimiter || c == '"' || c == '\r' || c == '\n') ? $"\"{s.Replace("\"", "\"\"")}\"" 
            : s;
    }
}

/// <summary>
/// Whether the given type is a "simple" type. Eg, a built in CLR type to represent data.
/// This includes all integral types, floating points types, DateTime, DateOnly, decimal, and Guid.
/// </summary>
/// <param name="type">The type to check.</param>
/// <param name="unwrapNullable">Whether the type inside a nullable type should be checked.</param>
/// <returns>Whether the type was a simple type.</returns>
/// <exception cref="ArgumentNullException">If type was empty.</exception>
public static bool IsSimpleType(this Type type, bool unwrapNullable = true)
{
    if (type == null) throw new ArgumentNullException(nameof(type));

    if (unwrapNullable) type = Nullable.GetUnderlyingType(type) ?? type;
    return type.IsPrimitive
           || type == typeof(string)
           || type == typeof(DateTime)
           || type == typeof(DateOnly)
           || type == typeof(decimal)
           || type == typeof(Guid)
        ;
}
0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜