[读书笔记]C#学习笔记六: C#3.0Lambda表达式及Linq解析
前言
最早使用到Lambda表达式是因为一个需求:
如果一个数组是:int[] s = new int[]{1,3,5,9,14,16,22};
例如只想要这个数组中小于15的元素然后重新组装成一个数组或者直接让s返回一个新数组该怎么截取?
最开始的想法就是将这个s遍历一遍然后判断下再来重新组装成新的数组.好麻烦是不是? 于是便百度到了一个叫做Lambda的东西, 所以用了之后效果如下:
1 class Program
2 {
3 static void Main(string[] args)
4 {
5 int[] s = new int []{ 1,3,5,9,14,16,22 };
6 var result = from n in s where n < 15 select n;
7 int[] b = result.ToArray();
8 for (int i = 0; i < b.Length; i++)
9 {
10 Console.WriteLine(b[i]);
11 }
12 Console.ReadKey();
13 }
14 }
打印结果如我们所想: 1, 3, 5, 9, 14.
剩下的就是在真实的项目中接触到的, 在这里只是作为举例, 不做细致讲解:
1 var splitTexts = cmbValidationText.Split(new string[] { IncidentConstant.Comma },
StringSplitOptions.RemoveEmptyEntries); 2 if (cmbValidation.Items != null && cmbValidation.Items.Count > 0) 3 { 4 foreach (var splitText in splitTexts) 5 { 6 bool valid = cmbValidation.Items.Any(item => (item != null) && (item.Enabled) &&
(string.Equals(splitText, item.Prefix, StringComparison.OrdinalIgnoreCase))); 7 8 if (!valid) 9 { 10 invalidText += splitText.ToString() + CommaAndBlank; 11 isInvalidTextExist = true; 12 } 13 } 14 } 15 16 var categoryAndCapabilities = capabilities.Select(item => 17 { 18 PRResponseCategory category = null; 19 PRCapability prCapability = provisioningManager.GetPRCapabilityByKey(item.PRCapabilityKey.
GetValueOrDefault()); 20 if (prCapability != null) 21 { 22 category = statusMonitorDao.GetResponseCategoryByKey(prCapability.ResponseCategoryKey.
GetValueOrDefault()); 23 } 24 return new { Category = category, PRCapability = prCapability, Capability = item }; 25 }) 26 .Where(item => (item != null && item.Category != null && item.PRCapability != null)) 27 .OrderBy(item => item.Category.Code) 28 .ThenBy(item => item.PRCapability.AllowNumeric.GetValueOrDefault() ? 1 : 0) 29 .ThenBy(item => item.PRCapability.CapabilityCode) 30 .GroupBy(item => item.PRCapability.ResponseCategoryKey.GetValueOrDefault()) 31 .ToDictionary(grouping => grouping.Key, grouping => grouping.ToList());
这里会不会觉得很神奇? 那么下面就开始Lambda及Linq之旅吧.
1,Linq解析
Linq是Language Integrated Query的缩写, 即"语言集成查询"的意思. 它主要包含4个组件: Linq to Object, Linq to XML, Linq to DataSet 和Linq to Sql.
更多详细内容可以查看一个国外网站: http://www.dotnetperls.com/linq
下面步入正题:
(1),查询表达式
查询表达式是一种使用查询语法表示的表达式,它用于查询和转换来自任意支持LINQ的数据源中的数据。查询表达式使用许多常见的C#语言构造,易读简洁,容易掌握。它由一组类似于SQL或XQuery的声明性语法编写的子句组成。每一个子句可以包含一个或多个C#表达式。这些C#表达式本身也可能是查询表达式或包含查询表达式。
查询表达式必须以from子句开头,以select或group子句结束。第一个from子句和最后一个select子句或group子句之间,可以包含一个活多个where子句、let子句、join子 句、orderby子句和group子句,甚至还可以是from子句。它包括8个基本子句,具体说明如下所示。
●from子句:指定查询操作的数据源和范围变量。
●select子句:指定查询结果的类型和表现形式。
●where子句:指定筛选元素的逻辑条件。
●let子句:引入用来临时保存查询表达式中的字表达式结果的范围变量。
●orderby子句:对查询结果进行排序操作,包括升序和降序。
●group子句:对查询结果进行分组。
●into子句:提供一个临时标识符。join子句、group子句或select子句可以通过该标识符引用查询操作中的中坚结果。
●join子句:连接多个用于查询操作的数据源。
1.1,select,from, where子句:
示例1
下面创建一个查询表达式query,该查询表达式查询arr数组中的每一个元素。
int[]arr =new int[]{0,1,2,3,4,5,6,7,8,9};
分析1
1 class Program 2 { 3 static void Main() 4 { 5 int[] arr = new int[] { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 }; 6 var query = from n in arr 7 select n; 8 foreach (var element in query) 9 Console.WriteLine(element); 10 Console.ReadKey(); 11 } 12 }
示例2
下面创建一个查询表达式query2.该查询表达式查询arr数组中大于6的元素。
1 class Program 2 { 3 static void Main() 4 { 5 int[] arr = new int[]{0,1,2,3,4,5,6,7,8,9}; 6 var query = from n in arr 7 where n >6 8 select n; 9 foreach (var element in query) 10 Console.WriteLine(element); 11 Console.ReadKey(); 12 } 13 }
分析2
变量只是保存查询操作,而不是查询的结果。当查询表达式执行查询操作时,才会计算该查询表达式的结果。以上两个变量的类型都属于集合类型。
示例3
下面创建一个查询表达式query。该查询表达式包含两个from子句,他们分别查询两个独立的数据源;arr1数组和arr2数组。最后,使用select子句计算当前元素的和。
1 class Program 2 { 3 static void Main() 4 { 5 int[] arr1= new int[] {0,1,2,3,4,5,6,7,8,9}; 6 int[] arr2=new int[] {0,1,2,3,4,5,6,7,8,9}; 7 var query = from a in arr1 8 from b in arr2 9 select a +b; 10 11 foreach (var element in query) 12 Console.WriteLine(element); 13 Console.ReadKey(); 14 } 15 }
分析3
包含符合from子句的查询表达式
在查询表达式中,有可能查询表达式的数据源中的每一个元素本身也作为该查询表达式的数据源。那么要查询数据源中的每一个元素中的元素,则需要使用符合from子句。符合from子句类似于嵌套的foreach语句。
1.2,let子句
let子句用来创建一个新的范围变量,它用于存储子表达式的结果。let子句使用编程者提供的表达式的结果初始化该变量。一旦初始化了该范围变量的值,它就不能用于存储其他的值。
示例
下面创建一个查询表达式query。该查询表达式从arr数组中查询为偶数的元素。
1 class Program 2 { 3 static void Main(string[] args) 4 { 5 int[] arr = new int[] {0,1,2,3,4,5,6,7,8,9}; 6 var query = from n in arr 7 let isEven = (n % 2 == 0 ? true : false) 8 where isEven 9 select n; 10 11 foreach (var element in query) 12 Console.WriteLine(element); 13 Console.ReadKey(); 14 } 15 }
分析
"return n%2==0?true:false"表达式判断n元素是否为偶数。如果是,则返回true,否则返回false。“let isEven =return n%2==0?true:false”表达式使用let子句创建新的范围变量isEven,用来保存"return n%2==0?true:false"表达式的结果。"where isEven"表达式使用where子句筛选isEven的值为true的元素。
1.3,orderby子句
orderby子句可使返回的查询结果按升序或者降序排序。升序由关键字ascending指定,而降序由关键字descending指定。
注意:orderby子句默认排序方式为升序。
示例
下面创建一个查询表达式query。该查询表达式从arr数组中查询大于1且小于6的元素,并且按照n元素对查询结果进行降序排序。
1 class Program 2 { 3 static void Main() 4 { 5 int[] arr = new int[]{0,1,2,3,4,5,6,7,8,9}; 6 var query = from n in arr 7 where n>1 && n<6 8 orderby n descending 9 select n ; 10 foreach (var element in query) 11 Console.WriteLine(element); 12 Console.ReadKey(); 13 } 14 }
分析
orderby子句可以包含一个或多个排序表达式,各个排序表达式使用逗号(,)分隔。
1.4, group子句
group子句用来将查询结果分组,并返回一对象序列。这些对象包含零个或更多个与改组的key值匹配的项,还可以使用group子句结束查询表达式。
注意:每一个分组都不是单个元素,而是一个序列(也属于集合)。
示例
下面创建一个查询表达式query。该查询表达式从arr数组中查询大于1且小于6的元素,并且按照n%2表达式的值对查询结果进行分组。
1 class Program 2 { 3 static void Main(string[] args) 4 { 5 int[] arr = new int[] { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 }; 6 var query = from n in arr 7 where n > 1 && n < 6 8 group n by n % 2; 9 10 foreach (var element in query) 11 Console.WriteLine(element); 12 Console.ReadKey(); 13 } 14 }
分析
query查询表达式的结果是一个序列(类型为IEnumerable<IGrouping<int,int>>),该序列的元素类型为IGrouping<int,int>.其实,该查询结果中的元素也是一个序列。
1.5, into子句
下面创建一个查询表达式query。该查询表达式从arr数组中查询大于1且小于6的元素,并且按照n%2表达式的值对查询结果进行分组。该查询表达式的具体说明如下所示:
where n>1 && n<6:指定筛选大于1且小于6的元素。
group n by n%2 into g: 按照n%2表达式的值对查询结果进行分组(0和0一组, 1和1 一组),并使用into子句创建临时标识符g。该临时标识符临时保存分组结果。
from sn in g:从g标识符指定的每一个分组中查询sn元素。
select sn:表示查询sn元素。
1 class Program 2 { 3 static void Main() 4 { 5 int[] arr = new int[]{0,1,2,3,4,5,6,7,8,9}; 6 var query = from n in arr 7 where n>1&& n<6 8 group n by n%2 into g 9 from sn in g 10 select sn; 11 foreach (var element in query) 12 Console.WriteLine(element); 13 Console.ReadKey(); 14 } 15 }
分析
上述查询表达式的查询结果包括4个元素,依次为2、4、3和5
1.6, join子句
oin子句用来连接两个数据源,即设置两个数据源之间的关系。join子句支持以下3种常见联接方式。
内部联接:元素的链接关系 必须同时满足两个数据源,类似于SQL语句中的inner join子句。
分组联接:包含into子句的join子句。
左外部联接:元素的链接关系必须满足联接中的左数据源,类似于SQL语句中的left join子句。
内部联接:join子句的内部联接要求两个数据源都必须存在相同的值,即两个数据源都必须存在满足联接关系的元素。
示例
下面创建一个查询表达式query。该查询表达式使用join子句联接了arra和arrb数组,具体说明如下。
创建arra数组,它包含10个元素(0~9)。
创建arrb数组,它包含5个元素(0、2、4、6和8)。
创建query查询。
from a in arra:从arra数组中选择元素,并表示为a。
where a < 7: 从arra数组中选择小于7的元素
join b in arrb on a equals b: 将arra和arrb数组进行联接,同时满足a和b相等的条件。其中,b元素是arrb数组中的元素。
select a: 选择a元素。
1 class Program 2 { 3 static void Main() 4 { 5 int[] arra = new int[] {0,1,2,3,4,5,6,7,8,9}; 6 int[] arrb = new int[]{0,2,4,6,8}; 7 var query = from a in arra 8 where a <7 9 join b in arrb on a equals b 10 select a; 11 foreach (var element in query) 12 Console.WriteLine(element); 13 Console.ReadKey(); 14 } 15 }
分析
上述查询表达式首先选择小于7的元素,(包括0~6),然后再喝arrb数组进行联接,并获取既包含在{0,1,2,3,4,5,6}集合中,又包含在arrb数组中的元素。最终,查询表达式的结果包含4个元素(0、2、4和6)
分组联接:join子句的分组联接包含into子句的join子句的链接。它将左数据源与右数据源的元素一次匹配。左数据源的所有元素都出现在查询结果中。若在右数据源中找到匹配项,则使用匹配的数据,否则用空表示。
(2),使用Linq to XML查询XML文件
在Linq提出之前, 我们可以使用XPath来查询XML文件, 但是用XPath时必须首先知道XML文件的具体结构, 而使用Linq to XML则不需要知道这些.
而且Linq to XML的代码还更加简洁.
1 class Program 2 { 3 //初始化xml数据 4 private static string xmlString = 5 "<Persons>" + 6 "<Person Id = '1'>" + 7 "<Name>Barry Wang</Name>" + 8 "<Age>18</Age>" + 9 "</Person>" + 10 "<Person Id = '2'>" + 11 "<Name>Tony Jia</Name>" + 12 "<Age>20</Age>" + 13 "</Person>" + 14 "<Person Id = '3'>" + 15 "<Name>Anson Shen</Name>" + 16 "<Age>19</Age>" + 17 "</Person>" + 18 "</Persons>"; 19 20 21 static void Main(string[] args) 22 { 23 Console.WriteLine("使用Linq方法来对XML文件查询, 查询结果是: "); 24 UsingLinqLinqToXmlQuery(); 25 Console.ReadKey(); 26 } 27 28 //使用Linq来对XML文件进行查询 29 private static void UsingLinqLinqToXmlQuery() 30 { 31 //导入XML文件 32 XElement xmlDoc = XElement.Parse(xmlString); 33 34 //创建查询, 获取姓名为"李四"的元素 35 var queryResults = from element in xmlDoc.Elements("Person") 36 where element.Element("Name").Value == "Barry Wang" 37 select element; 38 39 //输出查询结果 40 foreach (var xele in queryResults) 41 { 42 Console.WriteLine("姓名为: " + xele.Element("Name").Value + "Id为: " + xele.Attribute("Id").Value); 43 } 44 } 45 }
Linq to DataSet其实都和Linq to Object 类似, 这里就不在讲解了.更多内容在以下两个链接:
MSDN之Linq讲解
Linq操作合集
2,Lambda表达式
Lambda表达式可以理解为一个匿名方法, 它可以包含表达式和语句, 并且用于创建委托或转换表达式树.
在使用Lambda表示式时, 都会使用"=>"运算符(读作goes to), 该运算符的左边是匿名方法的输入参数, 右边则是表达式或语句块.
这里主要列举下Linq和Lambda表达式的一些区别:
LINQ的书写格式如下:
from 临时变量 in 集合对象或数据库对象
where 条件表达式
[order by条件]
select 临时变量中被查询的值
[group by 条件]
Lambda表达式的书写格式如下:
(参数列表) => 表达式或者语句块
其中:参数个数:可以有多个参数,一个参数,或者无参数。
参数类型:可以隐式或者显式定义。
表达式或者语句块:这部分就是我们平常写函数的实现部分(函数体)。
1.查询全部
查询Student表的所有记录。
1 select * from student 2 Linq: 3 from s in Students 4 select s 5 Lambda: 6 Students.Select( s => s)
2 按条件查询全部:
查询Student表中的所有记录的Sname、Ssex和Class列。
1 select sname,ssex,class from student 2 Linq: 3 from s in Students 4 select new { 5 s.SNAME, 6 s.SSEX, 7 s.CLASS 8 } 9 Lambda: 10 Students.Select( s => new { 11 SNAME = s.SNAME,SSEX = s.SSEX,CLASS = s.CLASS 12 })
3.distinct 去掉重复的
查询教师所有的单位即不重复的Depart列。
1 select distinct depart from teacher 2 Linq: 3 from t in Teachers.Distinct() 4 select t.DEPART 5 Lambda: 6 Teachers.Distinct().Select( t => t.DEPART)
4.连接查询 between and
查询Score表中成绩在60到80之间的所有记录。
1 select * from score where degree between 60 and 80 2 Linq: 3 from s in Scores 4 where s.DEGREE >= 60 && s.DEGREE < 80 5 select s 6 Lambda: 7 Scores.Where( 8 s => ( 9 s.DEGREE >= 60 && s.DEGREE < 80 10 ) 11 )
5.在范围内筛选 In
1 select * from score where degree in (85,86,88) 2 Linq: 3 from s in Scores 4 where ( 5 new decimal[]{85,86,88} 6 ).Contains(s.DEGREE) 7 select s 8 Lambda: 9 Scores.Where( s => new Decimal[] {85,86,88}.Contains(s.DEGREE))
6.or 条件过滤
查询Student表中"95031"班或性别为"女"的同学记录。
1 select * from student where class ='95031' or ssex= N'女' 2 Linq: 3 from s in Students 4 where s.CLASS == "95031" 5 || s.CLASS == "女" 6 select s 7 Lambda: 8 Students.Where(s => ( s.CLASS == "95031" || s.CLASS == "女"))
7.排序
以Class降序查询Student表的所有记录。
1 select * from student order by Class DESC 2 Linq: 3 from s in Students 4 orderby s.CLASS descending 5 select s 6 Lambda: 7 Students.OrderByDescending(s => s.CLASS)
8.count()行数查询
1 select count(*) from student where class = '95031' 2 Linq: 3 ( from s in Students 4 where s.CLASS == "95031" 5 select s 6 ).Count() 7 Lambda: 8 Students.Where( s => s.CLASS == "95031" ) 9 .Select( s => s) 10 .Count()
9.avg()平均
查询'3-105'号课程的平均分。
1 select avg(degree) from score where cno = '3-105' 2 Linq: 3 ( 4 from s in Scores 5 where s.CNO == "3-105" 6 select s.DEGREE 7 ).Average() 8 Lambda: 9 Scores.Where( s => s.CNO == "3-105") 10 .Select( s => s.DEGREE).Average()
10.子查询
查询Score表中的最高分的学生学号和课程号。
1 select distinct s.Sno,c.Cno from student as s,course as c ,score as sc 2 where s.sno=(select sno from score where degree = (select max(degree) from score)) 3 and c.cno = (select cno from score where degree = (select max(degree) from score)) 4 Linq: 5 ( 6 from s in Students 7 from c in Courses 8 from sc in Scores 9 let maxDegree = (from sss in Scores 10 select sss.DEGREE 11 ).Max() 12 let sno = (from ss in Scores 13 where ss.DEGREE == maxDegree 14 select ss.SNO).Single().ToString() 15 let cno = (from ssss in Scores 16 where ssss.DEGREE == maxDegree 17 select ssss.CNO).Single().ToString() 18 where s.SNO == sno && c.CNO == cno 19 select new { 20 s.SNO, 21 c.CNO 22 } 23 ).Distinct()
11.分组 过滤
查询Score表中至少有5名学生选修的并以3开头的课程的平均分数。
1 select avg(degree) from score where cno like '3%' group by Cno having count(*)>=5 2 Linq: 3 from s in Scores 4 where s.CNO.StartsWith("3") 5 group s by s.CNO 6 into cc 7 where cc.Count() >= 5 8 select cc.Average( c => c.DEGREE) 9 Lambda: 10 Scores.Where( s => s.CNO.StartsWith("3") ) 11 .GroupBy( s => s.CNO ) 12 .Where( cc => ( cc.Count() >= 5) ) 13 .Select( cc => cc.Average( c => c.DEGREE) ) 14 Linq: SqlMethod 15 like也可以这样写: 16 s.CNO.StartsWith("3") or SqlMethods.Like(s.CNO,"%3")
12.分组
查询Score表中至少有5名学生选修的并以3开头的课程的平均分数。
1 select avg(degree) from score where cno like '3%' group by Cno having count(*)>=5 2 Linq: 3 from s in Scores 4 where s.CNO.StartsWith("3") 5 group s by s.CNO 6 into cc 7 where cc.Count() >= 5 8 select cc.Average( c => c.DEGREE) 9 Lambda: 10 Scores.Where( s => s.CNO.StartsWith("3") ) 11 .GroupBy( s => s.CNO ) 12 .Where( cc => ( cc.Count() >= 5) ) 13 .Select( cc => cc.Average( c => c.DEGREE) ) 14 Linq: SqlMethod 15 like也可以这样写: 16 s.CNO.StartsWith("3") or SqlMethods.Like(s.CNO,"%3")
13. 多表查询
1 select sc.sno,c.cname,sc.degree from course as c,score as sc where c.cno = sc.cno 2 Linq: 3 from c in Courses 4 join sc in Scores 5 on c.CNO equals sc.CNO 6 select new 7 { 8 sc.SNO,c.CNAME,sc.DEGREE 9 } 10 Lambda: 11 Courses.Join ( Scores, c => c.CNO, 12 sc => sc.CNO, 13 (c, sc) => new 14 { 15 SNO = sc.SNO, 16 CNAME = c.CNAME, 17 DEGREE = sc.DEGREE 18 })
14,关联多条件查询
感谢@浪子哥 给的建议, 现在加上两张表的关联多条件查询, 只有Linq和Lambda表达式
今天自己又参考园里大神的一些帖子自己写了一个两张表关联查询的Linq及Lambda表达式的Demo, 大家可以看下.
1 class Program 2 { 3 static void Main() 4 { 5 DataTable tableA = new DataTable(); 6 tableA.Columns.Add("Name", typeof(string)); 7 tableA.Columns.Add("Age", typeof(int)); 8 tableA.Columns.Add("Score", typeof(int)); 9 10 DataTable tableB = new DataTable(); 11 tableB.Columns.Add("Name", typeof(string)); 12 tableB.Columns.Add("Age", typeof(int)); 13 tableB.Columns.Add("Score", typeof(int)); 14 15 tableA.Rows.Add("Barry", 12, 60); 16 tableA.Rows.Add("Tony", 24, 70); 17 tableA.Rows.Add("Tom", 22, 80); 18 tableA.Rows.Add("Brad", 25, 90); 19 20 tableB.Rows.Add("Kitty", 12, 65); 21 tableB.Rows.Add("Tom", 27, 75); 22 tableB.Rows.Add("Jhon", 22, 85); 23 tableB.Rows.Add("Brad", 21, 95); 24 25 //单表查询 26 var singleQuery = tableA.AsEnumerable().Where(stu => stu.Field<int>("Age") > 20); 27 foreach (var item in singleQuery) 28 { 29 Console.WriteLine("Case 1 query result: Age {0}, Name {1}", item.Field<int>("Age"),
item.Field<string>("Name").ToString()); 30 } 31 32 //Linq:两张表的关联查询 33 var doubleQuery = from a in tableA.AsEnumerable() 34 from b in tableB.AsEnumerable() 35 where a.Field<string>("Name") == b.Field<string>("Name") && 36 a.Field<int>("Age") != b.Field<int>("Age") 37 orderby a.Field<string>("Name"), a.Field<int>("Score") 38 select new 39 { 40 Name = a.Field<string>("Name"), 41 A_Age = a.Field<int>("Age"), 42 B_Age = b.Field<int>("Age") 43 }; 44 foreach (var item in doubleQuery) 45 { 46 Console.WriteLine("Case 2 query result: Name {0}, tableA_Age {1}, tableB_Age {2}",
item.Name, item.A_Age, item.B_Age); 47 } 48 49 //Lambda:两张表的关联查询 50 var query = tableA.AsEnumerable() 51 .Join(tableB.AsEnumerable(), 52 a => a.Field<string>("Name"), 53 b => b.Field<string>("Name"), 54 (a, b) => new 55 { 56 a = a, 57 b = b 58 }) 59 .Where(c => (c.a.Field<int>("Age") != c.b.Field<int>("Age"))) 60 .OrderBy(d => d.a.Field<string>("Name")) 61 .ThenBy(e => e.a.Field<int>("Score")) 62 .Select(f => new 63 { 64 Name = f.a.Field<string>("Name"), 65 A_Age = f.a.Field<int>("Age"), 66 B_Age = f.b.Field<int>("Age"), 67 A_Score = f.a.Field<int>("Score"), 68 B_Score = f.a.Field<int>("Score") 69 }); 70 foreach (var item in query) 71 { 72 Console.WriteLine("Case 3 query result: Name {0}, tableA_Age {1}, tableB_Age {2}, tableA_Score {3},
tableB_Score {4}",item.Name, item.A_Age, item.B_Age, item.A_Score, item.B_Score);
73 } 74 Console.ReadKey(); 75 } 76 }
解析:
首先可以看出来, Lambda表达式对于这种多表多条件的查询写法的易读性明显没有Linq高, 所以 还是建议用Linq去写. 运行结果如下图: