如何:从多个源填充对象集合 (LINQ)
更新:2007 年 11 月
此示例演示如何将不同源类型的数据合并为新类型的序列。以下代码中的示例可合并字符串和整数数组。但是,相同的原则也适用于任意两个数据源,包括内存中对象(包括 LINQ to SQL 查询结果、ADO.NET 数据集和 XML 文档)的任意组合。
![]() |
---|
不要尝试将内存中的数据或文件系统中的数据与仍在数据库中的数据相联接。此种跨域联接会生成未定义的结果,因为为数据库查询和其他类型的源定义联接运算的方式可能不同。另外,如果数据库中的数据量足够大,则存在此类运算引发内存不足异常的风险。若要将数据库数据与内存中的数据相联接,请首先对数据库查询调用 ToList 或 ToArray,然后对返回的集合执行联接。 |
创建数据文件
- 创建一个新的 Visual C# 或 Visual Basic 项目,然后将下面的 names.csv 和 scores.csv 文件复制到解决方案文件夹中,如如何:联接不同文件的内容 (LINQ) 中所述。
示例
下面的示例演示如何使用命名类型 Student 来存储两个模拟 .csv 格式的电子表格数据的内存中的字符串集合的合并数据。第一个字符串集合表示学生姓名和 ID,第二个集合表示学生 ID(在第一列中)和四次考试分数。
Class Student
Public FirstName As String
Public LastName As String
Public ID As Integer
Public ExamScores As List(Of Integer)
End Class
Class PopulateCollections
Shared Sub Main()
' Join content from spreadsheets into a list of Student objectss.
' names.csv contains the student name
' plus an ID number. scores.csv contains the ID and a
' set of four test scores. The following query joins
' the scores to the student names by using ID as a
' matching key, and then projects the results into a new type.
Dim names As String() = System.IO.File.ReadAllLines("../../../names.csv")
Dim scores As String() = System.IO.File.ReadAllLines("../../../scores.csv")
' Name: Last[0], First[1], ID[2], Grade Level[3]
' Omelchenko, Svetlana, 111, 2
' Score: StudentID[0], Exam1[1] Exam2[2], Exam3[3], Exam4[4]
' 111, 97, 92, 81, 60
' This query joins two dissimilar spreadsheets based on common ID value.
' Multiple from clauses are used instead of a join clause
' in order to store results of id.Split.
' Note the dynamic creation of a list of ints for the
' TestScores member. We skip 1 because the first string
' in the array is the student ID, not an exam score.
Dim scoreQuery1 = From name In names _
Let n = name.Split(New Char() {","}) _
From id In scores _
Let s = id.Split(New Char() {","}) _
Where n(2) = s(0) _
Select New Student() _
With {.FirstName = n(0), .LastName = n(1), .ID = n(2), _
.ExamScores = (From scoreAsText In s Skip 1 _
Select Convert.ToInt32(scoreAsText)).ToList()}
' Optional. Store the query results for faster access
' in future queries. May be useful with very large data files.
Dim students As List(Of Student) = scoreQuery1.ToList()
' Display the list contents
' and perform a further calculation
For Each s In students
Console.WriteLine("The average score of " & s.FirstName & " " & _
s.LastName & " is " & s.ExamScores.Average())
Next
' Keep console window open in debug mode.
Console.WriteLine("Press any key to exit.")
Console.ReadKey()
End Sub
End Class
' Output:
'The average score of Adams Terry is 85.25
'The average score of Fakhouri Fadi is 92.25
'The average score of Feng Hanying is 88
'The average score of Garcia Cesar is 88.25
'The average score of Garcia Debra is 67
'The average score of Garcia Hugo is 85.75
'The average score of Mortensen Sven is 84.5
'The average score of O'Donnell Claire is 72.25
'The average score of Omelchenko Svetlana is 82.5
'The average score of Tucker Lance is 81.75
'The average score of Tucker Michael is 92
'The average score of Zabokritski Eugene is 83
class Student
{
public string FirstName { get; set; }
public string LastName { get; set; }
public int ID { get; set; }
public List<int> ExamScores { get; set; }
}
class PopulateCollections
{
static void Main()
{
// These data files are defined in How to: Join Content from Dissimilar Files (LINQ)
string[] names = System.IO.File.ReadAllLines(@"../../../names.csv");
string[] scores = System.IO.File.ReadAllLines(@"../../../scores.csv");
// Merge the data sources using a named type.
// var could be used instead of an explicit type.
// Note the dynamic creation of a list of ints for the
// TestScores member. We skip 1 because the first string
// in the array is the student ID, not an exam score.
IEnumerable<Student> queryNamesScores =
from name in names
let x = name.Split(',')
from score in scores
let s = score.Split(',')
where x[2] == s[0]
select new Student()
{
FirstName = x[0],
LastName = x[1],
ID = Convert.ToInt32(x[2]),
ExamScores = (from scoreAsText in s.Skip(1)
select Convert.ToInt32(scoreAsText)).
ToList()
};
// Optional. Store the newly created student objects in memory
// for faster access in future queries. Could be useful with
// very large data files.
List<Student> students = queryNamesScores.ToList();
// Display the results and perform one further calculation.
foreach (var student in students)
{
Console.WriteLine("The average score of {0} {1} is {2}.",
student.FirstName, student.LastName, student.ExamScores.Average());
}
//Keep console window open in debug mode
Console.WriteLine("Press any key to exit.");
Console.ReadKey();
}
}
/* Output:
The average score of Adams Terry is 85.25.
The average score of Fakhouri Fadi is 92.25.
The average score of Feng Hanying is 88.
The average score of Garcia Cesar is 88.25.
The average score of Garcia Debra is 67.
The average score of Garcia Hugo is 85.75.
The average score of Mortensen Sven is 84.5.
The average score of O'Donnell Claire is 72.25.
The average score of Omelchenko Svetlana is 82.5.
The average score of Tucker Lance is 81.75.
The average score of Tucker Michael is 92.
The average score of Zabokritski Eugene is 83.
*/
这些示例中的数据源是使用对象初始值设定项初始化的。查询使用 join 子句来将姓名与分数相匹配。将 ID 用作外键。但是,在一个源中,ID 是字符串,而在另一个源中,ID 是整数。因为 join 需要进行相等比较,所以您必须首先从字符串中提取 ID,然后将它转换为整数。这在两个 let 子句中实现。第一个 let 子句中的临时标识符 x 存储通过在每个空格处拆分原始字符串而创建的三个字符串的数组。第二个 let 子句中的标识符 n 存储将 ID 子字符串转换为整数的结果。在 select 子句中,对象初始值设定项用于使用两个源的数据来实例化每个新 Student 对象。
如果您不需要存储查询结果,则匿名类型会比命名类型更方便。如果您要将查询结果传递到执行此查询所用的方法外部,则需要使用命名类型。下面的示例执行的任务与前面的示例相同,但使用匿名类型代替命名类型:
' This query uses an anonymous type
' Note the dynamic creation of a list of ints for the
' TestScores member. We skip 1 because the first string
' in the array is the student ID, not an exam score.
Dim scoreQuery2 = From name In names _
Let n = name.Split(New Char() {","}) _
From id In scores _
Let s = id.Split(New Char() {","}) _
Where n(2) = s(0) _
Select New With {.Last = n(0), _
.First = n(1), _
.TestScores = (From scoreAsText In s Skip 1 _
Select Convert.ToInt32(scoreAsText)).ToList()}
' Display the list contents
' and perform a further calculation
For Each s In scoreQuery2
Console.WriteLine("The average score of " & s.First & " " & s.Last & " is " & s.TestScores.Average())
Next
// Merge the data sources by using an anonymous type.
// Note the dynamic creation of a list of ints for the
// TestScores member. We skip 1 because the first string
// in the array is the student ID, not an exam score.
var queryNamesScores2 =
from name in names
let x = name.Split(',')
from score in scores
let s = score.Split(',')
where x[2] == s[0]
select new
{
First = x[0],
Last = x[1],
TestScores = (from scoreAsText in s.Skip(1)
select Convert.ToInt32(scoreAsText))
.ToList()
};
// Display the results and perform one further calculation.
foreach (var student in queryNamesScores2)
{
Console.WriteLine("The average score of {0} {1} is {2}.",
student.First, student.Last, student.TestScores.Average());
}
编译代码
创建一个面向 .NET Framework 3.5 版的 Visual Studio 项目。默认情况下,该项目具有对 System.Core.dll 的引用以及针对 System.Linq 命名空间的 using 指令 (C#) 或 Imports 语句 (Visual Basic)。
将此代码复制到您的项目。
按 F5 编译并运行程序。
按任意键退出控制台窗口。
请参见
概念
参考
修订记录
日期 |
修订 |
原因 |
---|---|---|
2008 年 7 月 |
又添加了一个代码示例集。 |
内容 Bug 修复 |