5.2 LINQ to DataSet实现复杂数据查询
LINQ to DataSet将LINQ和ADO.NET集成,它通过ADO.NET获取数据,然后通过LINQ进行数据查询,从而实现对数据集进行非常复杂查询。本节将介绍如何使用LINQ to DataSet操作数据集DataSet中的数据。
5.2.1 使用LINQ to DataSet
LINQ to DataSet可以简单理解成通过LINQ对DataSet中保存的数据进行查询,它和第7章介绍的LINQ查询并没有太大的区别。LINQ to DataSet的使用通常包含以下步骤:
(1)获取DataSet/DataTable数据源。LINQ to DataSet通过LINQ查询DataSet/DataTable中的数据,所以首先要准备DataSet/DataTable数据源,可以通过 ADO.NET技术从数据库获取,可以通过XML技术从XML文件获取,也可以从其他任何形式的数据源获取,甚至可以在内存中直接创建并填充 DataSet/DataTable对象。
(2)将DataTable转换成IEnumerable<T>类型。从第7章了解到,LINQ只能在 IEnumerable<T>或IQueryable<T>接口对象上执行查询操作,而DataTable并没有实现这两个接口,不能直接查询。在LINQ to DataSet中,通过DataTableExtensions扩展的AsEnumerable()方法从DataTable获取一个等价的 IEnumerable<T>对象。
(3)使用LINQ语法编写查询。LINQ to DataSet中查询的编写可以使用查询语法和方法语法,可以对它执行任何IEnumerable<T>允许的查询操作。
(4)使用查询结果。查询结果产生后,就可以使用查询结果(一个IEnumerable<T>对象),比如,用foreach遍历所有元素,用Max()等进行数值计算,将它作为数据源进行二次查询等。
后面几个小节将通过实例详细介绍LINQ to DataSet的具体使用,但是为了更加容易理解,这些示例中的DataSet都通过代码直接在内存中编写,并不从数据库获取。
注意:由于DataSet本身是DataTable的集合,它可以包含一个或多个DataTable及它们之间的关系,LINQ to DataSet实际是对DataTable进行数据查询,并非对DataSet进行查询。
5.2.2 查询单个数据表
一个DataSet通常包含一个或多个DataTable,同时也包括它们之间的关系集合等,实际上可以把它看成是一个缩影的数据库。LINQ to DataSet也是对一个或多个DataTable进行查询,这些DataTable可以来自单个DataSet,也可以是来自多个DataSet。
在5.2.1节介绍了查询DataTable中元素的主要步骤,在对DataTable进行数据查询时必须使用DataTable类的 AsEnumerable()方法,该方法将DataTable转换成一个类型为IEnumerable<DataRow>的可枚举数据集合,它的定义如下:
public static EnumerableRowCollection<DataRow> AsEnumerable(
this DataTable source)
因此,从DataTable中获取的元素类型为DataRow,要进一步访问数据表的记录的具体字段数据,就需要使用DataRow的一个扩展泛型方法 ——Field<T>(),通过它获取DataRow的某字段的数据,它包括6个重载版本,其中最常用的是下面3个。
public static T Field<T>( this DataRow row, DataColumn column )
public static T Field<T>( this DataRow row, int columnIndex )
public static T Field<T>( this DataRow row, string columnName )
其中,参数column表示数据列(DataColumn),表示要返回数据的字段。参数columnIndex表示从0开始的索引列索引。columnName表示要返回数据的字段的名称。通常为了让代码更加通用,作者建议尽量使用字段名称来指定要返回的字段。
在示例代码5-1中,方法BuildOneDTDataSet()在内存中创建一个名为“PeopleDS”的数据集合,它只包含一个名为 “PeopleDT”的数据表,数据表包含3个字段:姓名(Name)、性别(XingBie)、年龄(Age)。在方法UseSelect()中,首先通过BuildOneDTDataSet()创建数据集,然后通过DataSet.Tables属性获取名为“PeopleDT”的数据表。在查询 query1和query2中通过DataTable.AsEnumerable()方法将DataTable转换成 IEnumerable<T>类型的数据集合,并进行查询。query1查询所有元素,而query2只查询姓名字段。
示例代码5-1
//随机创建一个包含数据的DataSet
static DataSet BuildOneDTDataSet( )
{
//可选姓名、性别和年龄,用于创建学生数据到数据表中
string[] nameSet = {"王霞","张三","李四","李花","王五", "陆六","夏七","吴八" };
string[] xbSet = { "女", "男", "男", "女", "男", "男", "男", "男" };
int[] ageSet = {18, 20, 21, 22, 19, 20, 25, 24};
DataSet ds = new DataSet("PeopleDS"); //创建名为PeopleDS的DataSet对象
DataTable dt = new DataTable("PeopleDT"); //创建名为PeopleDT的DataTable对象
ds.Tables.Add(dt); //将数据表dt添加到数据集ds中
//创建DataTable的列(字段)信息,包括3个字段:
//姓名:Name,string类型
//性别:XingBie,string类型
//年龄:Age,int类型
dt.Columns.AddRange(
new DataColumn[]
{
new DataColumn("Name", Type.GetType("System.String")),
new DataColumn("XingBie", Type.GetType("System.String")),
new DataColumn("Age", Type.GetType("System.Int32")),
});
//利用前面定义的可选姓名nameSet、年龄ageSet、性别xbSet创建多个学生信息
for (int i = 0; i < nameSet.Length; i++)
{
//根据当前编号,自动新建数据表中的一行,并产生一行数据
//然后通过DataTable.Rows.Add()将这一行添加到数据表dt中
DataRow row = dt.NewRow( );
row["Name"] = nameSet;
row["Age"] = ageSet;
row["XingBie"] = xbSet;
dt.Rows.Add(row); //添加到数据表dt中
}
return ds; //返回DataSet
}
static void UseSelect( )
{
DataSet ds = BuildOneDTDataSet( ); //获取数据集ds
DataTable dt = ds.Tables["PeopleDT"]; //从数据集ds中获取名为“PeopleDT”的数据表dt
//查询query1表示查询DataTable中所有记录,演示AsEnumerable()的使用
var query1 =
from pl in dt.AsEnumerable( )
select pl;
// i=ds.Tables[0].Rows[0]["Name"]
//域
System.Console.WriteLine("Query1:");
foreach (var item in query1) //打印查询query1的结果
{
//演示Field<T>方法的使用
System.Console.WriteLine("姓名:{0},性别:{1},年龄:{2}",
item.Field<string>("Name"), item.Field<string>("XingBie"), item.Field<int>("Age"));
}
//查询query2表示查询DataTable中所有人的姓名,演示AsEnumerable()和Field<T>的使用
var query2 =
from pl in dt.AsEnumerable( )
select pl.Field<string>("Name");
System.Console.WriteLine("Query2:"); //打印查询query1的结果
foreach (var item in query2)
{
System.Console.Write("{0} ", item);
}
System.Console.WriteLine( );
}
示例代码5-1的输出如下所示,其中,查询query1的结果为表中所有完整记录,包括姓名、性别和年龄。查询query2的结果只包括表中“Name”字段的集合。
Query1:
姓名:王霞,性别:女,年龄:18
姓名:张三,性别:男,年龄:20
姓名:李四,性别:男,年龄:21
姓名:李花,性别:女,年龄:22
姓名:王五,性别:男,年龄:19
姓名:陆六,性别:男,年龄:20
姓名:夏七,性别:男,年龄:25
姓名:吴八,性别:男,年龄:24
Query2:
王霞 张三 李四 李花 王五 陆六 夏七 吴八
除了使用select语句外,还可以对DataTable记录进行where过滤、orderby排序、groupby分组等操作。如示例代码5-2所示,其中,查询query3和query4中同时使用orderby和where子句,同时进行过滤和排序两个操作。query3查询所有年龄大于22岁的记录,并按照年龄从低到高排序。query4查询所有年龄在20~25之间的记录,并按照年龄从高到低排序。
示例代码5-2
static void UseOrderByWhere( )
{
DataSet ds = BuildOneDTDataSet( ); //获取数据集ds
DataTable dt = ds.Tables["PeopleDT"]; //从数据集ds中获取名为“PeopleDT”的数据表dt
//查询query3查询数据表中所有年龄大于22的人,并且按照年龄从低到高排序
var query3 =
from pl in dt.AsEnumerable( )
orderby pl.Field<int>("Age")
where pl.Field<int>("Age") > 22
select pl;
System.Console.WriteLine("Query3:");
foreach (var item in query1) //打印查询query3的结果
{
System.Console.WriteLine("姓名:{0},性别:{1},年龄:{2}",
item.Field<string>("Name"), item.Field<string>("XingBie"), item.Field<int>("Age"));
}
//查询query4查询数据表中所有年龄大于20小于25的人,并且按照年龄从高到低排序
var query4 =
from pl in dt.AsEnumerable( )
orderby pl.Field<int>("Age") descending
where pl.Field<int>("Age") > 20
where pl.Field<int>("Age") < 25
select pl;
System.Console.WriteLine("Query4:");
foreach (var item in query2) //打印查询query4的结果
{
System.Console.WriteLine("姓名:{0},性别:{1},年龄:{2}",
item.Field<string>("Name"), item.Field<string>("XingBie"), item.Field<int>("Age"));
}
}
示例代码5-2的输出如下所示,其中,query3输出是年龄大于22岁的记录,query4输出是年龄在20~25之间的记录。
Query3:
姓名:吴八,性别:男,年龄:24
姓名:夏七,性别:男,年龄:25
Query4:
姓名:吴八,性别:男,年龄:24
姓名:李花,性别:女,年龄:22
姓名:李四,性别:男,年龄:21
技巧:LINQ to DataSet查询DataTable的数据可以简单分成两个部分,首先是将DataTable转换成IEnumerable<T>数据集合,然后就是对IEnumerable<T>进行操作,这一步可以完全应用第7章介绍的所有LINQ查询操作。
5.2.3 查询多个数据表
通常,一个数据集(DataSet)包含多个数据表(DataTable),而且数据表之间具有一定的关联关系,从而表示一个关系型数据库。通过LINQ to DataSet同样可以轻松查询多个数据表中的数据,这通常需要使用多个from子句进行复合查询,同时通过where子句来进行多个表之间的关系判断。
本节的例子中,使用示例代码5-3中创建的数据集合,BuildDataSet()方法创建一个名为Students的数据表,包含两个数据表 Students和Scores,前者记录学生信息,包括:姓名(Name)、性别(XingBie)、年龄(Age)、成绩号(ScoreID)。后者记录学生成绩,包括:成绩号(ScoreID)、数学成绩(Math)、语文成绩(Chinese)、英语成绩(English)。其中,字段成绩号是两个表关联字段,通过该字段可以查询学生的成绩信息。
示例代码5-3
static DataSet BuildDataSet( )
{
DataSet ds = new DataSet("Students"); //创建Students数据集
//创建Students数据表dtStu,并添加到数据集ds中
//Students数据表包含学生信息
DataTable dtStu = new DataTable("Students");
ds.Tables.Add(dtStu);
//添加学生信息记录的列信息,包括4列数据:
//姓名:Name,string类型
//性别:XingBie,string类型
//年龄:Age,int类型
//成绩编号:ScoreID,int类型
dtStu.Columns.AddRange(new DataColumn[]{
new DataColumn("Name", Type.GetType("System.String")),
new DataColumn("XingBie", Type.GetType("System.String")),
new DataColumn("Age", Type.GetType("System.Int32")),
new DataColumn("ScoreID", Type.GetType("System.Int32")),
});
//添加5个学生信息到数据表dtStu中,分别包括姓名、性别、年龄和成绩编号
dtStu.Rows.Add("张三", "男", 20, 1);
dtStu.Rows.Add("李四", "男", 19, 2);
dtStu.Rows.Add("王霞", "女", 21, 3);
dtStu.Rows.Add("赵敏", "女", 22, 4);
dtStu.Rows.Add("吴安", "男", 18, 5);
//创建Scores数据表,并添加到数据集
//Scores数据表包含学生成绩记录
DataTable dtScore = new DataTable("Scores");
ds.Tables.Add(dtScore);
//添加成绩记录表的列(字段)信息,包含4个字段:
//成绩编号:ScoreID,int类型,与Students表的ScoreID字段对应
//数学成绩:Math,int类型
//语文成绩:Chinese,int类型
//英语成绩:English,int类型
dtScore.Columns.AddRange(new DataColumn[]{
new DataColumn("ScoreID", Type.GetType("System.Int32")),
new DataColumn("Math", Type.GetType("System.Int32")),
new DataColumn("Chinese", Type.GetType("System.Int32")),
new DataColumn("English", Type.GetType("System.Int32")),
});
//添加学生成绩记录,分别包括成绩编号、数学成绩、语文成绩、英语成绩
dtScore.Rows.Add(1, 80, 75, 78);
dtScore.Rows.Add(3, 88, 80, 60);
dtScore.Rows.Add(4, 75, 90, 80);
dtScore.Rows.Add(5, 59, 80, 75);
return ds; //返回数据集
}
查询多个数据表的数据通常通过多个from子句进行联合查询,每个from子句对应一个数据表,同时用where子句来表示多个数据表之间的关系,一般单个where子句表示两个表之间的关系。在进行多表数据查询之前,要明确几个问题:
(1)要在哪些数据表中查询数据?from子句该如何编写?
(2)查询结果包含哪些数据表的哪些字段?select子句该如何编写?
(3)各数据表之间的关系如何进行关联?where子句该如何编写?
(4)是否需要其他的操作,比如排序(orderby子句)、分组(group子句)等?
(5)该查询是使用简单的单个查询实现,还是通过多个查询组合实现?
如示例代码5-4所示,方法QueryStuScores()中首先通过BuildDataSet()方法获取数据集和要查询的数据表,其中dtStu表示学生信息数据表,dtScore表示学生成绩数据表。查询query1用于查询数据集合中所有学生的成绩,如果学生没有成绩则不作为结果返回。
query1中,第1个from子句从表dtStu中查询学生信息记录,并保存到临时变量stu中。第2个from子句从表dtScore中查询成绩记录,并保存到临时遍历score中。Where子句则用于实现两个表之间的关联关系,即:成绩号(ScoreID)相等。最后select子句则表示要将表dtStu的Name字段和dtScore的Math、Chinese、English字段作为查询结果。
示例代码5-4
static void QueryStuScores( )
{
DataSet ds = BuildDataSet( ); //获取数据集ds
DataTable dtStu = ds.Tables["Students"]; //从数据集ds中获取Students表dtStu
DataTable dtScore = ds.Tables["Scores"]; //从数据集ds中获取Scores表dtScore
var query1 = //查询query1查询所有学生的成绩
from stu in dtStu.AsEnumerable( ) //从Students表和Scores表中查询
join score in dtScore.AsEnumerable( )
on stu.Field<int>("ScoreID") equals score.Field<int>("ScoreID")
// where stu.Field<int>("ScoreID") == score.Field<int>("ScoreID") //成绩编号(ScoreID)相等
select new //匿名类型为查询结果元素类型,包括四个成员
{
Name = stu.Field<string>("Name"),
MathS = score.Field<int>("Math"),
Chinese = score.Field<int>("Chinese"),
English = score.Field<int>("English")
};
System.Console.WriteLine("Query1-所有学生成绩:");
foreach (var item in query1) //打印查询query1的结果
{
System.Console.WriteLine("姓名:{0}, 数学:{1}, 语文:{2}, 英语:{3}",
item.Name, item.MathS, item.Chinese, item.English);
}
}
示例代码5-4的输出如下,从中可以看出学生“李四”没有成绩,所以不在查询query1的结果中。