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






4.71/5 (40投票s)
一个简单的实用程序,用于查询 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 文件编写自定义查询提供程序。 请继续关注。