一、什么是Linq
Linq是语言集成查询(Language Integrated Query)的简称,是visual Studio 2008和.NET Framework 3.5版本中一项突破性的创新,它在对象领域和数据领域之间架起了一座桥梁。
Linq支持各种数据源:
1、ADO.NET DataSet
2、XML文档
3、SQL Server数据库
4、支持IEnumerable或泛型IEnumerable(T)接口的任意对象集合
5、更多。。。
二、Linq的优点
- 传统的SQL查询
select FirstName,LastName,* from Customers
where city = 'Shanghai'
order by district
简单的字符串表示,没有编译时类型检查,没有IDE的智能感知支持。
以上例子只是针对SQL,针对不同的数据源,例如XML文档、各种WEB服务等我们还要学习不同的查询方法。
- Linq查询示例
完全类型检查和IDE智能感知支持。
三、Linq查询的步骤
所有的Linq查询操作都由以下三个不同的操作组成:
- 获得数据源
- 创建查询
- 执行查询
数据源
- 想要使用Linq进行查询,数据源必须支持IEnumerable或IEnumerable(T)泛型接口或派生接口(如泛型的IQueryable(T)接口)。
查询
- 查询指定要从数据源中检索的信息
- 查询还可以指定在返回这些信息之前如何对其进行排序、分组和结构化
- 查询存储在查询变量中,并用查询表达式进行初始化。为使编写查询的工作更加容易,C#引入了新的查询语法
查询执行
- 查询变量本身只是存储查询命令。实际的查询执行会延迟在foreach语句中循环访问变量时发生。此概念称为“延迟执行”。
- 强制立即执行,可以通过以下两个方法,使得Linq立即执行查询
执行聚合函数(Count、Max、Average、First)。
调用ToList(<T>Source)或ToArray(<T>Source)方法缓存结果。
四、查询基本操作
from子句
用于获取数据源
var queryAllCustomers=
from cust in Customers
select cust;
- 查询表达式必须以from子句开头
- 例子中cust是范围变量,范围变量类似于foreach循环中的迭代变量,但在查询表达式中,实际上不发生迭代。执行查询时,范围变量将用作对Customers中的每个后续元素的引用。因为编译器可以推断cust的类型,所以不必显示指定此类型。
- Customers是数据源,实现了IEnumerable或IEnumerable(T)或其派生接口的。
复合from子句
在某些情况下,原序列中的每个元素本身可能是序列,也可能包含序列。用于访问某个数据源中的内部集合。
需求:查询出成绩有90分以上的学生,得到他们的名字和成绩
//数据源
IList<Student> students = new List<Student>
{
new Student{ Name="Kevin", Score=new List<int>{89,93,88,78}},
new Student{ Name="Jackie",Score=new List<int>{92,87,83,91}},
new Student{ Name="Helen",Score=new List<int>{53,76,72,62}}
};
//使用复合from子句查询命令
var getStudent =
from student in students
from score in student.Score
where score > 90
select new { Name=student.Name,Score=score};
分析:从代码中,我们可以看到学生对象中有个Score属性,Score属性本身就是List集合,这时候我们就要用到复合from子句进行查询了。首先遍历学生对象集合中的每个学生对象,然后在用另一个from子句,对每个学生对象中的Score属性进行遍历,筛选出含有90分以上的学生信息进行返回。
使用let子句扩展范围变量
用于创建查询自身的范围变量
需求:将字符串数组中的两句英文语句中所有的元音字母打头的单词输出到控制台
string[] strings ={
"I am a new Student.",
"You are a talent"
};
var query = from sentences in strings
let words = sentences.Split(' ')
from word in words
let w = word.ToLower()
where w[0] == 'a' || w[0] == 'e' || w[0] == 'i' || w[0] == 'o' ||
w[0] == 'u'
select word;
foreach (var word in query)
{
Console.Write(word + ",");
}
分析:首先遍历字符串数组中的每个字符串,用let子句创建查询自身的范围变量words,并调用Split(' ')方法,将每个字符串中以空格分割为单词存入words变量中,然后再次使用let子句创建查询自身的范围变量word,并调用ToLower()方法,将每个单词都变为小写,最后筛选出首字母为元音的单词进行返回。
说明:let语句是重命名。let位于第一个from和select语句之间。
这个例子从联接投影出最终“Let”表达式:
var q =
from c in db.Customers
join o in db.Orders on c.CustomerID
equals o.CustomerID into ords
let z = c.City + c.Country
from o in ords
select new
{
c.ContactName,
o.OrderID,
z
};
where子句
将一个布尔条件("谓词")应用于每个源元素(由范围变量引用),并返回满足指定条件的元素。
适用场景:实现过滤,查询等功能。
说明:与SQL命令中的Where作用相似,都是起到范围限定也就是过滤作用的,而判断条件就是它后面所接的子句。
Where操作包括3种形式,分别为简单形式、关系条件形式、First()形式。下面分别用实例举例下:
1)简单形式:
例如:使用where筛选在伦敦的客户
var q =
from c in db.Customers
where c.City == "London"
select c;
再如:筛选1994 年或之后雇用的雇员:
var q =
from e in db.Employees
where e.HireDate >= new DateTime(1994, 1, 1)
select e;
2)关系条件形式:
筛选库存量在订货点水平之下但未断货的产品:
var q =
from p in db.Products
where p.UnitsInStock <= p.ReorderLevel && !p.Discontinued
select p;
筛选出UnitPrice 大于10 或已停产的产品:
var q =
from p in db.Products
where p.UnitPrice > 10m || p.Discontinued
select p;
下面这个例子是调用两次where以筛选出UnitPrice大于10且已停产的产品。
var q =
db.Products.Where(p=>p.UnitPrice > 10m).Where(p=>p.Discontinued);
3)First()形式:
返回集合中的一个元素,其实质就是在SQL语句中加TOP (1)。
简单用法:选择表中的第一个发货方。
Shipper shipper = db.Shippers.First();
元素:选择CustomerID 为“BONAP”的单个客户
Customer cust = db.Customers.First(c => c.CustomerID == "BONAP");
条件:选择运费大于 10.00 的订单:
Order ord = db.Orders.First(o => o.Freight > 10.00M);
其他示例代码:
需求:将数组中小于5的偶数查询出来输出到控制台
//数据源
int[] arr = { 0, 3, 2, 1, 9, 6, 8, 7, 4, 5 };
//使用Where子句查询的查询语句
var query = from a in arr
where a < 5 && a % 2 == 0
select a;
//执行查询
foreach (var a in query)
{
Console.WriteLine(a);
}
分析:首先遍历数组中的每个元素,然后用where语句筛选出小于5,并且对2去模是0的数查询出来返回。
where子句不仅能使用表达式来进行筛选,还可以使用方法进行筛选。
public static bool IsEven(int a)
{
return a % 2 == 0 ? true : false;
}
//数据源
int[] arr = { 0, 3, 2, 1, 9, 6, 8, 7, 4, 5 };
//where子句也可以接受一个方法
var query = from a in arr
where IsEven(a)
select a;
foreach (var a in query)
{
Console.Write(a + ",");
}
这就是一个在where语句中使用方法进行筛选的例子,输出的结果和上例完全一样。
使用where子句还要注意以下几点
- 一个查询表达式可以包含多个where子句
- where子句是一种筛选机制。除了不能是第一个或最后一个子句外,它几乎可以放在查询表达式中的任何位置。where子句可以出现在group子句的前面或后面,具体情况取决于是必须在对源元素进行分组之前还是分组之后来筛选源元素。
- 如果指定的谓词对于数据源中的元素无效,则会发生编译时错误。这是Linq提供的强类型检查的一个优点。
- 编译时,where关键字会被转换为对where标准查询运算符方法的调用。
orderby子句
适用场景:对查询出的语句进行排序,比如按时间排序等等。
说明:按指定表达式对集合排序;延迟,:按指定表达式对集合排序;延迟,默认是升序,加上descending表示降序,对应的扩展方法是OrderBy和OrderByDescending
- 对查询出来的结果集进行升序或降序排列。
- 可以指定多个键,以便执行一个或多个次要排序操作。
- 默认排序顺序为升序。
- 编译时,orderby子句将被转换为对OrderBy方法的调用。orderby子句中的多个键转换为ThenBy方法调用。
接着上个例子的演示,本例是一个升序的排序。
var query = from a in arr
where IsEven(a)
orderby a ascending
select a;
本例是一个降序的排序。
var query = from a in arr
where IsEven(a)
orderby a descending
select a;
1)简单形式
这个例子使用 orderby 按雇用日期对雇员进行排序:
var q =
from e in db.Employees
orderby e.HireDate
select e;
说明:默认为升序
2)带条件形式
注意:Where和Order By的顺序并不重要。而在T-SQL中,Where和Order By有严格的位置限制。
var q =
from o in db.Orders
where o.ShipCity == "London"
orderby o.Freight
select o;
语句描述:使用where和orderby按运费进行排序。
3)降序排序
var q =
from p in db.Products
orderby p.UnitPrice descending
select p;
4)ThenBy
语句描述:使用复合的 orderby 对客户进行排序,进行排序:
var q =
from c in db.Customers
orderby c.City, c.ContactName
select c;
说明:按多个表达式进行排序,例如先按City排序,当City相同时,按ContactName排序。这一句用Lambda表达式像这样写:
var q =
db.Customers
.OrderBy(c => c.City)
.ThenBy(c => c.ContactName).ToList();
在T-SQL中没有ThenBy语句,其依然翻译为OrderBy,所以也可以用下面语句来表达:
var q =
db.Customers
.OrderBy(c => c.ContactName)
.OrderBy(c => c.City).ToList();
所要注意的是,多个OrderBy操作时,级连方式是按逆序。 对于降序的,用相应的降序操作符替换即可。
var q =
db.Customers
.OrderByDescending(c => c.City)
.ThenByDescending(c => c.ContactName).ToList();
需要说明的是,OrderBy操作,不支持按type排序,也不支持匿名类。比如
var q =
db.Customers
.OrderBy(c => new
{
c.City,
c.ContactName
}).ToList();
会被抛出异常。错误是前面的操作有匿名类,再跟OrderBy时,比较的是类别。比如
var q =
db.Customers
.Select(c => new
{
c.City,
c.Address
})
.OrderBy(c => c).ToList();
如果你想使用OrderBy(c => c),其前提条件是,前面步骤中,所产生的对象的类别必须为C#语言的基本类型。比如下句,这里City为string类型。
var q =
db.Customers
.Select(c => c.City)
.OrderBy(c => c).ToList();
5)ThenByDescending
这两个扩展方式都是用在OrderBy/OrderByDescending之后的,第一个ThenBy/ThenByDescending扩展方法作为第二位排序依据,第二个ThenBy/ThenByDescending则作为第三位排序依据,以此类推
var q =
from o in db.Orders
where o.EmployeeID == 1
orderby o.ShipCountry, o.Freight descending
select o;
语句描述:使用orderby先按发往国家再按运费从高到低的顺序对 EmployeeID 1 的订单进行排序。
5)带GroupBy形式
var q =
from p in db.Products
group p by p.CategoryID into g
orderby g.Key
select new {
g.Key,
MostExpensiveProducts =
from p2 in g
where p2.UnitPrice == g.Max(p3 => p3.UnitPrice)
select p2
};
语句描述:使用orderby、Max 和 Group By 得出每种类别中单价最高的产品,并按 CategoryID 对这组产品进行排序。
group子句
- group子句返回一个IGrouping(T Key,T element)对象序列
- 编译时,group子句被转换成对GroupBy方法的调用
需求:根据首字母分组,并打印到控制台
//数据源
string[] fruits = { "apple", "banana", "peach", "orange", "melon", "lemon" };
//分组查询的查询语句
var query = from f in fruits
group f by f[0];
//执行查询
foreach (var letters in query)
{
Console.WriteLine("words that start with letter:" + letters.Key);
foreach (var word in letters)
{
Console.WriteLine(word);
}
}
分析:首先遍历字符串数组中的每个字符串,然后根据每个字符串的首字母进行分组,返回结果
var query = from f in fruits
group f by f[0] into g
where g.Key == 'p' || g.Key == 'b'
select g;
如果您想要对每个组执行附加查询操作,则可以使用into上下文关键字指定一个临时标识符。使用into时,必须继续编写该查询,并最终用一个select语句或另一个group子句结束该查询。
join子句
- 使用join子句可以将来自不同源序列并且在对象模型中没有直接关系的元素相关联。
- 唯一的要求是每个源中的元素需要共享某个可以进行比较以判断是否相等的值。
- join子句使用特殊的equals关键字比较指定的键是否相等。
1)内部连接
var innerJoinQuery =
from category in categories
join prod in products on category.ID equals prod.CategoryID
select new { ProductName = prod.Name, Category = category.Name };
2)分组连接
var innerGroupJoinQuery =
from category in categories
join prod in products on category.ID equals prod.CategoryID
into prodGroup
select new { CategoryName = category.Name, Products = prodGroup };
3)左外部连接
var leftOuterJoinQuery =
from category in categories
join prod in products on category.ID equals prod.CategoryID
into prodGroup
from item in prodGroup.DefaultIfEmpty(new Product{Name =
string.Empty, CategoryID = 0})
select new { CatName = category.Name, ProdName = item.Name };
在左外连接中,将返回左侧源序列中的所有元素,即使它们在右侧序列中没有匹配的元素也是如此。
若要在Linq中执行左外连接,请将DefaultIfEmpty方法与分组连接结合起来,以指定要在某个元素不具有匹配元素时产生的默认右侧元素,可以使用null作为任何引用类型的默认值。也可以指定用户定义的默认类型。
equals关键字
- join子句执行同等连接。换句话说,只能基于两个键之间的相等关系进行匹配。
- 为了表明所有连接都是同等连接,join子句使用equals关键字而不是==运算符
select子句(选择、投影)
select子句可以指定将在执行查询时产生的值的类型。该子句的结果将基于前面所有子句的计算结果以及select子句本身中的所有表达式。
查询表达式必须以select子句或group子句结束
在最简单的情况下,select子句仅指定范围变量。这会使返回的序列包含于数据源具有相同类型的元素。