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灵活的排序和筛选操作。

posted @ 2011-05-09 22:24  deepwishly  阅读(527)  评论(0编辑  收藏  举报