快速业务通道

BlogEngine.Net架构与源代码分析系列part9:开发扩展(上)

作者 佚名技术 来源 NET编程 浏览 发布时间 2012-05-19

 

如果我们使用Select的话也可以使用如下的方法得到一样的结果:

   1: IEnumerable<string[]> query =
   2:  
   3:   fullNames.Select (
   4:  
   5:     name => name.Split( ));
   6:  
   7: foreach (string[] stringArray in query)
   8:  
   9:   foreach (string name in stringArray)
  10:  
  11:       Console.Write (name + “,”);

 

SelectMany的好处就是它生成了单一扁平的结果序列.

通过一个额外的生成符——额外的from语句可以让复合语法支持SelectMany.这个from关键字拥有两层意思,在查询开始的那一个引出了迭代变量和输入序列,查询中其他地方出现的from关键字将被翻译成SelectMany:

   1: IEnumerable<string> query =
   2:  
   3:   from fullName in fullNames
   4:  
   5:   from name in fullName.Split()
   6:  
   7:     select name;

 

注意,第二个from引出了一个新的查询变量——这本例中,是name.从那时起,这个新的变量将会变成迭代变量,而原始的迭代变量将会变成外部迭代变量.

外部迭代变量

在我们前面的例子中,fullName在SelectMany之后变成了一个外部迭代变量. 这个外部迭代变量依然在可用范围内直到查询结束或者到达一个into从句:

   1: IEnumerable<string> query =
   2:  
   3:   from fullName in fullNames        // 外部变量
   4:  
   5:   from name in fullName.Split( )    // 迭代变量
   6:  
   7:   select name + ” came from “ + fullName;
   8:  
   9: Anne came from Anne Williams
  10:  
  11: Williams came from Anne Williams
  12:  
  13: John came from John Fred Smith

 

如果要使用Lambda语法的话,尤其在有where或者orderby的情况下,会更加复杂一点,这里有一个技巧:

   1: from fullName in fullNames
   2:  
   3: from x in
   4:  
   5:   fullName.Split( )
   6:  
   7:           .Select (name => new { name, fullName } )
   8:  
   9: orderby x.fullName, x.name
  10:  
  11: select x.name + ” came from “ + x.fullName;

 

我们使用了一个匿名类型来搭载临时数据,类似let从句的作用.最后我们可以得到一个完整的Lambda语法的查询:

   1: IEnumerable<string> query = fullNames
   2:  
   3:   .SelectMany (fName =>
   4:  
   5:      fName.Split( )
   6:  
   7:           .Select (name => new { name, fName } ))
   8:  
   9: .OrderBy (x => x.fName)
  10:  
  11: .ThenBy (x => x.name)
  12:  
  13: .Select (x => x.name + ” came from “ + x.fName);

 

复合查询的思考

正如我们前面看到的例子,如果我们需要外部迭代变量, 那么使用复合语法是一个很好的选择,因为它的语义让你可以更加直观的思考.

编写额外的生成器主要有两个基础的模式, 第一个是扩展和平整结果,为了做到这一点,我们可以通过调用已有查询变量的属性或者方法来完成,上面的例子我们就使用了这个方法.

在LINQ to SQL中一个类似的查询就是当你扩展关联的子属性,以下的实例列出了所有的客户和他们对应的采购单:

   1: IEnumerable<string> query =
   2:  
   3:   from c in dataContext.Customers
   4:  
   5:   from p in c.Purchases
   6:  
   7:   select c.Name + ” bought a “ + p.Description;
   8:  
   9: Tom bought a Bike
  10:  
  11: Tom bought a Holiday
  12:  
  13: Dick bought a Phone
  14:  
  15: Harry bought a Car

 

另外一个模式就是执行一个交叉连接,为了做到这一点, 我们的selector表达式返回的将会是一个序列而不是一个迭代变量了:

   1: int[] numbers = { 1, 2, 3 };
   2:  
   3: string[] letters = { “a”, “b” };
   4:  
   5: IEnumerable<string> query = from n in numbers
   6:  
   7:                             from l in letters
   8:  
   9:                             select n.ToString( ) + l;
  10:  
  11: RESULT: { “1a”, “1b”, “2a”, “2b”, “3a”, “3b” }

 

这种风格的查询就是SelectMany风格关联的基础.

使用SelectMany做关联

我们可以使用SelectMany,并通过过滤一个交叉结果集来关联两个序列.例如, 假设我们要安排一场比赛的选手对决, 我们使用以下的查询:

   1: string[] players = { “Tom”, “Jay”, “Mary” };
   2:  
   3: IEnumerable<string> query =
   4:  
   5:   from name1 in players
   6:  
   7:   from name2 in players
   8:  
   9:   select name1 + ” vs “ + name2;
  10:  
  11: RESULT: {“Tom vs Tom”, “Tom vs Jay”, “Tom vs Mary”,
  12:  
  13:          “Jay vs Tom”, “Jay vs Jay”, “Jay vs Mary”,
  14:  
  15:          “Mary vs Tom”, “Mary vs Jay”, “Mary vs Mary”}

 

但这并非我们期望的结果,很明显,选手自己并不需要与自己比赛,因此我们必须要过滤这些不符合条件的输出元素:

   1: IEnumerable<string> query =
   2:  
   3:   from name1 in players
   4:  
   5:   from name2 in players
   6:  
   7:   where name1.CompareTo (name2) > 0
   8:  
   9:   orderby name1, name2
  10:  
  11:   select name1 + ” vs “ + name2;
  12:  
  13: RESULT: { “Jay vs Mary”, “Jay vs Tom”, “Mary vs Tom” }

 

此过滤断言构成了连接条件,我们的查询可以被称为非等连接,因为连接条件没有使用等式操作符.

LINQ to SQL当中的SelectMany

LINQ to SQL当中的SelectMany可以被用于执行交叉连接,非等连接,inner join以及left outer join.我们可以像使用Select一样使用SelectMany,不同的地方在于SelectMany返回的是一个扁平的数据结构而不是层级机构.

交叉连接就像我们上面例举的类似,以下的查询显示了每一个客户对应的每一个采购:

   1: var query =
   2:  
   3:   from c in dataContext.Customers
   4:  
   5:   from p in dataContext.Purchases
   6:  
   7:   select c.Name + ” might have bought “ + p.Description;

 

更通常的情况是我们只想列出客户及其对应的采购单,为此,我们可以增加一个where语句和一个连接断言,结果将会得到一个类似SQL语法的查询:

   1: var query =
   2:  
   3:   from c in dataContext.Customers
   4:  
   5:   from p in dataContext.Purchases
   6:  
   7:   where c.ID == p.CustomerID
   8:  
   9:   select c.Name + ” bought a “ + p.Description;

 

如果在你的LINQ to SQL实体类中存在关联属性,你可以使用它们来避免交叉连接:

   1: from c in dataContext.Customers
   2:  
   3: from p in c.Purchases
   4:  
   5: select new { c.Name, p.Description };

 

这样写的好处是我们消除了关联断言,但结果是一致的.

我们还可以增加一个Where语句来做进一步的过滤,例如,我们只想列出那些名字以J开头的客户:

   1: from c in dataContext.Customers
   2:  
   3: where c.Name.StartsWith (“J”)
   4:  
   5: from p in c.Purchases
   6:  
   7: select new { c.Name, p.Description };

 

你也可以将where语句往下移一行,对于这个LINQ to SQL查询,结果是一致的.然后,如果是本地查询的话,这会得到一个相对差一些的性能.对于本地查询,我们总是应该使用before joining.

我们还可以通过增加from语句引出新的表,例如,每一个采购单都有采购明细:

   1: from c in dataContext.Customers
   2:  
   3: from p in c.Purchases
   4:  
   5: from pi in p.PurchaseItems
   6:  
   7: select new { c.Name, p.Description, pi.DetailLine };

 

另外,如果通过关联属性从父表开始选择数据的话我们不需要添加from语句.

   1: from c in dataContext.Customers
   2:  
   3: select new {
   4:  
   5:              Name = c.Name,
   6:  
   7:              SalesPerson = c.SalesPerson.Name
   8:  
   9:            };

 

在这个例子中,我们并没有使用SelectMany因为没有子集合需要处理.关联属性返回了一个单一的项.

SelectMany和Outer join

在前面使用select的left outer join例子中:

   1: from c in dataContext.Customers
   2:  
   3: select new {
   4:  
   5:              c.Name,
   6:  
   7:              Purchases =
   8:  
   9:                from p in c.Purchases
  10:  
  11:                where p.Price > 1000
  12:  
  13:                select new { p.Description, p.Price }
  14:  
  15:            };

 

每一个客户都会被包括,不管它是否包含任何订单. 假设我们现在要使用SelectMany重新编写这个查询:

   1: from c in dataContext.Customers
   2:  
   3: from p in c.Purchases
   4:  
   5: where p.Price > 1000
   6:  
   7: select new { c.Name, p.Description, p.Price };

 

然而这个查询得到的是一个inner join的效果,只有包含Price>1000的采购单的客户才会被包括进来. 为了能够得到一个left outer join结果,我们可以在内部序列上使用DefaultIfEmpty查询操作符,此方法对于没有包含任何元素的输入序列返回null:

   1: from c in dataContext.Customers
   2:  
   3: from p in c.Purchases.DefaultIfEmpty()
   4:  
   5: select new {
   6:  
   7:              c.Name,
   8:  
   9:              p.Description,
  10:  
  11:              Price = (decimal?) p.Price 
  12:  
  13:            };

 

这个查询在LINQ to SQL当中工作良好,并且将会返回所有的客户即便他们不包含任何的采购单. 但对于本次查询,当p等于null的时候将会得到一个NullReferenceException异常, 不过我们还是可以进一步完善它:

   1: from c in dataContext.Customers
   2:  
   3: from p in c.Purchases.DefaultIfEmpty( )
   4:  
   5: select new
   6:  
   7: {
   8:  
   9:   c.Name,
  10:  
  11:   Descript = p == null ? null : p.Description,
  12:  
  13:   Price = p == null ? (decimal?) null : p.Price
  14:  
  15: };

 

接下来让我们引出Price过滤器,我们不能简单的像以前做的那样直接加上where语句,因为它会在DefaultIfEmpty之后执行,正确的解决方案是我们需要一个子查询并且必须让Where语句在DefaultIfEmpty之前:

   1: from c in dataContext.Customers
   2:  
   3: from p in c.Purchases.Where (p => p.Price > 1000)
   4:  
   5:                      .DefaultIfEmpty( )
   6:  
   7: select new
   8:  
   9: {
  10:  
  11:   c.Name,
  12:  
  13:   Descript = p == null ? null : p.Description,
  14:  
  15:   Price = p == null ? (decimal?) null : p.Price
  16:  
  17: };

 

这个查询在LINQ to SQl当中将会被很好的转换成left outer join, 并且对于编写这类型的查询这是一个比较有效率的做法. 待续!

凌众科技专业提供服务器租用、服务器托管、企业邮局、虚拟主机等服务,公司网站:http://www.lingzhong.cn 为了给广大客户了解更多的技术信息,本技术文章收集来源于网络,凌众科技尊重文章作者的版权,如果有涉及你的版权有必要删除你的文章,请和我们联系。以上信息与文章正文是不可分割的一部分,如果您要转载本文章,请保留以上信息,谢谢!

分享到: 更多

Copyright ©1999-2011 厦门凌众科技有限公司 厦门优通互联科技开发有限公司 All rights reserved

地址(ADD):厦门软件园二期望海路63号701E(东南融通旁) 邮编(ZIP):361008

电话:0592-5908028 传真:0592-5908039 咨询信箱:web@lingzhong.cn 咨询OICQ:173723134

《中华人民共和国增值电信业务经营许可证》闽B2-20100024  ICP备案:闽ICP备05037997号