65.9K
CodeProject 正在变化。 阅读更多。
Home

LINQ to Text 或 CSV 文件 - 第 1 部分

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.71/5 (40投票s)

2008年10月11日

CPOL

2分钟阅读

viewsIcon

115614

downloadIcon

1115

一个简单的实用程序,用于查询 CSV 文件。

引言

随着 C# 3.0 中 LINQ 的引入,开发人员在查询集合方面变得非常容易。 在本文中,我将向您展示一种快速简便的方法来查询大型 CSV 文件。

背景

想象一下,您有一个非常大的 CSV 文件。 将整个文件加载到内存中(例如 DataSet)并使用 LINQ 查询它会带来巨大的开销。 所以我想为什么不直接使用 StreamReader 读取文件呢。

Using the Code

我已经实现了 IEnumerable<string[]>,并且每次调用 GetEnumerator 时,都会从 CSV 文件中读取一行。 此类返回一个可枚举的 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
}

使用这个类非常简单。 只需使用 CSV 文件名初始化该类,您就可以使用标准操作符对其进行 LINQ 查询了。 以下代码演示了如何查询 CSV 文件中的所有行,其中第一列文本以 '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));
            }
}

让我们让事情变得更有趣一些。 假设您知道 CSV 文件的格式,并且已经定义了与 CSV 文件对应的业务类。 在这种情况下,上面的类用处不大。 让我们修改 'TextFilereader' 以提供泛型支持。

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

现在让我们定义一个业务类 'Person'。 此类将保存存储在 CSV 文件中的信息。 它的属性用 'ColumnAttribute' 标记,以将列映射到 CSV 列。 代码不言自明。

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

再次使用这个类非常简单。 只需使用 CSV 文件名和分隔符初始化该类,然后对其进行 LINQ 查询。 以下代码演示了如何查询 CSV 文件中的所有行,其中第一列 Age 大于 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));
    }

关注点

好吧,这可能不是实现此功能的最佳方法。 我也在学习 LINQ 的过程中。 请随时提出您的意见或建议。 在我的下一篇文章中,我将向您展示如何为 CSV 文件编写自定义查询提供程序。 请继续关注。

© . All rights reserved.