# LINQ ## 什么是LIQN > 背景 > > 查询是一种从数据源检索数据的表达式。 查询通常用专门的查询语言来表示。 随着时间的推移,人们已经为各种数据源开发了不同的语言;例如,用于关系数据库的 SQL 和用于 XML 的 XQuery。 因此,开发人员对于他们必须支持的每种数据源或数据格式,都不得不学习一种新的查询语言。 > > 为了应对这种情况,LINQ应运而生。 LINQ,语言集成查询,全称是Language Integrated Query,是微软的一项技术,新增一种自然查询的SQL语法到 .NET Framework的编程语言中。 LINQ 通过提供处理各种数据源和数据格式的数据的一致模型,可以使用相同的基本编码模式来查询和转换 XML 文档、SQL 数据库、`ADO.NET` 数据集、.NET 集合中的数据。 ## LINQ查询操作的三个部分 > 所有的LINQ查询操作都由三个不同的操作组成。 - 获取数据源 - 创建查询 - 执行查询 下面代码演示了三个不同的操作 ```csharp class IntroToLINQ { static void Main() { // The Three Parts of a LINQ Query: // 1. Data source. int[] numbers = new int[7] { 0, 1, 2, 3, 4, 5, 6 }; // 2. Query creation. // numQuery is an IEnumerable var numQuery = from num in numbers where (num % 2) == 0 select num; // 3. Query execution. foreach (int num in numQuery) { Console.Write("{0,1} ", num); } } } ``` 通过上述代码我们可以看出,在LINQ中,查询的执行不同于查询本身,换句话说就是仅通过创建查询变量是不会检索到任何数据的。 ### 数据源 支持 IEnumerable 或派生接口(如泛型 IQueryable)的类型称为可查询类型。 上例中,数据源是一个数组,因此它隐式支持泛型 `IEnumerable` 接口。这一事实意味着该数据源可以用 LINQ 进行查询。 查询在 foreach 语句中执行,且 foreach 需要 IEnumerable 或 IEnumerable。 ### 查询 查询指定要从数据源中检索的信息。 查询还可以指定在返回这些信息之前如何对其进行排序、分组和结构化。 查询存储在查询变量中,并用查询表达式进行初始化。 为使编写查询的工作变得更加容易,C# 引入了新的查询语法。 上一个示例中的查询从整数数组中返回所有偶数。 该查询表达式包含三个子句:from、where 和 select。 - from 子句指定数据源 - where 子句应用筛选器 - select 子句指定返回的元素的类型。 需要注意的是,在 LINQ 中,查询变量本身不执行任何操作并且不返回任何数据。 它只是存储在以后某个时刻执行查询时为生成结果而必需的信息。 ### 查询执行 #### 延迟执行 如前所述,查询变量本身只存储查询命令。 查询的实际执行将推迟到在 foreach 语句中循环访问查询变量之后进行。 此概念称为延迟执行。 `foreach` 语句也是检索查询结果的地方。 例如,在上一个查询中,迭代变量 num 保存了返回的序列中的每个值(一次保存一个值)。 #### 强制立即执行 对一系列源元素执行聚合函数的查询必须首先循环访问这些元素。 `Count`、`Max`、`Average` 和 `First` 就属于此类查询。 这些类型的查询返回单个值,而不是 IEnumerable 集合。 ```csharp var evenNumQuery = from num in numbers where (num % 2) == 0 select num; int evenNumCount = evenNumQuery.Count(); ``` 要强制立即执行任何查询并缓存其结果,可调用 ToList 或 ToArray 方法。 ```csharp List numQuery2 = (from num in numbers where (num % 2) == 0 select num).ToList(); // or like this: // numQuery3 is still an int[] var numQuery3 = (from num in numbers where (num % 2) == 0 select num).ToArray(); ``` 此外,还可以通过在紧跟查询表达式之后的位置放置一个 foreach 循环来强制执行查询。 但是,通过调用 ToList 或 ToArray,可以将所有数据缓存在单个集合对象中。 ## 基本的LINQ查询操作 本节简要介绍了 LINQ 查询表达式和一些在查询中执行的典型操作。 ### 准备测试用的数据 ```csharp _dtDemo.Columns.Add("NO",typeof(string)); _dtDemo.Columns.Add("Name", typeof(string)); _dtDemo.Columns.Add("BirthDay", typeof(DateTime)); _dtDemo.Columns.Add("Age", typeof(int)); _dtDemo.Columns.Add("Address", typeof(string)); _dtDemo.Columns.Add("IsCn", typeof(bool)); _dtDemo.Columns.Add("Sex", typeof(string)); _dtDemo.Rows.Add("1","张三","2010-10-11",7,"山东淄博淄川",false,"男"); _dtDemo.Rows.Add("2", "李四", "2000-4-11", 18, "山东潍坊青州", true, "女"); _dtDemo.Rows.Add("3", "王五", "1990-5-6", 27, "山东淄博临淄", true, "男"); _dtDemo.Rows.Add("4", "马六", "2015-6-11", 2, "山东东营广饶", false, "男"); _dtDemo.Rows.Add("5", "赵七", "2005-9-11", 12, "山东淄博淄川", false, "女"); _dtDemo1.Columns.Add("NO", typeof(string)); _dtDemo1.Columns.Add("Interest", typeof(string)); _dtDemo1.Rows.Add("1", "天文"); _dtDemo1.Rows.Add("1", "影视"); _dtDemo1.Rows.Add("2", "影视"); _dtDemo1.Rows.Add("2", "时尚"); _dtDemo1.Rows.Add("3", "运动"); _dtDemo1.Rows.Add("3", "旅游"); _dtDemo1.Rows.Add("4", "美食"); _dtDemo1.Rows.Add("4", "旅游"); _dtDemo1.Rows.Add("6", "游戏"); _dtDemo1.Rows.Add("6", "科技"); ``` ### LINQ基本语法之select >和SQL命令中的select作用相似但位置不同,查询表达式中的select及所接子句是放在表达式最后并把子句中的变量也就是结果返回回来。 **返回DataRow集合** ```csharp var result = from r in _dtDemo.AsEnumerable() select r; ``` 上面的语法中的r指代DataTable中的一行。 **返回DataRow中部分列集合** ```csharp var result = from r in _dtDemo.AsEnumerable() select new { NO = r.Field("NO"), Name = r.Field("Name") }; ``` 上面的语法中的`r.Field(“列名”)`是用来获取DataRow中指定列的值;`new { NO=r.Field("NO"), Name= r.Field("Name") }`是声明了一个匿名对象,里面有两个属性分别是NO和Name。这个语法与下面的语法是等效的: ```csharp var result = _dtDemo.AsEnumerable().Select(x => new { NO = x.Field("NO"), Name = x.Field("Name") }); ``` **返回人员姓名和是否成年,如果成年返回“成年”否则返回“未成年”** ```csharp var result = from r in _dtDemo.AsEnumerable() select new { Name = r.Field("Name"), Cn = r.Field("IsCn") ? "成年" : "未成年" }; ``` 上面语法中,返回结果集是采用的三元表达式的形式,类似于SQL中的 `case when condition then else`。上面的语法等价于: ```csharp var result = _dtDemo.AsEnumerable().Select(x=> new { Name = x.Field("Name"), Cn = x.Field("IsCn") ? "成年" : "未成年" }); ``` ### LINQ语法之嵌套查询形式 返回的结果集中,有属性也一个集合类,可以采用嵌套查询的形式进行 ```csharp var result = from r in _dtDemo.AsEnumerable() select new { Name = r.Field("Name"), Interest = from r1 in _dtDemo1.AsEnumerable() where r1.Field("NO") == r.Field("NO") select r1.Field("Interest") }; ``` 上面的语法在查询结果集中的Interest属性并不是一个具体的值,而是一个集合,通过嵌套子查询获取到的集合。上面的语法等价于: ```csharp var result = _dtDemo.AsEnumerable().Select(x => new { Name = x.Field("Name"), Interest = _dtDemo1.AsEnumerable().Where(y => y.Field("NO").Equals(x.Field("NO"))).Select(y => y.Field("Interest")) }); ``` ### LINQ语法之Distinct 筛选字段中不重复的值,用于查询不重复的结果集。 **返回不重复的兴趣爱好** ```csharp var result = (from r in _dtDemo1.AsEnumerable() select r.Field("Interest")).Distinct(); ``` 上面语法调用了IEnumerable对象的Distinct方法,获取不重复的记录。上述语法等价于: ```csharp var result = _dtDemo1.AsEnumerable().Select(x => x.Field("Interest")).Distinct(); ``` ### LINQ语法之where where关键字的适用场景:过滤数据 说明:与SQL的where关键字的作用相似,都是起到范围限定,也就是数据过滤的作用。判断条件就是跟在where关键字后面的字句。 **只有一个条件示例** 返回未成年的人员的姓名 ```csharp var result = from r in _dtDemo.AsEnumerable() where !r.Field("IsCn") select r.Field("Name"); ``` 上面的语法中的`where !r.Field("IsCn")`即为过滤条件,与下面的语法是等效的: ```csharp var result = _dtDemo.AsEnumerable().Where(x => !x.Field("IsCn")).Select(x=>x.Field("Name")); ``` **多个条件示例** 返回未成年并且性别是女的人员的姓名 ```csharp var result = from r in _dtDemo.AsEnumerable() where !r.Field("IsCn") && r.Field("Sex") == "女" select r.Field("Name"); ``` 注意上述语法的与关键字和sql语法是不一样的,sql中采用的是and,这边采用的是&&,与下面语法等效: ```csharp var result = _dtDemo.AsEnumerable().Where(x => !x.Field("IsCn") && x.Field("Sex") == "女").Select(x => x.Field("Name")); ``` 返回未成年或者是女的人员的姓名 ```csharp var result = from r in _dtDemo.AsEnumerable() where !r.Field("IsCn") || r.Field("Sex") == "女" select r.Field("Name"); ``` 注意上述语法的与关键字和sql语法是不一样的,sql中采用的是or,这边采用的是||,与下面语法等效: ```csharp var result = _dtDemo.AsEnumerable().Where(x => !x.Field("IsCn") || x.Field("Sex") == "女").Select(x => x.Field("Name")); ``` **Where的变种First** First方法用于返回集合中的第一行数据,其本质类似于SQL语句中的top 1 获取性别是女第一个人的姓名 ```csharp var result = _dtDemo.AsEnumerable().First(x => x.Field("Sex") == "女")["Name"]; ``` ### LINQ语法之Count/Sum/Min/Max/Avg **Count** 返回集合中元素的个数,int类型。 获取数据表的个数 ```csharp var result = _dtDemo.AsEnumerable().Count(); ``` 获取性别女的个数 ```csharp var result = (from r in _dtDemo.AsEnumerable() where r.Field("Sex") == "女" select r).Count(); ``` 上面语法也可以写成: ```csharp var result = _dtDemo.AsEnumerable().Count(x => x.Field("Sex") == "女"); ``` **LongCount** 返回集合中元素的个数,long类型。当集合中元素的个数过多时可以采用。用法同Count一致。 **Sum/Average** 返回集合中数值类型元素之和/平均值。 获取所有人的年龄之和 ```csharp var result = (from r in _dtDemo.AsEnumerable() select r.Field("Age") ).Sum(); ``` 上面语法等效于: ```csharp var result = _dtDemo.AsEnumerable().Sum(x => x.Field("Age")); ``` 获取所有未成年人的年龄之和 ```csharp var result = (from r in _dtDemo.AsEnumerable() where !r.Field("IsCn") select r.Field("Age") ).Sum(); ``` 上面语法等效于: ```csharp var result = _dtDemo.AsEnumerable().Where(x => !x.Field("IsCn")).Sum(x => x.Field("Age")); ``` 分别获取男和女的年龄之和 ```csharp var result = from r in _dtDemo.AsEnumerable() group r by r.Field("Sex") into g select new { Sex = g.Key, Sum = g.Sum(x => x.Field("Age")) }; 上面的语法等效于: ```csharp var result = _dtDemo.AsEnumerable().GroupBy(x => x.Field("Sex")).Select(g=>new { Sex = g.Key, Sum = g.Sum(y => y.Field("Age")) }); ``` **Max/Min** 返回集合中元素的最大值/最小值,用法和Sum的类似 返回年龄的最大值 ```csharp var result = (from r in _dtDemo.AsEnumerable() select r.Field("Age") ).Max(); ``` 上面的语法等效于: ```csharp var result = _dtDemo.AsEnumerable().Max(x => x.Field("Age")); ``` 返回未成年人的年龄最大值 ```csharp var result = (from r in _dtDemo.AsEnumerable() where !r.Field("IsCn") select r.Field("Age") ).Max(); ``` 上面的语法等效于: ```csharp var result = _dtDemo.AsEnumerable().Where(x => !x.Field("IsCn")).Max(x => x.Field("Age")); ``` 分别返回男和女的年龄最大值 ```csharp var result = from r in _dtDemo.AsEnumerable() group r by r.Field("Sex") into g select new { Sex = g.Key, Max = g.Max(x => x.Field("Age")) }; ``` 上面语法等效于: ```csharp var result = _dtDemo.AsEnumerable().GroupBy(x => x.Field("Sex")).Select(g => new { Sex = g.Key, Max = g.Max(x => x.Field("Age")) }); ``` ### Linq语法之Join **等值连接** 获取人员及其兴趣 ```csharp var result = from r in _dtDemo.AsEnumerable() join r1 in _dtDemo1.AsEnumerable() on r.Field("NO") equals r1.Field("NO") select new { Name = r.Field("Name"), Interest = r1.Field("Interest") }; ``` 上面的语法等价于: ```csharp var result = _dtDemo.AsEnumerable().Join(_dtDemo1.AsEnumerable(), x => x.Field("NO"), y => y.Field("NO"), (x, y) => new { Name = x.Field("Name"), Interest = y.Field("Interest") }); ``` **左外连接** 获取人员及其兴趣 ```csharp var result = from r in _dtDemo.AsEnumerable() join r1 in _dtDemo1.AsEnumerable() on r.Field("NO") equals r1.Field("NO") into temp from r2 in temp.DefaultIfEmpty() select new { Name = r.Field("Name"), Interest = r2==null?null:r2.Field("Interest") }; ``` 上面的语法中以_dtDemo为左表,以_dtDemo1为右表,当右表中的值为空时,用null值填充。Join的结果放到临时表temp中,然后通过调用临时表的DefaultIfEmpty方法左外连接的集合(如果不用该方法,则获取不到左表有,右表没有的数据)。 ### Linq语法之Order By 对查询出的数据进行排序 **简单排序** 按照生日升序 ```csharp var result = from r in _dtDemo.AsEnumerable() orderby r.Field("BirthDay") select new { Name = r.Field("Name"), BirthDay = r.Field("BirthDay") }; ``` 上面的语法等价于: ```csharp var result = _dtDemo.AsEnumerable().OrderBy(x => x.Field("BirthDay")).Select(x => new { Name = x.Field("Name"), BirthDay = x.Field("BirthDay") }); ``` 按照生日降序 ```csharp var result = from r in _dtDemo.AsEnumerable() orderby r.Field("BirthDay") descending select new { Name = r.Field("Name"), BirthDay = r.Field("BirthDay") }; ``` 上面的语法等价于: ```csharp var result = _dtDemo.AsEnumerable().OrderByDescending (x => x.Field("BirthDay")).Select(x => new { Name = x.Field("Name"), BirthDay = x.Field("BirthDay") }); ``` **带条件排序** 男的按生日排序 ```csharp var result = from r in _dtDemo.AsEnumerable() where r.Field("Sex") == "男" orderby r.Field("BirthDay") select new { Name = r.Field("Name"), BirthDay = r.Field("BirthDay") }; ``` 上面的语法等效于: ```csharp var result = _dtDemo.AsEnumerable().Where(x=>x.Field("Sex")=="男").OrderBy(x => x.Field("BirthDay")).Select(x => new { Name = x.Field("Name"), BirthDay = x.Field("BirthDay") }); ``` **复合排序** 按照性别、年龄升序排序 ```csharp var result = from r in _dtDemo.AsEnumerable() orderby r.Field("Sex"), r.Field("Age") select new { Name = r.Field("Name"), Sex = r.Field("Sex"), Age = r.Field("Age") }; ``` 上面语法等效于: ```csharp var result = _dtDemo.AsEnumerable().OrderBy(x => x.Field("Sex")).ThenBy(x => x.Field("Age")).Select(x => new { Name = x.Field("Name"), Sex = x.Field("Sex"), Age = x.Field("Age") }); ``` 该语法采用了新的排序方法ThenBy,也可以连续使用OrderBy进行复合排序,但是这个时候排序列的先后顺序需要颠倒过来,如下: ```csharp var result = _dtDemo.AsEnumerable().OrderBy(x => x.Field("Age")).OrderBy(x => x.Field("Sex")).Select(x => new { Name = x.Field("Name"), Sex = x.Field("Sex"), Age = x.Field("Age") }); ``` ### Linq语法之GroupBy/Where **简单语法** 按照性别分组 ```csharp var result = from r in _dtDemo.AsEnumerable() group r by r.Field("Sex") into g select g; ``` 上面的语法分组后把结果保存到g中,g是一个以性别为键,以复合条件的DataRow集合为值得集合。集合g有以下方法: - Max:最大值 - Min:最小值 - Average:平均值 - Sum:求和 - Count:计数 该语法等效于: ```csharp var result = _dtDemo.AsEnumerable().GroupBy(x => x.Field("Sex")); ``` **多列分组** 按照性别和是否成年分组 ```csharp var result = from r in _dtDemo.AsEnumerable() group r by new { Sex = r.Field("Sex"), IsCn = r.Field("IsCn") } into g select g; ``` 上面语法中,g的键不在是一个具体的值,而是一个对象,对象中有两个属性,分别为Sex和IsCn。等效语法为: ```csharp var result = _dtDemo.AsEnumerable().GroupBy(x => new { Sex = x.Field("Sex"), IsCn = x.Field("IsCn") }); ``` **条件分组** 按照年龄是否大于15分组,大于15的一个组,不大于的一个组 ```csharp var result = from r in _dtDemo.AsEnumerable() group r by new { Age = r.Field("Age")>15 } into g select g; ``` 上面语法等效于: ```csharp var result = _dtDemo.AsEnumerable().GroupBy(x => new { Age = x.Field("Age")>15 }); ``` **分组最大值/最小值/平均值/和** 按照性别分组获取年龄的最大值/最小值/平均值/和 ```csharp var result = from r in _dtDemo.AsEnumerable() group r by r.Field("Sex") into temp select new { Sex = temp.Key, Max = temp.Max(x => x.Field("Age")) }; ``` 最大值、最小值、平均值和求和的语法出了方法名字不一样,语法完全一致。上面的语法等效于: ```csharp var result = _dtDemo.AsEnumerable().GroupBy(x => x.Field("Sex")).Select(g => new { Sex = g.Key, Max = g.Max(x => x.Field("Age")) }); ``` **分组计数** 按照性别分组,并且获取各自的数据 ```csharp var result = from r in _dtDemo.AsEnumerable() group r by r.Field("Sex") into temp select new { Sex = temp.Key, Count = temp.Count() }; ``` 上面的语法等效于: ```csharp var result = _dtDemo.AsEnumerable().GroupBy(x => x.Field("Sex")).Select(g => new { Sex = g.Key, Count = g.Count () }); ``` **分组where** 通过写where条件,我们可以对分组进行过滤,例如获取地址重复的人员 ```csharp var result = from r in _dtDemo.AsEnumerable() group r by r.Field("Address") into g where g.Count() > 1 select new { Address = g.Key, Person = g }; ``` 上面的语法按照地址分组后如果存在重复的地址则其Count会大于1,通过where子句我们就可以把存在重复地址的集合给过滤出来。上面的语法等效于: ```csharp var result = _dtDemo.AsEnumerable().GroupBy(x => x.Field("Address")).Where(g => g.Count() > 1).Select(g => new { Address = g.Key, Person = g }); ``` **多表联立分组** 有些时候我们在进行分组统计的时候不仅仅是通过一个表,而是通过多个表联合起来进行分组统计,例如我们想获取每种兴趣的人数 ```csharp var result = from r in _dtDemo.AsEnumerable() join r1 in _dtDemo1.AsEnumerable() on r.Field("NO") equals r1.Field("NO") into temp from t in temp group t by t.Field("Interest") into g select new { Inserest = g.Key, Count = g.Count() }; ``` 上面的语法把联立查询的结果先放到一个临时的范围变量temp中,然后对temp进行分组最终获取到我们想要的结果,上面的语法等效于 ```csharp var result = _dtDemo.AsEnumerable().Join(_dtDemo1.AsEnumerable(), r => r.Field("NO"), r1 => r1.Field("NO"), (r, r1) => new { Name = r.Field("Name"), Interest = r1.Field("Interest") }).GroupBy(x => x.Interest).Select(g => new { Inserest = g.Key, Count = g.Count() }); ``` ### Linq语法之调用本地方法 在一些查询中需要对一些字段进行加工处理,但是在linq语法中不好实现,可以通过调用本地方法的方式实现 返回人员及兴趣爱好,兴趣爱好转化为英文显示 ```csharp var result = from r in _dtDemo.AsEnumerable() from r1 in _dtDemo1.AsEnumerable() where r.Field("NO") == r1.Field("NO") select new { Name = r.Field("Name"), Interest = ConvertLanguage(r1.Field("Interest"))}; private string ConvertLanguage(string interest) { switch (interest) { case "天文": return "Astronomy"; case "影视": return "Movies"; case "时尚": return "Fashion"; case "运动": return "Sports"; case "旅游": return "Tourism"; case "美食": return "Delicacy"; case "游戏": return "Game"; case "科技": return "Science"; default: return interest; } } ``` 上面的语法中,获取Interest时调用本地方法把中文的兴趣转化为了英文的兴趣。上面的语法等效于: ```csharp var result = _dtDemo.AsEnumerable().Join(_dtDemo1.AsEnumerable(), x => x.Field("NO"), y => y.Field("NO"), (x, y) => new { Name = x.Field("Name"), Interest = ConvertLanguage(y.Field("Interest")) }); private string ConvertLanguage(string interest) { switch (interest) { case "天文": return "Astronomy"; case "影视": return "Movies"; case "时尚": return "Fashion"; case "运动": return "Sports"; case "旅游": return "Tourism"; case "美食": return "Delicacy"; case "游戏": return "Game"; case "科技": return "Science"; default: return interest; } } ```