GroupBy操作
换一种写作风格。本节讲groupby操作。
在所有的Linq To Sql操作中,GroupBy是最难理解的一个。因为,这里和Sql的出入较大。而Group真的就能返回n多组。
打开vs,新建一工程,加入System.Data.Linq.dll的引用。把northwind数据库做个映射,添加到工程里。创建一Northwind类的对象。在这里呢,我们要做分组统计,我们要先明白什么是分组。在我们开始设计product表时,我们欲留了一列叫做CategoryID。 这个字段,代表了这条记录中的产品,归属于那个类别。如图,字段的设计。

可以简单的举例,比如,当CategoryID的值为1时,就表示,对应的记录的产品是饮料类的。当它为2时,代表调味品类的。具体代表那一类,要看事先的约定。一般会专门有个表来保存这个约定。在northwind中,用Categories 表来保存这个约定。下面是catagories表中的纪录。

Categories 和Product表,通过CategoryID字段关联关系。也就是说,Product表中,CategoryID字段所代表的意思,保存在Categories 表中。好了,知道分组的概念了,我们开始分类统计吧。在Linq To Sql的所有操作中,groupby算是比较难理解的一个。我要友好的提醒你注意下这里的分组所用的字段哦,这个将来是要被用做继承的哦。
我们来看下面的例子。
var q
=
from p
in
db.Products
group p by p.CategoryID into g
select g;
在这里,from p in db.Products 表示从表中将对象纪录取出来。p就是每一个产品。而后面一句,group p by p.CategoryID into g表示对p进行归类,按什么归类呢,就是要按我们设定的CategoryID这个字段。而这个归类的结果,被重新命名为g,而且是必须重新命名。一旦重新命名,p的作用域就结束了,所以,最后select时,只能select g。
那有人就要问了,不重新命名可以吗?回答,可以。那你的select语句就没有了,上面语句等同与整个查询语句返回八个组,这和我们数据库的八种产品是对应的。找到最后一个group,点开+号,我们可以看到,里面有个Key和Results View字段,其中Key为8,是数据库里对应的Seafood分类,点开Results View你会惊奇的发现,所有的Seafood都在里面。我们把代码改一下,看看dlinq究竟做了什么。
var q
=
from p
in
db.Products
group p by p.CategoryID;
这是query表达式中,很少见的没有select语句的情况。我记忆中,好像只有此一种。我们还是按
var q =
from p in db.Products
group p by p.CategoryID into g
select g;
来讲解。因为这种更好理解。在T-Sql中,groupby只是用来做分组统计,计算平均值,最大值,求和等等,而在dlinq中的groupby,则发挥到了极值。我们用ToList()取到结果,来看看上面语句返回的结果。如图:

Northwind db
=
new
Northwind(ConStr);
db.Log
=
Console.Out;

Query
#region Query
var q = from p in db.Products
group p by p.CategoryID into g
select g;
#endregion
Query

Verification
#region Verification
foreach (var gp in q)

{

}
return;
#endregion
Verification
单步调试该foreach段代码,foreach的目的是让dlinq加载数据。在console windows上,你可以看到如下输出。
SELECT
[
t0
]
.
[
CategoryID
]
AS
[
Key
]
FROM
[
Products
]
AS
[
t0
]
GROUP
BY
[
t0
]
.
[
CategoryID
]
--
Context: SqlProvider(Sql2005) Model: AttributedMetaModel Build: 2.0.20612.0
SELECT
[
t0
]
.
[
ProductID
]
,
[
t0
]
.
[
ProductName
]
,
[
t0
]
.
[
SupplierID
]
,
[
t0
]
.
[
CategoryID
]
,
[
t0
]
.
[
QuantityPerUnit
]
,
[
t0
]
.
[
UnitPrice
]
,
[
t0
]
.
[
UnitsInStock
]
,
[
t0
]
.
[
UnitsOnO
rder
]
,
[
t0
]
.
[
ReorderLevel
]
,
[
t0
]
.
[
Discontinued
]
FROM
[
Products
]
AS
[
t0
]
WHERE
((
@x1
IS
NULL
)
AND
(
[
t0
]
.
[
CategoryID
]
IS
NULL
))
OR
((
@x1
IS
NOT
NULL
)
AND
(
[
t0
]
.
[
CategoryID
]
IS
NOT
NULL
)
AND
(
@x1
=
[
t0
]
.
[
CategoryID
]
))
--
@x1: Input Int32 (Size = 0; Prec = 0; Scale = 0) [1]
--
Context: SqlProvider(Sql2005) Model: AttributedMetaModel Build: 2.0.20612.0
原来dlinq不仅仅是做了一个groupby操作呀,他还针对每个组,都去做了一个遍历呀。我们可以用下面的图来理解。

外面的大圈,表示全集。而这个全集被分成了若干个小的集合。每个集合就叫做g。在g集合中,包含一个系统定义的Property,叫做Key。key值实际为分类时所用字段的值。而另外一部分,是该类别中所有的product.所以,我们如果想遍历某类别中所有的纪录,要这么来做。
var
q
=
from
p
in
db.Products
group
p
by
p.CategoryID
into
g
select
g;

foreach (
var
gp
in
q)
{
if
(gp.
Key
==
7
)
{
foreach (
var
p
in
gp)
{
//
do something
}
}
}
有朋友反馈说我提供的sample不能编译。大概是版本的问题,可以到http://msdn2.microsoft.com/en-us/bb330936.aspx下载for beta1的版本。本节接着讲groupby。
上一节,我们讲了如何理解groupby返回的结果。本节会延这个思路阐述下去。先来看下面的例子
GroupBy操作中Select的匿名类
var
q
=
from
p
in
db.Products
group
p
by
p.CategoryID
into
g
select
new { CategoryID
=
g.
Key
, g };
本例中,select操作中使用了匿名类。本系列中第一次提到匿名类是在http://www.cnblogs.com/126/archive/2006/12/20/503519.html 一文中。本文将再一次,阐述匿名类的理解。所谓匿名类,其实质,并不是匿名,而是编译器帮你去创建了这么一个类,在用户看来,好像并没有去创建,此所谓匿名类。也就是说,编译器在编译时,还是有这个类的,这个类是编译器自己创建的,其名称是编译器界定的。 在上例的匿名类中,有2个property,一个叫CategoryID, 一个叫g。 大家要注意了,这个匿名类,其实质是对返回结果重新进行了包装。而那个叫做g的property,就封装了一个完整的分组。如图,仔细比较和上篇图的区别。

如果,使用下面的语句。
var
q
=
from
p
in
db.Products
group
p
by
p.CategoryID
into
g
select
new { CategoryID
=
g.
Key
,GroupSet
=
g };
只是把g重新命名为GroupSet.需要用下面的遍历,获取每个产品纪录。
foreach
(var gp
in
q)

{
if (gp.CategoryID == 7)

{
foreach (var p in gp.GroupSet)

{
}
}
}
这里groupby的操作相对难理解些,主要原因,它包含了整个分组的具体信息,而不是简单的求和,取平均值等。如果在最终结果中,也就是在select语句中,不包含g的全部信息,而只是g的聚合函数。又会是怎么样的一番风景呢?
GroupBy中的Max, Min, Sum, Average,Count
如果,只想取每类产品中,单价为最大,用T-sql该怎么办呢?是不是要这么来写
SELECT
MAX
(
[
t0
]
.
[
UnitPrice
]
)
AS
[
MaxPrice
]
,
[
t0
]
.
[
CategoryID
]
FROM
[
dbo
]
.
[
Products
]
AS
[
t0
]
GROUP
BY
[
t0
]
.
[
CategoryID
]
我们来看看,dlinq如何来做同样的事情.如下,先按CategoryID归类,然后,只取CategoryID值和同类产品中单价最大的。
var
q
=
from
p
in
db.Products
group
p
by
p.CategoryID
into
g
select
new {
g.
Key
,
MaxPrice
=
g.
Max
(p
=>
p.UnitPrice)
};
在这里,Max函数只对每个分组进行操作。我们来看看其结果

呀,这次,dlinq并没有把组里所有的纪录都取出来的吗。(请参考http://www.cnblogs.com/126/archive/2006/09/01/486388.html一文中的方法,配置sample.) dlinq只是简单做了统计,并返回结果。
每类产品中,单价为最小的,
var
q
=
from
p
in
db.Products
group
p
by
p.CategoryID
into
g
select
new {
g.
Key
,
MinPrice
=
g.
Min
(p
=>
p.UnitPrice)
};
每类产品的价格平均值
var
q
=
from
p
in
db.Products
group
p
by
p.CategoryID
into
g
select
new {
g.
Key
,
AveragePrice
=
g.Average(p
=>
p.UnitPrice)
};
每类产品,价格之和
var
q
=
from
p
in
db.Products
group
p
by
p.CategoryID
into
g
select
new {
g.
Key
,
TotalPrice
=
g.
Sum
(p
=>
p.UnitPrice)
};
各类产品,数量之和
var
q
=
from
p
in
db.Products
group
p
by
p.CategoryID
into
g
select
new {
g.
Key
,
NumProducts
=
g.
Count
()
};
如果用OrderDetails表做统计,会更好些,因为,不光可以统计同一种产品,还可以统计同一订单。
接着统计,同各类产品中,断货的产品数量。使用下面的语句。
var
q
=
from
p
in
db.Products
group
p
by
p.CategoryID
into
g
select
new {
g.
Key
,
NumProducts
=
g.
Count
(p
=>
p.Discontinued)
};
在这里,count函数里,使用了Lambda表达式。在上篇中,我们已经阐述了g是一个组的概念。那在该Lambda表达式中的p,就代表这个组里的一个元素或对象,即某一个产品。还可以使用where条件来限制最终筛选结果
var
q
=
from
p
in
db.Products
group
p
by
p.CategoryID
into
g
where
g.
Count
()
>=
10
select
new {
g.
Key
,
ProductCount
=
g.
Count
()
};
这句在翻译成sql语句时,欠套了一层,在最外层加了条件。
SELECT
[
t1
]
.
[
CategoryID
]
,
[
t1
]
.
[
value2
]
AS
[
ProductCount
]
FROM
(
SELECT
COUNT
(
*
)
AS
[
value
]
,
COUNT
(
*
)
AS
[
value2
]
,
[
t0
]
.
[
CategoryID
]
FROM
[
dbo
]
.
[
Products
]
AS
[
t0
]
GROUP
BY
[
t0
]
.
[
CategoryID
]
)
AS
[
t1
]
WHERE
[
t1
]
.
[
value
]
>=
@p0
--
@p0: Input Int32 (Size = 0; Prec = 0; Scale = 0) [10]
--
Context: SqlProvider(Sql2005) Model: AttributedMetaModel Build: 2.0.20612.0
GroupBy操作中GroupBy的匿名类
在第一次谈到匿名类时,我们就提到不光Select操作可以使用匿名类,其他操作符也可以。但是,OrderBy不支持。请参考C#3.0入门系列(六)-之OrderBy操作
当用户既想按产品的分类,又想按供应商来做分组,该怎么办呢。这时,我们就该使用匿名类。
var
categories
=
from
p
in
db.Products
group
p
by
new { p.CategoryID, p.SupplierID }
into
g
select
new {g.
Key
, g};
在by后面,new出来一个匿名类。这里,Key其实质是一个类的对象,Key包含两个Property,一个是CategoryID,再一个是SupplierID ,要想取到具体CategoryID的值,需要g.Key.CategoryID,才能访问到。我们来看dlinq翻译的T-sql语句。
SELECT
[
t0
]
.
[
SupplierID
]
,
[
t0
]
.
[
CategoryID
]
FROM
[
dbo
]
.
[
Products
]
AS
[
t0
]
GROUP
BY
[
t0
]
.
[
CategoryID
]
,
[
t0
]
.
[
SupplierID
]
--
Context: SqlProvider(Sql2005) Model: AttributedMetaModel Build: 2.0.20612.0
先按CategoryID,再按SupplierID ,和匿名类中的循序一样。
最后一个例子。
var
categories
=
from
p
in
db.Products
group
p
by
new { Criterion
=
p.UnitPrice
>
10
}
into
g
select
g;
按产品单价是否大于10分类。其结果为两类,大于的是一类,小于及等于为另一类。好了,剩下的,大家自己多去领会。
Join操作
本节讲join操作。我们知道,T-sql中,有三种最基本的join,inner join, left join, 和right join。 而dlinq并不支持right join。道理很简单,right join以right表为基础,left表中没有对应记录的,将以null值填充。而dlinq以left表做为主表创建对象。如果一个对象为null,你如何获取它的其他的属性呢?
在C# 3.0入门系列(四)-之Select操作一文中,我们提到了query expression首先会被翻译成标准的API, 而dlinq在join操作中,一共为我们提供了三个API.它们是Join, SelectMany和GroupJoin
Join
在101 的sample中,并没有join的例子。当一个query expression 有join字句时,而没有into字句,它将会被翻译成join方法。如,以Customers为主表,Orders为子表,用CustomerID 做关联进行join操作。
var
q
=
from
c
in
db.Customers
join
o
in
db.Orders
on
c.CustomerID equals o.CustomerID

select
new { c.CustomerID, o.EmployeeID };
它将会被翻译成
var
q
=
db.Customers.
Join
(db.Orders, c
=>
c.CustomerID, o
=>
o.CustomerID, (c, o)
=>
new { c.CustomerID, o.EmployeeID });
join方法的第一个参数,为子表,第二个参数,表示主表中的选择键,第三个参数为子表中的对应键,第四个为最终筛选结果。大家需要注意的时,因为参数的顺序是确定的,所以在写dlinq语句时,c.CustomerID equals o.CustomerID 的顺序是不能变的。
该语句所产生的T-sql语句为
SELECT
[
t0
]
.
[
CustomerID
]
,
[
t1
]
.
[
EmployeeID
]
FROM
[
Customers
]
AS
[
t0
]
INNER
JOIN
[
Orders
]
AS
[
t1
]
ON
[
t0
]
.
[
CustomerID
]
=
[
t1
]
.
[
CustomerID
]
SelectMany
在101sample中,给了4个SelectMany的例子。会被翻译成SelectMany需要满足2个条件。1,query语句中没有join和into,2,必须出现EntitySet。 关于EntitySet,请参考C#3.0进阶系列(一)-从映射讲起
先看第一个例子
var
q
=
from
c
in
db.Customers
from
o
in
c.Orders
where
c.City
==
"London"
select
o;
Customers与Orders是1:M的关系。即Orders在Customers类中,以EntitySet出现。所以第二个from是从c.Orders而不是db.Orders里进行筛选。定义了他们关系的Mapping Code用Attribute保存了他们的关系。如
[Association(Name="Order_OrderDetail", Storage="_OrderDetails", OtherKey="OrderID")]
[Association(Name="Order_OrderDetail", Storage="_Order", ThisKey="OrderID", IsForeignKey=true)]
所以,你就不用担心,dlinq是否知道该按那个键进行关联。有兴趣的朋友,可以自己修改这里的OtherKey和ThisKey的值,看看翻译的T-sql语句是否变了。
第二个例子
var
q
=
from
p
in
db.Products
where
p.Supplier.Country
==
"USA"
&&
p.UnitsInStock
==
0
select
p;
这个例子,直接就使用了p.Supplier.Country 做条件,这样,也间接关联了Supplier表。该语句生成的T-sql语句更是值得揣摩,这大概是Left Out Join 的最简单的Dlinq语句。
SELECT
[
t0
]
.
[
ProductID
]
,
[
t0
]
.
[
ProductName
]
,
[
t0
]
.
[
SupplierID
]
,
[
t0
]
.
[
CategoryID
]
,
[
t0
]
.
[
QuantityPerUnit
]
,
[
t0
]
.
[
UnitPrice
]
,
[
t0
]
.
[
UnitsInStock
]
,
[
t0
]
.
[
UnitsOnOrder
]
,
[
t0
]
.
[
ReorderLevel
]
,
[
t0
]
.
[
Discontinued
]
FROM
[
dbo
]
.
[
Products
]
AS
[
t0
]
LEFT
OUTER
JOIN
[
dbo
]
.
[
Suppliers
]
AS
[
t1
]
ON
[
t1
]
.
[
SupplierID
]
=
[
t0
]
.
[
SupplierID
]
WHERE
(
[
t1
]
.
[
Country
]
=
@p0
)
AND
(
[
t0
]
.
[
UnitsInStock
]
=
@p1
)
--
@p0: Input String (Size = 3; Prec = 0; Scale = 0) [USA]
--
@p1: Input Int32 (Size = 0; Prec = 0; Scale = 0) [0]
--
Context: SqlProvider(Sql2005) Model: AttributedMetaModel Build: 2.0.20612.0
第三个例子是M : M的关系
var
q
=
from
e
in
db.Employees
from
et
in
e.EmployeeTerritories
where
e.City
==
"Seattle"
select
new {e.FirstName, e.LastName, et.Territory.TerritoryDescription};
M:M的关系,一般会涉及三个表。(如果,有一个表是自关联的,那有可能只有2个表。)在这里,涉及Employees, EmployeeTerritories, Territories共三个表。它们的关系是1 : M : 1. Employees和Territories没有很明确的关系。这个例子和上一个不同的是,它是在Select字句中,牵扯到Territories表。其生成的T-sql为
SELECT
[
t0
]
.
[
FirstName
]
,
[
t0
]
.
[
LastName
]
,
[
t2
]
.
[
TerritoryDescription
]
FROM
[
dbo
]
.
[
Employees
]
AS
[
t0
]
CROSS
JOIN
[
dbo
]
.
[
EmployeeTerritories
]
AS
[
t1
]
INNER
JOIN
[
dbo
]
.
[
Territories
]
AS
[
t2
]
ON
[
t2
]
.
[
TerritoryID
]
=
[
t1
]
.
[
TerritoryID
]
WHERE
(
[
t0
]
.
[
City
]
=
@p0
)
AND
(
[
t1
]
.
[
EmployeeID
]
=
[
t0
]
.
[
EmployeeID
]
)
--
@p0: Input String (Size = 7; Prec = 0; Scale = 0) [Seattle]
--
Context: SqlProvider(Sql2005) Model: AttributedMetaModel Build: 2.0.20612.0
最后一个例子是自关联的,并且夹带了条件
var
q
=
from
e1
in
db.Employees
from
e2
in
e1.Employees
where
e1.City
==
e2.City
select
new {
FirstName1
=
e1.FirstName, LastName1
=
e1.LastName,
FirstName2
=
e2.FirstName, LastName2
=
e2.LastName,
e1.City
};
其T-sql为
SELECT
[
t0
]
.
[
FirstName
]
,
[
t0
]
.
[
LastName
]
,
[
t1
]
.
[
FirstName
]
AS
[
FirstName2
]
,
[
t1
]
.
[
LastName
]
AS
[
LastName2
]
,
[
t0
]
.
[
City
]
FROM
[
dbo
]
.
[
Employees
]
AS
[
t0
]
,
[
dbo
]
.
[
Employees
]
AS
[
t1
]
WHERE
(
[
t0
]
.
[
City
]
=
[
t1
]
.
[
City
]
)
AND
(
[
t1
]
.
[
ReportsTo
]
=
[
t0
]
.
[
EmployeeID
]
)
--
Context: SqlProvider(Sql2005) Model: AttributedMetaModel Build: 2.0.20612.0
从上面的例子我们可以看出,Dlinq以非常灵活的方式,处理其内部各表的关系。它不须显式的声明需要关联到那个表,也可以放在Where和Select等子句中,隐式关联。
GroupJoin
当dlinq语句中,有join而且还有into时,它会被翻译为GroupJoin.我们先来看第一个例子。
var
q
=
from
c
in
db.Customers
join
o
in
db.Orders
on
c.CustomerID equals o.CustomerID
into
orders
select
new {c.ContactName, OrderCount
=
orders.
Count
()};
本系列曾在C#3.0入门系列(八)-之GroupBy操作 一文中,第一次谈到到into。into的概念是对其结果进行重新命名。为什么需要重新命名呢?我们以本例为例。One To Many的关系中,左边是one,它每条记录叫做c(from c in db.Customers),右边是many,其每条记录叫做o ( join o in db.Orders ),每对应左边的一个c,都会有一组o,那这一组o,就叫做orders,也就是说,我们把一组o命名为orders,这就是into用途。(和groupby中类似)。这也就是为什么在select语句中,orders可以调用聚合函数Count。

在这个例子中,翻译的t-sql为
SELECT
[
t0
]
.
[
ContactName
]
, (
SELECT
COUNT
(
*
)
FROM
[
dbo
]
.
[
Orders
]
AS
[
t1
]
WHERE
[
t0
]
.
[
CustomerID
]
=
[
t1
]
.
[
CustomerID
]
)
AS
[
value
]
FROM
[
dbo
]
.
[
Customers
]
AS
[
t0
]
--
Context: SqlProvider(Sql2005) Model: AttributedMetaModel Build: 2.0.20612.0
dlinq很聪明,直接用其内欠的t-sql返回值作为字段值。
第二个例子
var
q
=
from
c
in
db.Customers
join
o
in
db.Orders
on
c.CustomerID equals o.CustomerID
into
ords
join
e
in
db.Employees
on
c.City equals e.City
into
emps
select
new {c.ContactName, ords
=
ords.
Count
(), emps
=
emps.
Count
()};
三个表联合查询。在其join语句后,紧跟着又是一个join.只是表多了些,并没有太多新鲜的东西。
第三个例子
var
q
=
from
e
in
db.Employees
join
o
in
db.Orders
on
e equals o.Employee
into
ords
from
o
in
ords.DefaultIfEmpty()
select
new {e.FirstName, e.LastName,
Order
=
o};
Left Out Join的标准写法。以Employees为左表,Orders 为右,Orders 表中为空时,填冲null值。在将join的结果重命名后,再使用DefaultEmpty()函数,对其再次查询。大家需要注意的时,其最后的结果中有个Order,因为from o in ords.DefaultIfEmpty() 是对ords组再一次遍历,所以,最后结果中的Order并不是一个集合。但是,如果没有from o in ords.DefaultIfEmpty() 这句,最后的select语句写成select new { e.FirstName, e.LastName, Order = ords }的话,那Order就是一个集合
上例翻译的T-sql 为
SELECT
[
t0
]
.
[
FirstName
]
,
[
t0
]
.
[
LastName
]
,
[
t2
]
.
[
test
]
,
[
t2
]
.
[
OrderID
]
,
[
t2
]
.
[
CustomerID
]
,
[
t2
]
.
[
EmployeeID
]
,
[
t2
]
.
[
OrderDate
]
,
[
t2
]
.
[
RequiredDate
]
,
[
t2
]
.
[
ShippedDate
]
,
[
t2
]
.
[
ShipVia
]
,
[
t2
]
.
[
Freight
]
,
[
t2
]
.
[
ShipName
]
,
[
t2
]
.
[
ShipAddress
]
,
[
t2
]
.
[
ShipCity
]
,
[
t2
]
.
[
ShipRegion
]
,
[
t2
]
.
[
ShipPostalCode
]
,
[
t2
]
.
[
ShipCountry
]
FROM
[
dbo
]
.
[
Employees
]
AS
[
t0
]
LEFT
OUTER
JOIN
(
SELECT
1
AS
[
test
]
,
[
t1
]
.
[
OrderID
]
,
[
t1
]
.
[
CustomerID
]
,
[
t1
]
.
[
EmployeeID
]
,
[
t1
]
.
[
OrderDate
]
,
[
t1
]
.
[
RequiredDate
]
,
[
t1
]
.
[
ShippedDate
]
,
[
t1
]
.
[
ShipVia
]
,
[
t1
]
.
[
Freight
]
,
[
t1
]
.
[
ShipName
]
,
[
t1
]
.
[
ShipAddress
]
,
[
t1
]
.
[
ShipCity
]
,
[
t1
]
.
[
ShipRegion
]
,
[
t1
]
.
[
ShipPostalCode
]
,
[
t1
]
.
[
ShipCountry
]
FROM
[
dbo
]
.
[
Orders
]
AS
[
t1
]
)
AS
[
t2
]
ON
[
t0
]
.
[
EmployeeID
]
=
[
t2
]
.
[
EmployeeID
]
--
Context: SqlProvider(Sql2005) Model: AttributedMetaModel Build: 2.0.20612.0
第四个例子,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};
let语句有点类似into,也是个重命名的概念。需要提醒大家的是,let只要是放在第一个from后,select语句前就是符合语法的。上面的语句和下面这条是等价的。
var
q
=
from
c
in
db.Customers
let z
=
c.City
+
c.Country
join
o
in
db.Orders
on
c.CustomerID equals o.CustomerID
into
ords
from
o
in
ords
select
new { c.ContactName, o.OrderID, z };
其产生的T-sql均为:
SELECT
[
t1
]
.
[
ContactName
]
,
[
t2
]
.
[
OrderID
]
,
[
t1
]
.
[
value
]
FROM
(
SELECT
[
t0
]
.
[
City
]
+
[
t0
]
.
[
Country
]
AS
[
value
]
,
[
t0
]
.
[
CustomerID
]
,
[
t0
]
.
[
ContactName
]
FROM
[
dbo
]
.
[
Customers
]
AS
[
t0
]
)
AS
[
t1
]
CROSS
JOIN
[
dbo
]
.
[
Orders
]
AS
[
t2
]
WHERE
[
t1
]
.
[
CustomerID
]
=
[
t2
]
.
[
CustomerID
]
--
Context: SqlProvider(Sql2005) Model: AttributedMetaModel Build: 2.0.20612.0
它也应该和下面的语句等价,但其翻译的T-sql语句稍微有所不同。
var
q
=
from
c
in
db.Customers
join
o
in
db.Orders
on
c.CustomerID equals o.CustomerID
into
ords
from
o
in
ords
let z
=
c.City
+
c.Country
select
new { c.ContactName, o.OrderID, z };
有兴趣的朋友可以研究下,其产生的T-sql 为
SELECT
[
t2
]
.
[
ContactName
]
,
[
t2
]
.
[
OrderID
]
,
[
t2
]
.
[
value
]
FROM
(
SELECT
[
t0
]
.
[
City
]
+
[
t0
]
.
[
Country
]
AS
[
value
]
,
[
t0
]
.
[
CustomerID
]
,
[
t0
]
.
[
ContactName
]
,
[
t1
]
.
[
OrderID
]
,
[
t1
]
.
[
CustomerID
]
AS
[
CustomerID2
]
FROM
[
Customers
]
AS
[
t0
]
,
[
Orders
]
AS
[
t1
]
)
AS
[
t2
]
WHERE
[
t2
]
.
[
CustomerID
]
=
[
t2
]
.
[
CustomerID2
]
--
Context: SqlProvider(Sql2005) Model: AttributedMetaModel Build: 2.0.20612.0
第五个例子为composite key.
var
q
=
from
o
in
db.Orders
from
p
in
db.Products
join
d
in
db.OrderDetails
on
new {o.OrderID, p.ProductID} equals new {d.OrderID, d.ProductID}
into
details
from
d
in
details
select
new {o.OrderID, p.ProductID, d.UnitPrice};
这里,它使用三个表,并且用匿名类来表示它们之间的关系。因为,其之间的关系已经不是一个键可以描述清楚的,所以只有用匿名类,表示组合键。这个例子有点像SelectMany中的ManyToMany的那个。
还有一种composite key的,就是两个表之间是用composite key表示关系的。这种情况很简单,不需像该例中使用匿名类。该例翻译的T-sql为
SELECT
[
t0
]
.
[
OrderID
]
,
[
t1
]
.
[
ProductID
]
,
[
t2
]
.
[
UnitPrice
]
FROM
[
dbo
]
.
[
Orders
]
AS
[
t0
]
,
[
dbo
]
.
[
Products
]
AS
[
t1
]
,
[
dbo
]
.
[
Order Details
]
AS
[
t2
]
WHERE
(
[
t0
]
.
[
OrderID
]
=
[
t2
]
.
[
OrderID
]
)
AND
(
[
t1
]
.
[
ProductID
]
=
[
t2
]
.
[
ProductID
]
)
--
Context: SqlProvider(Sql2005) Model: AttributedMetaModel Build: 2.0.20612.0
最后一个例子,没有看出什么好玩的来,不讲了。
写到这里,c#3.0的入门系列已经接近尾声了。我们一起学习了Dlinq的最基本操作。还剩下Union, In, Like还有一些聚合函数等操作。将会在下面几章中介绍。不知道大家对什么还感兴趣的,或者我能够提供帮助的,尽管问。
关于Linq To Sql 中的,Create, update, Delete 操作,以及Store procedure 及UDF等,更像是运用函数,而不是语言。所以,不在C#语言中讲。在考虑是不是开个什么Linq To Sql的深入应用。
写blog是对自己个人知识的总结,也是对自己表达功底的考验。因本人水平有限,错误再所难免,还请大家指出并谅解。
In, Like操作
有这么一个例子,寻找一个表中的某个字段介于某个给定的集合该怎么办?Sql写起来很简单,比如:Select * from table where id in (2,3, 4, 5)。 就是寻找id字段为这个给定的集合(2,3, 4, 5)内的值。那Linq to Sql该怎么做呢?一个字,简单。
In Operator
比如,我们想要查找,"AROUT", "BOLID" 和 "FISSA" 这三个客户的订单。该如何做呢?Linq to Sql是这么做的。
string
[]
customerID_Set
=
new string
[]
{ "AROUT", "BOLID", "FISSA" };

var
q
=
(
from
o
in
db.Orders
where
customerID_Set.
Contains
(o.CustomerID)
select
o).ToList();
其生成的sql语句为
SELECT
[
t0
]
.
[
OrderID
]
,
[
t0
]
.
[
CustomerID
]
,
[
t0
]
.
[
EmployeeID
]
,
[
t0
]
.
[
OrderDate
]
,
[
t0
]
.
[
RequiredDate
]
,
[
t0
]
.
[
ShippedDate
]
,
[
t0
]
.
[
ShipVia
]
,
[
t0
]
.
[
Freight
]
,
[
t0
]
.
[
Sh
ipName
]
,
[
t0
]
.
[
ShipAddress
]
,
[
t0
]
.
[
ShipCity
]
,
[
t0
]
.
[
ShipRegion
]
,
[
t0
]
.
[
ShipPosta
lCode
]
,
[
t0
]
.
[
ShipCountry
]
FROM
[
dbo
]
.
[
Orders
]
AS
[
t0
]
WHERE
[
t0
]
.
[
CustomerID
]
IN
(
@p0
,
@p1
,
@p2
)
--
@p0: Input String (Size = 5; Prec = 0; Scale = 0) [AROUT]
--
@p1: Input String (Size = 5; Prec = 0; Scale = 0) [BOLID]
--
@p2: Input String (Size = 5; Prec = 0; Scale = 0) [FISSA]
先定义了一个数组,在linq query中,使用Contains,也很好理解,就是这个数组,包含了所有的CustomerID, 即返回结果中,所有的CustomerID都在这个集合内。也就是in。 你也可以把数组的定义放在linq语句里。比如:
var
q
=
(
from
o
in
db.Orders
where
(new string
[]
{ "AROUT", "BOLID", "FISSA" }).
Contains
(o.CustomerID)
select
o).ToList();
Not in 呢?加个取反就是
var
q2
=
(
from
o
in
db.Orders
where
!(new string
[]
{ "AROUT", "BOLID", "FISSA" }).
Contains
(o.CustomerID)
select
o).ToList();
就这么简单。
Like Operator
Like的操作,有点像in,但是,方向变了。什么意思呢。就是你给定一个字符串,去寻找数据中某个字段包含这个字符串。就是给定的字符串是某字段的子集。Sql Script是这么写的。
Selec
*
from
table
where
id
like
'
%AD%
'
Selec
*
from
table
where
id
like
'
%AD
'
Selec
*
from
table
where
id
like
'
AD%
'
上面的%是通配符,表示,该字段含有某个值,不知道的位置使用%代替。第一个是表示中间一段是AD,两头不清楚。第二个是结尾是AD,前面的不清楚。第三个相反,开头是AD,结尾不清楚。其对应的Linq 语句为
var
q
=
(
from
c
in
db.Customers
where
c.CustomerID.
Contains
("ROUT")
select
c).ToList();
其生成的sql为
SELECT
[
t0
]
.
[
CustomerID
]
,
[
t0
]
.
[
CompanyName
]
,
[
t0
]
.
[
ContactName
]
,
[
t0
]
.
[
ContactT
itle
]
,
[
t0
]
.
[
Address
]
,
[
t0
]
.
[
City
]
,
[
t0
]
.
[
Region
]
,
[
t0
]
.
[
PostalCode
]
,
[
t0
]
.
[
Coun
try
]
,
[
t0
]
.
[
Phone
]
,
[
t0
]
.
[
Fax
]
FROM
[
dbo
]
.
[
Customers
]
AS
[
t0
]
WHERE
[
t0
]
.
[
CustomerID
]
LIKE
@p0
--
@p0: Input String (Size = 6; Prec = 0; Scale = 0) [%ROUT%]
以ISSA结尾,头部通配:
var
q
=
(
from
c
in
db.Customers
where
c.CustomerID.EndsWith("ISSA")
select
c).ToList();
其生成的sql为
SELECT
[
t0
]
.
[
CustomerID
]
,
[
t0
]
.
[
CompanyName
]
,
[
t0
]
.
[
ContactName
]
,
[
t0
]
.
[
ContactT
itle
]
,
[
t0
]
.
[
Address
]
,
[
t0
]
.
[
City
]
,
[
t0
]
.
[
Region
]
,
[
t0
]
.
[
PostalCode
]
,
[
t0
]
.
[
Coun
try
]
,
[
t0
]
.
[
Phone
]
,
[
t0
]
.
[
Fax
]
FROM
[
dbo
]
.
[
Customers
]
AS
[
t0
]
WHERE
[
t0
]
.
[
CustomerID
]
LIKE
@p0
--
@p0: Input String (Size = 5; Prec = 0; Scale = 0) [%ISSA]
以ARO开始,尾部通配:
var
q
=
(
from
c
in
db.Customers
where
c.CustomerID.StartsWith("ARO")
select
c).ToList();
其生成的sql为
SELECT
[
t0
]
.
[
CustomerID
]
,
[
t0
]
.
[
CompanyName
]
,
[
t0
]
.
[
ContactName
]
,
[
t0
]
.
[
ContactT
itle
]
,
[
t0
]
.
[
Address
]
,
[
t0
]
.
[
City
]
,
[
t0
]
.
[
Region
]
,
[
t0
]
.
[
PostalCode
]
,
[
t0
]
.
[
Coun
try
]
,
[
t0
]
.
[
Phone
]
,
[
t0
]
.
[
Fax
]
FROM
[
dbo
]
.
[
Customers
]
AS
[
t0
]
WHERE
[
t0
]
.
[
CustomerID
]
LIKE
@p0
--
@p0: Input String (Size = 4; Prec = 0; Scale = 0) [ARO%]
Linq 还提供了一种方法,叫做SqlMethods.Like,需要先添加System.Data.Linq.SqlClient名称空间。上面的三个可以写成
var
q
=
(
from
c
in
db.Customers
where
SqlMethods.
Like
(c.CustomerID, "
%
ROUT
%
")
select
c).ToList();
这里,你需要自己填写通配符,告诉Linq你是如何匹配。比如
var
q
=
(
from
c
in
db.Customers
where
SqlMethods.
Like
(c.CustomerID, "
%
ISSA")
select
c).ToList();
再比如:
var
q
=
(
from
c
in
db.Customers
where
SqlMethods.
Like
(c.CustomerID, "ARO
%
")
select
c).ToList();
SqlMethods.Like最奇妙的地方,莫过于,自己定义的通配表达式,你可以在任何地方实现通配。比如
var
q
=
(
from
c
in
db.Customers
where
SqlMethods.
Like
(c.CustomerID, "A
%
O
%
T")
select
c).ToList();
其生成的sql为
SELECT
[
t0
]
.
[
CustomerID
]
,
[
t0
]
.
[
CompanyName
]
,
[
t0
]
.
[
ContactName
]
,
[
t0
]
.
[
ContactT
itle
]
,
[
t0
]
.
[
Address
]
,
[
t0
]
.
[
City
]
,
[
t0
]
.
[
Region
]
,
[
t0
]
.
[
PostalCode
]
,
[
t0
]
.
[
Coun
try
]
,
[
t0
]
.
[
Phone
]
,
[
t0
]
.
[
Fax
]
FROM
[
dbo
]
.
[
Customers
]
AS
[
t0
]
WHERE
[
t0
]
.
[
CustomerID
]
LIKE
@p0
--
@p0: Input String (Size = 5; Prec = 0; Scale = 0) [A%O%T]
就是最标准的知道以A开头,以T结尾,中间知道一个值O,其他就什么不知道了。就用这个。
SQL Server 定义了四种通配符,在这里都可以使用。它们是:
Wildcard character | Description | Example |
---|
% | Any string of zero or more characters. | WHERE title LIKE '%computer%' finds all book titles with the word 'computer' anywhere in the book title. |
_ (underscore) | Any single character. | WHERE au_fname LIKE '_ean' finds all four-letter first names that end with ean (Dean, Sean, and so on). |
[ ] | Any single character within the specified range ([a-f]) or set ([abcdef]). | WHERE au_lname LIKE '[C-P]arsen' finds author last names ending with arsen and beginning with any single character between C and P, for example Carsen, Larsen, Karsen, and so on. |
[^] | Any single character not within the specified range ([^a-f]) or set ([^abcdef]). | WHERE au_lname LIKE 'de[^l]%' all author last names beginning with de and where the following letter is not l. |
%表示零长度或任意长度的字符串。_表示一个字符。[]表示在某范围区间的一个字符。[^]表示不在某范围区间的一个字符
比如:
var
q
=
(
from
c
in
db.Customers
where
SqlMethods.
Like
(c.CustomerID, "A_O_T")
select
c).ToList();
就用_代表一个字符。其生成sql为
SELECT
[
t0
]
.
[
CustomerID
]
,
[
t0
]
.
[
CompanyName
]
,
[
t0
]
.
[
ContactName
]
,
[
t0
]
.
[
ContactT
itle
]
,
[
t0
]
.
[
Address
]
,
[
t0
]
.
[
City
]
,
[
t0
]
.
[
Region
]
,
[
t0
]
.
[
PostalCode
]
,
[
t0
]
.
[
Coun
try
]
,
[
t0
]
.
[
Phone
]
,
[
t0
]
.
[
Fax
]
FROM
[
dbo
]
.
[
Customers
]
AS
[
t0
]
WHERE
[
t0
]
.
[
CustomerID
]
LIKE
@p0
--
@p0: Input String (Size = 5; Prec = 0; Scale = 0) [A_O_T]
对于Not Like,也很简单,加个取非就是。
var
q
=
(
from
c
in
db.Customers
where
!SqlMethods.
Like
(c.CustomerID, "A_O_T")
select
c).ToList();
SqlMethods.Like还有一个参数,叫escape Character,其将会被翻译成类似下面的语句。
SELECT
columns
FROM
table
WHERE
column
LIKE
'
%/%%
'
ESCAPE
'
/
'
escape 是因为某字段中含有特殊字符,比如%,_ [ ]这些被用作通配符的。这时就要用到Escape了。这是sql server的事情了。详细情况请参考:
http://msdn2.microsoft.com/en-us/library/Aa933232(SQL.80).aspx