Click here to Skip to main content
6,594,932 members and growing! (15,569 online)
Email Password   helpLost your password?
Platforms, Frameworks & Libraries » LINQ » General     Intermediate License: The Code Project Open License (CPOL)

LINQ to Text or CSV Files - Part 1

By Alomgir Miah A

A simple utility to LINQ against CSV files
C# 3.0.NET 3.0, .NET 3.5, LINQ, Architect, Dev
Posted:11 Oct 2008
Updated:1 Jan 2009
Views:18,349
Bookmarked:63 times
Announcements
Loading...
 
Search    
Advanced Search
Add to IE Search
printPrint   add Share
      Discuss Discuss   Broken Article?Report  
34 votes for this article.
Popularity: 6.82 Rating: 4.45 out of 5
1 vote, 2.9%
1

2
1 vote, 2.9%
3
8 votes, 23.5%
4
24 votes, 70.6%
5

Introduction

With the introduction of LINQ in C# 3.0, the life of a developer has become very easy when it comes to querying collections. In this article, I will show you a quick and easy way to query large CSV files.

Background

Imagine you have a very large CSV file. Loading the entire file into memory (e.g. DataSet) and querying it with LINQ would be a huge overhead. So I thought why not use StreamReader on the file instead.

Using the Code

I have implemented IEnumerable<string[]> and for every call to GetEnumerator, a line is read from CSV file. This class returns an enumerable string[].

public class TextFileReader : IEnumerable<string[]>
{
    private string _fileName = string.Empty;
    private string _delimiter = string.Empty;

    public TextFileReader(string fileName, string delimiter)
    {
        this._fileName = fileName;
        this._delimiter = delimiter;
    }

    #region IEnumerable<string[]> Members

    IEnumerator<string[]> IEnumerable<string[]>.GetEnumerator()
    {
        using (StreamReader streamReader = new StreamReader(this._fileName))
        {
            while (!streamReader.EndOfStream)
            {
                yield return streamReader.ReadLine().Split(new char[] { ',' });
            }
        }
    }

    #endregion

    #region IEnumerable Members

    IEnumerator IEnumerable.GetEnumerator()
    {
        return ((IEnumerable)((IEnumerator)this)).GetEnumerator();
    }

    #endregion
}

Using this class is very simple. Just initialize the class with the CSV file name and you are all set to LINQ it using standard operator. The following code shows how to query all the rows in the CSV file with the first column text starting with 'a'.

TextFileReader reader1 = new TextFileReader(@"Sample.txt", ",");

            var query1 = from it1 in reader1
                        where it1[0].StartsWith("a")
                        select new { Name = it1[0], Age = it1[1], 
			EmailAddress = it1[2] };

            foreach (var x1 in query1)
            {
                Console.WriteLine(String.Format("Name={0} Age={1} 
		EmailAddress = {2}", x1.Name, x1.Age, x1.EmailAddress));
            }
}

Let's make things a little bit more interesting. Suppose you know the format of the CSV file and have defined business classes corresponding to the CSV files. In this case, the above class is not very useful. Let's modify the 'TextFilereader' to have generic support.

public class TextFileReader<T> : IEnumerable<T> where T : new()
{
    private string _fileName = string.Empty;
    private string _delimiter = string.Empty;
    private Dictionary<String, PropertyInfo> _headerPropertyInfos = 
		new Dictionary<string, PropertyInfo>();
    private Dictionary<String, Type> _headerDaytaTypes = new Dictionary<string, Type>();

    public TextFileReader(string fileName, string delimiter)
    {
        this._fileName = fileName;
        this._delimiter = delimiter;
    }

    #region IEnumerable<string[]> Members

    IEnumerator<T> IEnumerable<T>.GetEnumerator()
    {
        using (StreamReader streamReader = new StreamReader(this._fileName))
        {
            string[] headers = streamReader.ReadLine().Split(new String[] 
		{ this._delimiter }, StringSplitOptions.None);
            this.ReadHeader(headers);

            while (!streamReader.EndOfStream)
            {
                T item = new T();

                string[] rowData = streamReader.ReadLine().Split(new String[] 
		{ this._delimiter }, StringSplitOptions.None);

                for (int index = 0; index < headers.Length; index++)
                {
                    string header = headers[index];
                    this._headerPropertyInfos[header].SetValue
			(item, Convert.ChangeType(rowData[index], 
			this._headerDaytaTypes[header]), null);
                }
                yield return item;
            }
        }
    }

    #endregion

    #region IEnumerable Members

    IEnumerator IEnumerable.GetEnumerator()
    {
        return ((IEnumerable)((IEnumerator<T>)this)).GetEnumerator();
    }

    #endregion

    private void ReadHeader(string[] headers)
    {
        foreach (String header in headers)
        {
            foreach (PropertyInfo propertyInfo in (typeof(T)).GetProperties())
            {
                foreach (object attribute in propertyInfo.GetCustomAttributes(true))
                {
                    if ( attribute is ColumnAttribute )
                    {
                        ColumnAttribute columnAttribute = attribute as ColumnAttribute;
                        if (columnAttribute.Name == header)
                        {
                            this._headerPropertyInfos[header] = propertyInfo;
                            this._headerDaytaTypes[header] = columnAttribute.DataType;
                            break;
                        }
                    }
                }
            }
        }
    }
}
}

Now let's define a business class 'Person'. This class will hold the information stored in the CSV file. Its properties are marked with 'ColumnAttribute' to map the columns with CSV columns. The code is self explanatory.

public class Person
{
    private string _name;

    [Column(Name = "Name", DataType = typeof(String))]
    public string Name
    {
        get { return _name; }
        set { _name = value; }
    }
    private int _age;

    [Column(Name = "Age", DataType = typeof(Int32))]
    public int Age
    {
        get { return _age; }
        set { _age = value; }
    }
    private string _emailAddress;

    [Column(Name = "EmailId", DataType = typeof(String))]
    public string EmailAddress
    {
        get { return _emailAddress; }
        set { _emailAddress = value; }
    }
}

[AttributeUsage(AttributeTargets.Property)]
public class ColumnAttribute : Attribute
{
    private string _name;

    public string Name
    {
        get { return _name; }
        set { _name = value; }
    }
    private Type _dataType;

    public Type DataType
    {
        get { return _dataType; }
        set { _dataType = value; }
    }
}

Using this class is again very simple. Just initialize the class with the CSV file name and the delimiter and LINQ it. The following code shows how to query all the rows in the CSV file with the first column Age greater than 2.

    TextFileReader<Person> reader2 = 
	new TextFileReader<Person>(@"SampleWithHeader.txt", ",");
    var query2 = from it2 in reader2
                 where it2.Age > 2
                 select it2;

    foreach (var x2 in query2)
    {
        Console.WriteLine(String.Format("Name={0} Age={1} 
		EmailAddress = {2}", x2.Name, x2.Age, x2.EmailAddress));
    }

Points of Interest

Well, this may not be the best way to implement this. I am also into the process of learning LINQ. Please feel free to give your comments or suggestions. In my next article, I will show you how to write a custom query provider for CSV files. Please stay tuned.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

About the Author

Alomgir Miah A


Member

Location: United States United States

Other popular LINQ articles:

Article Top
You must Sign In to use this message board.
FAQ FAQ 
 
Noise Tolerance  Layout  Per page   
 Msgs 1 to 14 of 14 (Total in Forum: 14) (Refresh)FirstPrevNext
GeneralGood to see you here! PinmemberDigvijay Chauhan5:33 11 Sep '09  
GeneralThanks for the codez PinmemberMycroft Holmes15:56 20 Dec '08  
GeneralRe: Thanks for the codez [modified] PinmemberAlomgir Miah Abdul21:56 20 Dec '08  
GeneralCSV and field separator PinmemberTobiasP2:52 14 Oct '08  
GeneralElegant! PinmemberPat Tormey2:06 14 Oct '08  
GeneralJust a comment... PinmemberSeth Morris23:17 13 Oct '08  
GeneralRe: Just a comment... PinmemberTobiasP4:07 14 Oct '08  
GeneralRe: Just a comment... PinmemberAlomgir Abdul Miah5:00 14 Oct '08  
GeneralRe: Just a comment... PinmemberMycroft Holmes15:51 20 Dec '08  
GeneralExcellent, but how about... PinmemberJohn O'Halloran18:23 13 Oct '08  
GeneralRe: Excellent, but how about... PinmemberAlomgir Abdul Miah18:55 13 Oct '08  
GeneralGreat article!!! PinmemberAchintya Jha15:16 12 Oct '08  
GeneralGreat article! PinmemberIamBond0073:25 12 Oct '08  
GeneralNice! PinmemberDale Visser2:32 12 Oct '08  

General General    News News    Question Question    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

PermaLink | Privacy | Terms of Use
Last Updated: 1 Jan 2009
Editor: Deeksha Shenoy
Copyright 2008 by Alomgir Miah A
Everything else Copyright © CodeProject, 1999-2009
Web11 | Advertise on the Code Project