LINQ to DataSet实现复杂数据查询
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[i];
row["Age"] = ageSet[i];
row["XingBie"] = xbSet[i];
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;
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表中查询
from score in dtScore.AsEnumerable( )
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的结果中。
Query1-所有学生成绩:
姓名:张三, 数学:80, 语文:75, 英语:78
姓名:王霞, 数学:88, 语文:80, 英语:60
姓名:赵敏, 数学:75, 语文:90, 英语:80
姓名:吴安, 数学:59, 语文:80, 英语:75
对于一些比较复杂的查询,仅使用一个LINQ查询很难实现,这就需要使用多个查询配合使用。比如现在需要查询没有成绩的学生的信息,该查询可以有两种方法实现,但都需要通过多个查询配合实现。
如示例代码5-5所示,query2查询采用第1种方法,首先查询scoreIDs得出表dtScore中所有的成绩号集合,然后query2在从表dtStu中找出所有学生中,成绩号不在查询scoreIDs中的学生,这些学生就是没有成绩的学生。
query3则采用第2种方法,首先查询scrStu通过两个并列的from子句,从表dtStu和dtScore中查询所有具有成绩的学生集合,和示例代码5-4中一样。然后query3通过方法语法的形式,通过Except()方法从所有学生信息中剔除具有成绩的学生,剩下就是没有成绩的学生。
示例代码5-5
static void QueryNoneScoreStu( )
{
DataSet ds = BuildDataSet( ); //获取数据集ds
DataTable dtStu = ds.Tables["Students"]; //从数据集ds中获取Students表dtStu
DataTable dtScore = ds.Tables["Scores"]; //从数据集ds中获取Scores表dtScore
var scoreIDs = //查询scoreIDs查询所有有成绩的学生的成绩编号
from score in dtScore.AsEnumerable( )
select score.Field<int>("ScoreID");
var query2 = //查询query2查询所有成绩号不在查询scoreIDs中学生信息
from stu in dtStu.AsEnumerable( )
where !scoreIDs.Contains<int>(stu.Field<int>("ScoreID"))
select stu;
System.Console.WriteLine("Query2-没有成绩的学生:");
foreach (var item in query2) //打印查询query2的结果
{
System.Console.WriteLine("姓名:{0}, 性别:{1}, 年龄:{2}",
item.Field<string>("Name"), item.Field<string>("XingBie"), item.Field<int>("Age"));
}
var scrStu = //查询scrStu查询所有具有成绩信息的学生
from stu in dtStu.AsEnumerable( )
from score in dtScore.AsEnumerable( )
where stu.Field<int>("ScoreID") == score.Field<int>("ScoreID")
select stu;
//查询query3是从所有学生记录中剔除具有成绩的学生。
var query3 = dtStu.AsEnumerable( ).Except(scrStu);
System.Console.WriteLine("Query3-没有成绩的学生:");
foreach (var item in query3) //打印查询query3的结果
{
System.Console.WriteLine("姓名:{0}, 性别:{1}, 年龄:{2}",
item.Field<string>("Name"), item.Field<string>("XingBie"), item.Field<int>("Age"));
}
}
示例代码5-5的输出如下所示,可以看出query2和query3虽然实现的方法不同,但是最终产生的查询结果都是一样,都给出了没有成绩的学生“李四”的详细信息。
Query2-没有成绩的学生:
姓名:李四, 性别:男, 年龄:19
Query3-没有成绩的学生:
姓名:李四, 性别:男, 年龄:19
技巧:从示例代码5-5中query2和query3可以看出,很多问题都可以有很多种不同的解决方法,但是最终结果都是一样,在解决问题时,也应该尽可能去寻找更多的解决办法,并从中选择最简单高效最适用的一种方法。另外,不要忘了方法语法在LINQ中的使用。
5.2.4 用查询创建数据表
LINQ to DataSet通过DataTableExtensions类提供的扩展方法CopyToDataTable()将从数据表中获取到的查询结果(类型为IEnumerable<DataRow>)直接复制到一个新的数据表(DataTable)中,从而可以将查询结果绑定到界面控件(DataGridView等),也可以使用一些DataTable特有的特性。
CopyToDataTable()包括3个重载版本,定义如下,其中第1个版本最简单,也最常用。注意,这里的所有类型T都是DataRow类型及其子类。
public static DataTable CopyToDataTable<T>(
this IEnumerable<T> source) where T : DataRow
public static void CopyToDataTable<T>(
this IEnumerable<T> source,
DataTable table,
LoadOption options) where T : DataRow
public static void CopyToDataTable<T>(
this IEnumerable<T> source,
DataTable table,
LoadOption options,
FillErrorEventHandler errorHandler) where T : DataRow
其中,table表示目标数据表对象,用来保存数据。options用于指定DataTable的加载属性。errorHandler是一个函数委托,开发人员可以指定自定义的异常处理操作。CopyToDataTable()方法使用下面的过程通过查询创建DataTable复本:
(1)CopyToDataTable()方法克隆源表中的DataTable(实现IQueryable<T>接口的DataTable对象)。IEnumerable源通常来源于LINQ to DataSet表达式或方法查询。
(2)目标DataTable的架构从源表中第一个DataRow对象的列生成,克隆表的名称是源表的名称加上单词“query”。
(3)对于源表中的每一行,将行内容复制到新DataRow对象中,然后将该对象插入到目标DataTale中。
(4)复制完源表中所有DataRow对象后,返回克隆的DataTable。如果源序列不包含任何DataRow对象,则该方法将返回一个空DataTable。
示例代码5-6演示了CopyToDataTable()方法的使用,其中,查询query1查询所有既有成绩,年龄又大于20岁的学生信息,此时query1类型为IEnumerable<DataRow>。然后使用query1的CopyToDataTable()方法创建一个DataTable副本newDt,最后打印出newDt的数据。
示例代码5-6
static void UseCopyToDTSimple( )
{
DataSet ds = BuildDataSet( ); //获取数据集ds
DataTable dtStu = ds.Tables["Students"]; //从数据集ds中获取Students表dtStu
DataTable dtScore = ds.Tables["Scores"]; //从数据集ds中获取Scores表dtScore
var query1 = //查询query1年龄大于20且具有成绩的学生
from stu in dtStu.AsEnumerable( )
from score in dtScore.AsEnumerable( )
where stu.Field<int>("ScoreID") == score.Field<int>("ScoreID")
where (int)stu["Age"] > 20
select stu;
//通过CopyToDataTable()方法创建新的副本
//然后打印该副本的信息
DataTable newDt = query1.CopyToDataTable<DataRow>( );
System.Console.WriteLine("学生列表:");
foreach (var item in newDt.AsEnumerable()) //打印该副本的信息
{
System.Console.WriteLine("姓名:{0}, 性别:{1}, 年龄:{2}",
item["Name"], item["XingBie"], item["Age"]);
}
}
示例代码5-6的输出如下所示,可见query1的副本newDt所包含的数据和query1完全相同,注意本例中的数据源是示例代码5-3所生成。
学生列表:
姓名:王霞, 性别:女, 年龄:21
姓名:赵敏, 性别:女, 年龄:22
技巧:由于本例是控制台应用程序,所以只是简单打印newDt的记录,实际开发中CopyToDataTable()创建的副本,通常用于界面绑定。
5.2.5 修改表中字段数据
在前面章节的示例代码中,只是使用DataRowExtensions.Field()方法来获取数据表中字段的数据,当然LINQ to DataSet有时也需要对数据表中的数据进行修改,本节介绍如何使用SetField()修改数据。
在LINQ to DataSet中,DataRowExtensions类提供泛型扩展方法SetField(),用于设置数据表中指定列的数据,并且指定明确的数据类型。DataRowExtensions.SetField()方法具有3个重载版本,它们的定义如下所示:
public static void SetField<T>(
this DataRow row,
DataColumn column,
T value)
public static void SetField<T>(
this DataRow row,
int columnIndex,
T value)
public static void SetField<T>(
this DataRow row,
string columnName,
T value)
其中,column是表示要设置数据的列对象(DataColumn类型),columnIndex是从0开始的要设置数据的列索引,columnName是要设置数据的列名称。通常,一个数据表的列名是固定不变的,所以作者建议尽可能使用列名指定要设置数据的列,这样的代码更具扩展性。
示例代码5-7演示了SetField()方法的使用,通过BuildDataSet()方法获取数据表,第1个foreach语句遍历数据表的记录。通过SetField<int>()方法将学生的年龄增加2岁,因为学生年龄是int类型,所以SetField<T>()中的T用int类型表示。最后,打印出数据表中所有的学生列表。
示例代码5-7
static void UseSetField( )
{
DataSet ds = BuildDataSet( ); //获取数据集ds
DataTable dtStu = ds.Tables["Students"]; //从数据集ds中获取Students表dtStu
//遍历表dtStu中所有的学生,并通过SetField()方法将它们的年龄都增加2岁。
foreach (var row in dtStu.AsEnumerable())
{
int age = row.Field<int>("Age");
row.SetField<int>("Age", age + 2);
}
System.Console.WriteLine("学生列表:");
foreach (var item in dtStu.AsEnumerable( )) //打印修改年龄后的学生信息
{
System.Console.WriteLine("姓名:{0}, 性别:{1}, 年龄:{2}",
item["Name"], item["XingBie"], item["Age"]);
}
}
示例代码5-7的输出如下所示,从中可以看出,所有学生的年龄都被增加了2岁。
学生列表:
姓名:张三, 性别:男, 年龄:22
姓名:李四, 性别:男, 年龄:21
姓名:王霞, 性别:女, 年龄:23
姓名:赵敏, 性别:女, 年龄:24
姓名:吴安, 性别:男, 年龄:20
注意:SetField()方法是直接修改数据表中的记录,如果要保持原数据不变,那么在使用SetField()之前,应该先备份源数据表,作者建议通过CopyToDataTable()方法对源数据表进行备份。
5.2.6 使用数据视图DataView
数据视图(DataView)是常用的只读形式数据保存形式,可以将它绑定到UI界面,为用户提供形象动态的数据查阅功能。DataView本身可以从DataTable或DataSet获取,除此之外,在.NET3.5中,还可以通过LINQ查询来获取DataView。
在LINQ to DataSet中,可以使用DataTableExtensions.AsDataView()扩展方法从DataTable或LINQ查询创建一个与数据源对应的DataView对象。AsDataView()方法包括2个重载版本,定义如下,它们只是扩展的目标类型不同(即:数据源不同)。
public static DataView AsDataView(
this DataTable table
)
public static DataView AsDataView<T>(
this EnumerableRowCollection<T> source
) where T : DataRow
AsDataView()第2个重载版本中,需要EnumerableRowCollection<DataRow>类型的数据源,该类型表示从LINQ查询返回的DataRow对象集合,可以从LINQ查询获取。
示例代码5-8演示了AsDataView()方法的使用。其中,BuildDataSet()只是用来创建数据源,不再赘述。CreateDataView()方法中,dvDt是通过DataTable类的扩展方法AsDataView()创建DataView,该DataView中包含数据表中所有的数据记录。其中,查询query1、query2、query3都是LINQ to DataSet查询,并且明确类型为EnumerableRowCollection<DataRow>,当然这里也可以用var可变类型,编译器会自动转换。
示例代码5-8
static DataSet BuildDataSet( )
{
DataSet ds = new DataSet("Students"); //创建Students数据集
//创建Students数据表,并添加到数据集
//Students数据表包含学生信息
DataTable dtStu = new DataTable("Students");
ds.Tables.Add(dtStu);
//添加学生信息记录的列(字段)信息,包括3个字段:
//姓名:Name,string类型
//性别:XingBie,string类型
//年龄:Age,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")),
});
//添加学生信息的行信息,包括:姓名、性别和年龄
dtStu.Rows.Add("张三", "男", 20);
dtStu.Rows.Add("李四", "男", 19);
dtStu.Rows.Add("王霞", "女", 21);
dtStu.Rows.Add("赵敏", "女", 22);
dtStu.Rows.Add("吴安", "男", 18);
dtStu.Rows.Add("杨花", "女", 23);
return ds; //返回数据集
}
static void CreateDataVeiw( )
{
DataSet ds = BuildDataSet( ); //获取数据集ds
DataTable dt = ds.Tables["Students"]; //从数据集ds获取Students表dt
//用DataTable.AsDataView()方法从数据表dt创建DataView对象dvDt
DataView dvDt = dt.AsDataView( );
//query1用LINQ查询创建DataView对象dvDt,查询它所有的元素
EnumerableRowCollection<DataRow> query1 =
from stu in dt.AsEnumerable( )
select stu;
DataView dvNml = query1.AsDataView( ); //获取查询query1产生的DataView
//query2用LINQ查询创建具有过滤信息的DataView,查询所有姓“杨”的学生
EnumerableRowCollection<DataRow> query2 =
from stu in dt.AsEnumerable( )
where stu.Field<string>("Name").StartsWith("杨")
select stu;
DataView dvFilter = query2.AsDataView( ); //获取查询query2产生的DataView
//query3用LINQ查询创建具有排序信息的DataView,将学生按照从小到大的顺序排序
EnumerableRowCollection<DataRow> query3 =
from stu in dt.AsEnumerable( )
orderby stu.Field<int>("Age")
select stu;
DataView dvSort = query3.AsDataView( ); //获取查询query3产生的DataView
}
很多场合都需要对数据进行排序和过滤操作,在LINQ to DataSet中有两种方法完成这样的操作,一是通过具有排序和过滤操作得到的LINQ查询创建DataView。如示例代码5-8中的dvFilter和dvSort,前者通过具有where子句过滤功能的查询query2创建,后者通过具有orderby子句排序功能的查询query3创建,dvFilter和dvSort所得到的数据自然是过滤和排序之后的数据集合。另外一种方法是通过DataView类的RowFilter和Sort属性分别进行过滤和排序操作,这两个属性都是字符串(string)类型。
RowFilter属性接收一个表示过滤条件的字符串,格式为:指定列的名称后跟一个运算符和一个要筛选的值。运算符可以是:等于(=)、大于(>)、小于(<)等。通过设置RowFilter属性的值,有两种不同的方式清除DataView上的过滤条件:
q 将RowFilter属性设置为null。
q 将RowFilter属性设置为一个空字符串。Sort属性接收一个表示排序信息的字符串,它包含列名,后跟“ASC”(升序)或“DESC”(降序)。在默认情况下列按升序排序,多个列可用逗号隔开。清除DataView中的排序信息也有两种方式:
q 将Sort属性设置为null。
q 将Sort属性设置为一个空字符串。
示例代码5-9演示了RowFilter和Sort属性的使用,DataView对象dvDt是直接从DataTable对象dtStu创建而来,包含dtStu中的所有记录。其中RowFilter设置为“Age>20”表示只需要Age字段大于20的记录。Sort设置为“Age asc, Name desc”表示先按照Age字段从低到高排序,后按照姓名从高到低排序。
示例代码5-9
static void UseDataView( )
{
DataSet ds = BuildDataSet( ); //获取数据集ds
DataTable dt = ds.Tables["Students"]; //从数据集ds获取Students表dt
//用DataTable.AsDataView()方法从数据表dt创建DataView对象dvDt
DataView dvDt = dt.AsDataView( );
//通过RowFilter属性设置DataView过滤信息,只需要年龄大于20岁的学生记录
dvDt.RowFilter = "Age > 20";
//设置RowFilter为null或空字符串,清除过滤信息,二选一
dvDt.RowFilter = string.Empty;
dvDt.RowFilter = null;
//通过Sort属性设置DataView排序信息,年龄从小到大排序,姓名从大到小排序
dvDt.Sort = "Age asc, Name desc";
//设置RowFilter为null或空字符串,清除过滤信息,二选一
dvDt.Sort = string.Empty;
dvDt.Sort = null;
}
从上面示例代码可以看出,通过LINQ to DataSet查询和DataView的RowFilter、Sort属性都可以进行排序和过滤。但是LINQ查询提供的排序和过滤功能更加强大,开发人员可以编写自定义的过滤函数、排序方法等。而RowFilter和Sort属性只能是基于字段的表达式,只能实现简单的排序和过滤,由于可以清除,所以更为灵活。
技巧:当DataView要显示的数据比较复杂,需要进行复杂的过滤或排序操作时,建议使用LINQ查询来创建DataView,作为最终或临时视图。然后使用RowFilter和Sort属性进行DataView灵活的排序和筛选操作。