最近看了多篇园内的LINQ中介绍SeletMany()的帖子,基本上都是这么说的:
会被翻译成SelectMany需要满足2个条件。1,query语句中没有join和into,2,必须出现EntitySet。
我个人认为这种说法是不正确的,应该这样描述:
第一,表达式中未出现Join;第二,要出现两个或以上的from子句。如果第二个from子句选择的是第一个from子句数据源的EntitySet<T>集合,则被转换为Left Join,如果两个from子句是独立的则会被转换为Cross Join。
不知道正确与否,请大家一起讨论一下,下面是详细的讲解:
14.6.3 SelectMany方法
一个LINQ表达式被系统转为SelectMany时,要符合两个条件:第一,表达式中未出现Join;第二,要出现两个或以上的from子句。如果第二个from子句选择的是第一个from子句数据源的EntitySet<T>集合,则被转换为Left Join,如果两个from子句是独立的则会被转换为Cross Join。
再来分析一下上节中的例子:查询Order_Details中单位价格大于10元的订单信息。在数据库中,Orders表是Orders_Details表的主表,通过Orders_Details表中定义的外键“FK_Order_Details_Orders”关联起来的,表现在Orders表的封装类中即为EntitySet<T>类型:
[Association(Name="Orders_Order_Details", Storage="_Order_Details", OtherKey="OrderID")]
public EntitySet<Order_Details> Order_Details
{
get{return this._Order_Details;}
set{this._Order_Details.Assign(value);}
}
在Orders_Details表中也有对应的主表定义:
[Association(Name="Orders_Order_Details", Storage="_Orders", ThisKey="OrderID", IsForeignKey=true)]
public Orders Orders
{
get{return this._Orders.Entity;}
set{……}
}
如果使用了EntitySet<T>,则不需要使用join od in nwdb.Order_Details on o.OrderID equals od.OrderID,因为“OrderID”这个关系键是被记录在EntitySet<T>的[AssociationAttribute]中的OtherKey和ThisKey。
如:
var result =
from o in nwdb.Orders
from od in o.Order_Details
where od.UnitPrice > 10
select new { o.OrderID, o.EmployeeID, od.ProductID, od.UnitPrice };
对应的操作方法则为SelectMany:
var result = nwdb.Orders.SelectMany(o => o.Order_Details, (o1, od) => new { o1.OrderID, o1.EmployeeID, od.ProductID, od.UnitPrice }).Where(od => od.UnitPrice > 10);
转换成的SQL为:
SELECT [t0].[OrderID], [t0].[EmployeeID], [t1].[ProductID], [t1].[UnitPrice]
FROM [dbo].[Orders] AS [t0], [dbo].[Order Details] AS [t1]
WHERE ([t1].[UnitPrice] > @p0) AND ([t1].[OrderID] = [t0].[OrderID])
此段SQL语句与上一节中使用不用Join子句而代用where子句时生成的SQL是一致的。因此使用EntitySet<T>后,省略了关系列(OrderID)的条件where o.OrderID == od.OrderID,而是由系统自动由OtherKey和ThisKey生成。
使用到EntitySet<T>的情况有很多,并不一定在from子句中,在select、where等其它子句中也可以,如上例可以写成:
var result =
from od in nwdb.Order_Details
where od.UnitPrice > 10
select new { od.OrderID, od.Orders.EmployeeID, od.ProductID, od.UnitPrice };
这种写法是在select中使用了od.Orders.EmployeeID去访问主表,对应的SQL语句会使用Inner Join子句:
SELECT [t0].[OrderID], [t1].[EmployeeID], [t0].[ProductID], [t0].[UnitPrice]
FROM [dbo].[Order Details] AS [t0]
INNER JOIN [dbo].[Orders] AS [t1] ON [t1].[OrderID] = [t0].[OrderID]
WHERE [t0].[UnitPrice] > @p0
下面再看一个在where中使用的例子,如:查询EmployeeID为1的销售人员下的订单中包含单价大于10元的订单详情。
var result =
from od in nwdb.Order_Details
where od.UnitPrice > 10 && od.Orders.EmployeeID == 1
select od;
对应的SQL语句为:
SELECT [t0].[OrderID], [t0].[ProductID], [t0].[UnitPrice], [t0].[Quantity], [t0].[Discount]
FROM [dbo].[Order Details] AS [t0]
INNER JOIN [dbo].[Orders] AS [t1] ON [t1].[OrderID] = [t0].[OrderID]
WHERE ([t0].[UnitPrice] > @p0) AND ([t1].[EmployeeID] = @p1)
但以上两例不会转换为SelectMany方法,只需要使用Where方法就可以了。
再看一个三表联合查询的例子,如查找由EmpolyeeID=1的销售人员签订的单价在90元以上订单中的产品名称。
这个查询要涉及到三个表,Orders表(EmployeeID=1)、Order_Details表(单价90元以上)、Products表(产品名称),这三个表是有关联的:
Orders表与Orders_Details表是一对多的关系,关联键为OrderID;Products表与Order_Details也是一对多的关系,关联键是ProductID。其LINQ表达式如下:
var result =
from od in nwdb.Order_Details
where od.Orders.EmployeeID == 1 && od.UnitPrice>80
select new { od.Orders.OrderID, od.Orders.CustomerID, od.UnitPrice, od.Products.ProductName };
这种写法并不会转换为SelectMany方法,而直接使用Where方法就行了:
nwdb.Order_Details.Where(od => ((od.Orders.EmployeeID = Convert(1)) && (od.UnitPrice > 80))).Select(od => new (OrderID = od.Orders.OrderID, CustomerID = od.Orders.CustomerID, UnitPrice = od.UnitPrice, ProductName = od.Products.ProductName))
转换成的SQL语句使用Left Join将其它两个表进行左连接:
SELECT [t1].[OrderID], [t1].[CustomerID], [t0].[UnitPrice], [t2].[ProductName]
FROM [dbo].[Order Details] AS [t0]
INNER JOIN [dbo].[Orders] AS [t1] ON [t1].[OrderID] = [t0].[OrderID]
INNER JOIN [dbo].[Products] AS [t2] ON [t2].[ProductID] = [t0].[ProductID]
WHERE ([t1].[EmployeeID] = @p0) AND ([t0].[UnitPrice] > @p1)
如果将LINQ写成:
var result =
from o in nwdb.Orders
from od in nwdb.Order_Details
where o.EmployeeID == 1 && od.UnitPrice>80
select new { o.OrderID, o.CustomerID, od.UnitPrice, od.Products.ProductName };
这种写法的意义为将Orders表和Order_Details进行Cross Join,即将Orders表乘以Order_Details表的笛卡尔积,例如Orders表中有10条记录,Order_Details中有20条记录,结果将有200条记录,这就是所谓的“多对多查询”。
其对应的操作方法为:
var result = nwdb.Orders.SelectMany( nw => nwdb.Order_Details, (o, od) => new { o = o, od = od }).Where(t => t.o.EmployeeID == 1 && t.od.UnitPrice > 80).Select(tmp => new { tmp.o.OrderID, tmp.o.CustomerID, tmp.od.UnitPrice, tmp.od.Products.ProductName });
在SelectMany方法的第一参数中,只引入Order_Details表,而不是o=>o. Order_Details这样的EntitySet,否则就会是Left Join连接。
SQL语句如下:
SELECT [t0].[OrderID], [t0].[CustomerID], [t1].[UnitPrice], [t2].[ProductName]
FROM [dbo].[Orders] AS [t0]
CROSS JOIN [dbo].[Order Details] AS [t1]
INNER JOIN [dbo].[Products] AS [t2] ON [t2].[ProductID] = [t1].[ProductID]
WHERE ([t0].[EmployeeID] = @p0) AND ([t1].[UnitPrice] > @p1)
如果两个表无任何联系,也可以使用SelectMany()方法,先来看看LINQ查询表达式:
var result =
from em in nwdb.Employees
from re in nwdb.Region
select new { em.EmployeeID, re.RegionDescription };
对应的操作方法为:
var result = nwdb.Employees.SelectMany(em => nwdb.Region, (em, re) => new { EmployeeID = em.EmployeeID, RegionDescription = re.RegionDescription });
其查询的结果就是Employees表(9条记录)和Region表(4条记录)的迪卡尔积,总共36条记录。