最近看了多篇园内的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]中的OtherKeyThisKey

如:

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,而是由系统自动由OtherKeyThisKey生成。

 

使用到EntitySet<T>的情况有很多,并不一定在from子句中,在selectwhere等其它子句中也可以,如上例可以写成:

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中使用的例子,如:查询EmployeeID1的销售人员下的订单中包含单价大于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表是一对多的关系,关联键为OrderIDProducts表与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条记录。

posted on 2008-09-04 11:41  李彦  阅读(5455)  评论(4编辑  收藏  举报