LINQ to CSV






4.82/5 (6投票s)
LINQ to CSV 文件。
介绍
你好... 这是我们几年前开始的关于 LINQ 的疯狂想法的延续,自从我创建了 LINQ to XML。今天我带来了 LINQ to CSV,作为 LINQ to Everything 梦想的延续。
背景
CSV 是逗号分隔值的缩写,它是一种基于文本的文件,包含由逗号分隔的一系列值,它有一个 .csv 或 .txt 扩展名。
CSV 文件格式通常用于在不同应用程序之间交换数据。该文件格式,正如在 Microsoft Excel 中使用的那样,已成为整个行业的一种伪标准,即使在非 Microsoft 平台中也是如此。
CSV 格式
- 每条记录占一行,但字段可能包含嵌入的换行符,因此一条记录可能跨越多行。
- CSV 文件中的第一条记录可能是包含列(字段)名称的标题记录。
- 字段用逗号分隔。
1997,Ford,E350
- 紧邻逗号字段分隔符的开头和结尾的空格字符将被忽略。
"luxurious truck"
- 包含嵌入逗号的字段必须用双引号字符分隔。
1997,Ford,E350,"Super, luxurious truck"
- 包含双引号字符的字段必须用双引号括起来,并且嵌入的双引号必须用一对连续的双引号表示。
1997,Ford,E350,"Super, ""luxurious"" truck"
- 包含嵌入换行符的字段必须用双引号括起来。
1997,Ford,E350,"Go get one now they are going fast"
这是一个 CSV 看起来的例子
Year,Make,Model,Description,Price
1997,Ford,E350,"ac, abs, moon",3000.00
1999,Chevy,"Venture ""Extended
Edition""","",4900.00
1999,Chevy,"Venture ""Extended Edition, Very
Large""",,5000.00
1996,Jeep,Grand Cherokee,"MUST SELL!
air, moon roof, loaded",4799.00
我引用了上面的样本来自维基百科,所以更多细节请查看这个 链接。
我见过许多读写 CSV 文件的实现,甚至还有 LINQ to CSV,但我在读取值或查询值时并没有看到太大的灵活性。许多文章都逐行解析 CSV 文件,这对于达到他们的目标是好的,但当遇到多行值时几乎会失败。所以我努力以 LINQ 的方式自己实现。毫无疑问,我们知道 CSV 字段取决于文件本身,并且没有包含这些属性的 CLR 对象。相反,有些人对 LINQ to CSV 做得很棒,他们创建了包含相同属性的 CLR 对象,这是一种映射。相信我,这种方式一段时间后会困扰程序员,因为一旦文件中的字段发生变化,他/她就需要创建另一个 POCO 来映射这些字段。
所以我花了两天时间思考如何以一种更好的方式来做...如果我们可以写 record.Name 而不是魔法字符串 record["Name"],那就太好了。
正如我们在 .NET 4 中所知,Dynamic 和 DLR 对于几乎所有程序员来说都变得越来越重要,可以用来进行动态编程。我认为我们正在进行的正是动态编程的一种,正如 Anders Hejlsberg 所说:“在可以时使用强类型,但当不能时,动态来拯救。” 我仍然记得 2010 年 DevDays 大会上的话。这对我个人来说是一个巨大的挑战。
在我这个小库中,我将主要专注于动态编程和正则表达式来实现我的目标,你可以在互联网上找到很多关于它们各自的材料。
使用代码
Record
类是一个非常重要的类,用于动态表示 CSV 记录。换句话说,它包含 CSV 文件中特定记录的所有字段和值,这些字段和值在运行时成为该对象的成员,这借鉴了反射和相关内容的复杂性。此类似乎是 ExpandoObject
的实现,它包含一个数据字典来存储 CSV 文件中每条记录的键和值。
Public Class Record Inherits DynamicObject
Private dictionary As New Dictionary(Of String, Object)
Public Sub AddPair(key As String, value As Object)
dictionary(key) = value
End Sub
Public Overrides Function TryGetMember(binder As GetMemberBinder, _
ByRef result As Object) As Boolean
result = dictionary(binder.Name)
Return True
End Function
Public Overrides Function TrySetMember(binder As SetMemberBinder, _
value As Object) As Boolean
dictionary(binder.Name) = value
Return True
End Function
End Class
如果我们深入研究 Record 类,我们会发现它实现了 DynamicObject
类,这使我们能够创建一个可以在运行时发现其属性和方法的动态对象。我们只需重写 TryGetMember
和 TrySetMember
方法即可实现 Record 类成员的 getter 和 setter。
最酷的功能之一是 AddPair()
,它允许我们在运行时向这个类添加属性?!也许听起来很奇怪,这以及更多东西都得益于 DLR 和框架本身的动态支持。这在“反射时代”是很难做到的。
CSVDataContext
是将从 CSV 文件中获取的所有记录的容器。它直接接受要解析的文件路径。将字段提取到 Key 属性,并将值提取到 Records 属性。
在下面的代码中,我们注意到我们有两个属性 Keys,它存储字段名称,第二个是 Records,它保存每条记录的实际数据。
主要逻辑发生在构造函数中,该构造函数接受 CSV 文件的文件名,第二件事是从文件中获取字段,并遍历该文件的每一行,然后应用以下正则表达式
"[" & vbTab & ",](?=(?:[^""]|""[^""]*"")*$)"
该表达式返回与上述模式匹配的令牌,这意味着给我所有由逗号分隔的令牌,如果它们在双引号内,请将它们视为数据。最后,将它们存储在已创建的 Records
属性中。
Public Class CSVDataContext
Private Property keys As List(Of String)
Public Property Records As List(Of Object)
Public Sub New(filePath As String)
If File.Exists(filePath) Then
Try
Dim lines = File.ReadAllLines(filePath)
keys = New List(Of String)(lines(0).Replace(" ", "").Split(","))
Records = New List(Of Object)()
Dim tokens As IEnumerable(Of String)
Dim flag As Boolean
For i As Integer = 1 To lines.Length - 1
Dim record As New Record()
flag = lines(i).Where(Function(c) c = """").Count() Mod 2 = 1
If flag Then lines(i) = String.Concat(lines(i), lines(i + 1))
tokens = Regex.Split(lines(i), "[" & vbTab & ",](?=(?:[^""]|""[^""]*"")*$)")
For j = 0 To keys.Count - 1
record.AddPair(keys(j), tokens)
Next
If flag Then i += 1
Records.Add(record)
Next
Catch ex As Exception
Throw New FormatException()
End Try
Else
Throw New FileNotFoundException()
End If
End Sub
End Class
其余的则是实例化 CSVDataContext
,并以 LINQ 的方式使用 Records 属性!
Dim db As New CSVDataContext("test.csv")
Dim query = From r In db.Records
Select r
For Each q In query
Console.WriteLine("{0} {1} {2} {3}", q.Year, q.Make.ToString().PadRight(10, _
" "), q.Model.ToString().PadRight(45, " "), q.Price)
Next
在上面的代码片段中,我们创建了一个 DataContext
对象,然后创建了一个简单的查询,该查询从 *test.csv* 文件中获取所有记录。最后,我们遍历查询结果并在控制台中打印它们。如果我们仔细查看该查询,我们会注意到它看起来像一个普通的 LINQ to SQL,没有魔法字符串之类的,这就是动态对象的优点,它允许我们创建自定义属性并在运行时发现它们。
兴趣点
动态编程使开发者的生活比以前轻松得多,而有了 LINQ,生活变得甜蜜无比!不同技术和功能的结合探索了 .NET 的强大功能。