9.2 LINQ基本子句

其实,同SQL查询语句一样,LINQ查询语句也提供有如from、select、where、orderby等关键字,这些关键字在LINQ中是基本子句。

9.2.1 from查询子句

from子句是LINQ查询语句中最基本的,同时也是最重要的、必需的子句关键字。与SQL查询语句不同的是,from关键字必须在LINQ查询语句的开始,后面跟随着项目名称和数据源。格式如下所示:


from<项目>in<数据源>


示例如下面的代码所示:


var result=from data in num select data;


如上面的from子句格式与示例代码所示,from子句指定项目名称和数据源,并且指定需要查询的内容。其中,项目名称作为数据源的一部分而存在,用于表示和描述数据源中的每个元素;而数据源可以是数组、集合、数据库甚至XML等。

顾名思义,可以将from语句理解为“来自”,而in可以被理解为“在哪个数据源中”。这样可以将from data in num select data语句翻译成“找到来自num数据源中的集合data”,这样就能够更加方便地理解from语句。

注意from子句的数据源的类型必须为IEnumerable、IEnumerable<T>类型或者IEnumerable、IEnumerable<T>的派生类,否则from不能够支持LINQ查询语句。

其实,在.NET Framework泛型编程中,List(可通过索引的强类型列表)也能够支持LINQ查询语句的from关键字,因为List实现了IEnumerable、IEnumerable<T>类型,在LINQ中可以对List类进行查询。示例代码如下所示:


List<string>myList=new List<string>();

myList.Add("马伟");

myList.Add("马伟1");

myList.Add("马伟2");

var result=from data in myList select data;

foreach(variin result)

{

Label1.Text+=i.ToString();

}


除了上面的简单查询之外,from查询子句还支持嵌套查询。

我们知道,在SQL语句中,为了实现某一功能,往往需要包含多个条件,以及包含多个SQL子句嵌套。而在LINQ查询语句中,并没有and关键字为复合查询提供功能。如果需要进行复杂的复合查询,可以使用在from子句中嵌套另一个from子句来实现这样的复合查询。示例代码如下所示:


var result=from namedata in name

from emaildata in email

where emaildata.Contains(namedata)

select namedata;


上述代码就使用了一个嵌套查询进行LINQ查询。在有多个数据源或者包括多个表的数据需要查询时,就可以使用这种from子句嵌套查询。

为了演示这种from子句嵌套查询,下面来分别创建两个数据源。如下面的代码所示:


List<string>name=new List<string>();

name.Add("mawei");

name.Add("zhangjun");

name.Add("huawei");

name.Add("liaojie");

List<string>email=new List<string>();

email.Add("mawei@hotmail.com");

email.Add("liaojie@hotmail.com");

email.Add("zhangjun@hotmail.com");

email.Add("zhangsan@hotmail.com");


其中,name存放了联系人姓名的拼音名称,而email则存放了联系人的邮箱。为了方便地查询在数据源中“联系人”和“联系人邮箱”都存在并且匹配的数据,就需要使用from子句嵌套查询。示例代码如下所示:


var result=from namedata in name

from emaildata in email

where emaildata.Contains(namedata)

select namedata;

foreach(variin result)

{

Label1.Text+=i.ToString()+"<br>";

}


运行结果如图9-4所示。

其实,对于这种嵌套查询在LINQ中会被经常使用到,因为开发人员常常遇到需要面对多个表与多个条件,以及不同数据源或数据源对象的情况,使用LINQ查询语句的嵌套查询可以方便地在不同的表和数据源对象之间建立关系。

9.2.2 select选择子句

与from子句一样,select子句也是LINQ查询语句中必不可少的关键字。在LINQ查询语句中必须包含select子句,若不包含select子句则系统会抛出异常(除特殊情况外)。select子句指定了返回到集合变量中的元素是来自哪个数据源的。

如上面的from子句嵌套查询示例中,如果将select namedata改为select emaildata,如下面的代码所示:


var result=from namedata in name

from emaildata in email

where emaildata.Contains(namedata)

select emaildata;

foreach(variin result)

{

Label1.Text+=i.ToString()+"<br>";

}


这样得出的结果就是email数据源中的数据,如图9-5所示。

figure_0348_0250

图 9-4 from子句嵌套查询示例运行结果

figure_0348_0251

图 9-5 示例运行结果

从上面的示例中可以看出,对于不同的select对象返回的结果也不尽相同,当开发人员需要进行复合查询时,可以通过select语句返回不同的复合查询对象。这在多数据源和多数据对象查询中是非常有帮助的。

9.2.3 where条件子句

我们知道,在SQL查询语句中可以通过where子句来设置相关的查询条件表达式,以此来进行数据的筛选工作。而在LINQ中,同样也包括功能强大的where子句,可以通过为它设置相关的查询表达式来进行数据源中数据的筛选工作。示例如下面的代码所示:


string[]str={"abd","abcrttt","ab",

"ggggabcd","abc","abcde",};

var result=from data in str

where data.Contains("abc")

select data;

foreach(variin result)

{

Label1.Text+=i.ToString()+",";

}


在上面的示例代码中,where data.Contains("abc")表示在数组str中查询匹配“abc”的字符串。因此它返回的结果是abcrttt, ggggabcd, abc, abcde。

同样,也可以在LINQ查询语句中包含多个where子句,多个where子句使用“&&”连接,而且where子句中可以包含一个或多个布尔值变量。例如,需要在上面的数组str中查询匹配“abc”并且长度大于7的字符串。如下面的代码所示:


var result=from data in str

where data.Contains("abc")

&&data.Length>7

select data;

foreach(variin result)

{

Label1.Text+=i.ToString()+",";

}


这样,它返回的结果便成了ggggabcd。

除此使用“&&”连接之外,还可以将多个where子句写成如下方式:


var result=from data in str

where data.Contains("abc")

where data.Length>7

select data;


其运行结果与上面的“&&”连接一样,同样返回ggggabcd。

复合where子句查询通常用于同一个数据源中的数据查询,当需要在同一个数据源中进行筛选查询时,可以使用where子句进行单个或多个where子句条件查询,where子句能够对数据源中的数据进行筛选并将复合条件的元素返回到集合中。

9.2.4 orderby排序子句

同SQL查询语句一样,在LINQ查询语句中也提供了一个排序子句orderby。这里的orderby是一个词组,而不是分开的。orderby能够支持对象的升序((acending)或者降序((dscending)排序。排序示例代码如下所示:


int[]num={1,2,3,4,5,6,7,9,8};

var result=from data in num

where data>5

orderby data descending

select data;

foreach(variin result)

{

Label1.Text+=i.ToString();

}


在上面的代码中,因为采用降序((dscending)排序,所以返回的结果是9876。

当然,使用orderby子句同样能够进行多个条件排序,如果需要使用orderby子句进行多个条件排序,只需要将这些条件用“,”号分隔即可。示例代码如下所示:


var result=from data in employee

orderby data.employeeid descending, data.employeename

select data;


9.2.5 group分组子句

在LINQ查询语句中,group子句对from语句执行查询的结果进行分组,并返回元素类型为IGrouping<TKey, TElement>的对象序列。group子句支持将数据源中的数据进行分组。但进行分组前,数据源必须支持分组操作才可使用group语句进行分组处理。如下面的示例代码所示:


public class Student

{

public int Age;

public string Name;

public Student(int age, string name)

{

this.Age=age;

this.Name=name;

}

}


上面的Student类用于描述学生的年龄和姓名,并且要求按照年龄进行分组。如下面的示例代码所示:


List<Student>person=new List<Student>();

person.Add(new Student(25,"张三"));

person.Add(new Student(26,"张华"));

person.Add(new Student(25,"小西"));

person.Add(new Student(24,"张军"));

person.Add(new Student(26,"张雨"));

var result=frompin person

orderby p.Age ascending

grouppby p.Age;

foreach(var element in result)

{

Label1.Text+=element.Key+"岁组的学生有:";

foreach(Studentpin element)

{

Label1.Text+=p.Name.ToString()+",";

}

Label1.Text+="<br>";

}


在上面的代码中,使用了grouppby p.Age子句使数据按照年龄进行分组,其运行结果如图9-6所示。

figure_0351_0252

图 9-6 分组示例运行结果

如图9-6所示,group子句将数据源中的数据进行分组,在遍历数据元素时,并不像前面的章节那样直接对元素进行遍历,因为group子句返回的是元素类型为IGrouping<TKey, TElement>的对象序列,必须在循环中嵌套一个对象的循环才能够查询相应的数据元素。

在使用group子句时,LINQ查询子句的末尾并没有select子句,因为group子句会返回一个对象序列,通过循环遍历才能够在对象序列中寻找到相应的对象的元素。如果使用group子句进行分组操作,可以不使用select子句。

9.2.6 into联接子句

通常情况下,LINQ查询语句中无须into子句,但是如果需要对分组中的元素进行操作,则需要使用into子句,into子句通常需要和group子句一起使用。into语句能够创建临时标识符用于保存查询的集合。示例代码如下所示:


List<Student>person=new List<Student>();

person.Add(new Student(25,"张三"));

person.Add(new Student(26,"张华"));

person.Add(new Student(25,"小西"));

person.Add(new Student(24,"张军"));

person.Add(new Student(26,"张雨"));

var result=frompin person

orderby p.Age ascending

grouppby p.Age

into per

select per;

foreach(var element in result)

{

Label1.Text+=element.Key+"岁组的学生有:";

foreach(Studentpin element)

{

Label1.Text+=p.Name.ToString()+",";

}

Label1.Text+="<br>";

}


上面的代码通过使用into子句创建标识,从LINQ查询语句中可以看出,查询后返回的是一个集合变量per而不是p,但是编译能够通过并且能够执行查询,这说明LINQ查询语句将查询的结果填充到了临时标识符对象per中并返回查询集合给result集合变量。这里,还需要说明的是into子句必须以select、group等子句作为结尾子句,否则会抛出异常。示例运行结果如图9-7所示。

figure_0351_0253

图 9-7 into子句示例运行结果

9.2.7 join联接子句

对于熟悉SQL语句的读者,相信对join联接子句并不陌生。join子句用于根据两个或多个表中的列之间的关系,从这些表中查询数据。在LINQ中,同样也可以使用join子句对有关联的数据源或数据对象进行查询。通常,join子句可以实现以下3种联接关系:

❑内部联接,元素的联接关系必须同时满足被联接的两个数据源;

❑分组联接,含有into子句的join子句;

❑左外部联接。

1.内部联接

内部联接要求元素的联接关系必须同时满足被联接的两个数据源,同SQL查询语句中的inner join查询语句相似。示例代码如下所示:


public class Student

{

public int ID;

public int Age;

public string Name;

public Student(int id, int age, string name)

{

this.ID=id;

this.Age=age;

this.Name=name;

}

}

public class Course

{

public int ID;

public string CourseName;

public Course(int id, string courseName)

{

this.ID=id;

this.CourseName=courseName;

}

}


上面代码创建了两个类,其中,Student类用于描述学生,而Course类用于描述学生所选择的课程,它们之间通过ID进行关联。现在,就可以使用List类来创建相关的对象了,并使用LINQ的join联接子句进行联接查询。示例代码如下所示:


List<Student>person=new List<Student>();

person.Add(new Student(1,25,"张三"));

person.Add(new Student(2,26,"张华"));

person.Add(new Student(3,25,"小西"));

person.Add(new Student(4,24,"张军"));

person.Add(new Student(5,26,"张雨"));

List<Course>course=new List<Course>();

course.Add(new Course(1,"ASP.NET"));

course.Add(new Course(2,"C"));

course.Add(new Course(3,"VB"));

var result=frompin person

joincin course on p.ID equals c.ID

select p;

foreach(variin result)

{

Label1.Text+=i.Name.ToString()+"<br>";

}


上面的代码使用join子句进行不同数据源之间关系的创建,其运行结果如图9-8所示。

figure_0353_0254

图 9-8 内部联接示例运行结果

2.分组联接

含有into子句的join子句被分组联接。分组联接产生分层数据结构,它将第一个集合中的每个元素与第二个集合中的一组相关元素进行匹配。在查询结果中,第一个集合中的元素都会出现在查询结果中。如果第一个集合中的元素在第二个集合中找到相关元素,则使用被找到的元素,否则使用空。示例代码如下所示:


List<Student>person=new List<Student>();

person.Add(new Student(1,25,"张三"));

person.Add(new Student(2,26,"张华"));

person.Add(new Student(3,25,"小西"));

person.Add(new Student(4,24,"张军"));

person.Add(new Student(5,26,"张雨"));

List<Course>course=new List<Course>();

course.Add(new Course(1,"ASP.NET"));

course.Add(new Course(2,"C"));course.Add(new Course(3,"VB"));var result=frompin person

joincin course on p.ID equals c.ID into g

select new

{

ID=p.ID,

Name=p.Name,

Age=p.Age,

Courses=g.ToList()

};

foreach(varvin result)

{

Label1.Text+=v.Name+":"

+(v.Courses.Count>0?v.Courses[0].CourseName:"没有选课")

+"</br>";

}


其运行结果如图9-9所示。

figure_0353_0255

图 9-9 分组联接示例运行结果

3.左外部联接

左外部联接与SQL语句中的left join子句比较相似,它将返回第一个集合中的每一个元素,而无论该元素在第二个集合中是否具有相关元素。

需要说明的是,LINQ查询表达式若要执行左外部联接,往往与DefaultIfEmpty()方法和分组联接结合起来使用。如果第一个集合中的元素没有找到相关元素,DefaultIfEmpty()方法可以指定该元素的相关元素的默认元素。

通常,若要生成两个集合的左外部联接,可以分为两步来实现:

1)使用分组联接执行内部联接。

2)在结果集内包含第一个(左)集合的每个元素,即使该元素在右集合中没有匹配的元素也是如此。这是通过对分组联接中的每个匹配元素序列调用DefaultIfEmpty()方法来实现的。如下面的示例代码所示:


List<Student>person=new List<Student>();

person.Add(new Student(1,25,"张三"));

person.Add(new Student(2,26,"张华"));

person.Add(new Student(3,25,"小西"));

person.Add(new Student(4,24,"张军"));

person.Add(new Student(5,26,"张雨"));

List<Course>course=new List<Course>();

course.Add(new Course(1,"ASP.NET"));

course.Add(new Course(2,"C"));

course.Add(new Course(3,"VB"));

var result=frompin person

joincin course on p.ID equals c.ID into g

from pc in g.DefaultIfEmpty()

select new

{

ID=p.ID,

Name=p.Name,

Age=p.Age,

Courses=g.ToList()

};

foreach(varvin result)

{

Label1.Text+=v.Name+":"

+(v.Courses.Count>0?v.Courses[0].CourseName:"没有选课")

+"</br>";

}


其运行结果如图9-10所示。

figure_0354_0256

图 9-10 左外部联接示例运行结果

9.2.8 let临时表达式子句

在查询表达式中,存储子表达式的结果有时很有用,这样可以在随后的子句中使用。可以使用let关键字完成这一工作,该关键字可以创建一个新的范围变量,并且用提供的表达式的结果初始化该变量。一旦用值初始化了该范围变量,它就不能用于存储其他值。但如果该范围变量存储的是可查询的类型,则可以对其进行查询。

换句话讲,可以将let关键字看作是在表达式中创建了一个临时的变量用于保存表达式的结果,但是let子句指定的范围变量的值只能通过初始化操作进行赋值,一旦初始化之后就无法再次进行更改操作。示例代码如下所示:


String[]str={"Thank you so much.",

"I appreciate your kindness.",

"I have no words to thank you."};

var result=from sentence in str

let words=sentence.Split('')//用空格分隔str数组

from word in words

let w=word.ToLower()//把数组元素进行小写操作

where w[0]=='y'

select word;

foreach(varvin result)

{

Label1.Text+=v+"<br>";

}


在上面的代码中,使查询只能对范围变量word调用一次ToLower。如果不使用let,则必须在where子句的每个谓词中调用ToLower。let就相当于一个中转变量,用于临时存储表达式的值。运行结果如图9-11所示。

figure_0355_0257

图 9-11 let子句示例运行结果