C#学习笔记(八)—–LINQ查询创建策略

这篇文章是引用博客园的,这篇文章应该也是出自C# in a nutshell,但是翻译的比较好,容易理解。地址为:http://www.cnblogs.com/lifepoem/archive/2011/10/31/2230175.html

LINQ查询创建策略

通过前面几篇的讨论学习,我们已经了解了怎么去写一个比较简单的LINQ查询,也知道了创建LINQ查询的两种方式:方法语法和查询表达式。在这里,我们会描述三种创建复杂LINQ查询的创建策略:

渐进式创建查询

渐进式创建查询就是通过链接查询运算符的方式来创建LINQ查询。因为每一个查询运算符返回一个装饰者sequence,所以我们可以在其之上继续调用其它查询运算符。使用这种方式有如下几个优点:

  • 使得查询易于编写
  • 我们可以根据条件来决定是否调用某个查询运算符,如:if (includeFilter) query = query.Where(…)
    渐进的方式通常是对查询的创建有益的,考虑如下的例子:我们需要在名字列表中去除所有名字的元音字母,然后对长度大于2的名字进行排序。在方法语法中,我们可以在一个表达式中完成这个查询:
 string[] names = { "Tom", "Dick", "Harry", "Mary", "Jay" };
            IEnumerable<string> query = names
                .Select(n => n.Replace("a", "").Replace("e", "").Replace("i", "")
                    .Replace("o", "").Replace("u", ""))
                .Where(n => n.Length > 2)
                .OrderBy(n => n);   // Result: Dck, Hrry, Mry

如果直接将上面的query改写成查询表达式语法,我们将会遇到麻烦,这时因为查询表达式要求要以Select或Group结束。但在上面的查询中,我们需要先做Select(结果投影)去除元音字母,再做过滤和排序。如果把Select直接放到后面,那么结果将会被改变。幸运的是,我们还是有办法让查询表达式来完成上面的工作,得到我们期望的结果。 第一种方式就是查询表达式的渐进式(分步)查询:

IEnumerable<string> query =
                from n in names
                select n.Replace("a", "").Replace("e", "").Replace("i", "")
                    .Replace("o", "").Replace("u", "");

            query = from n in query
                    where n.Length > 2
                    orderby n
                    select n;   // Result: Dck, Hrry, Mry

into关键字

在我们前面查询表达式的例子中,select关键字的出现也就意味着查询的结束了。而into关键字让我们在结果投影之后还可以继续我们的查询,它是对分步构建查询表达式的一种简写方式。现在我们可以使用into关键字来重写上例中的查询:

IEnumerable<string> query =
                    from n in names
                    select n.Replace("a", "").Replace("e", "").Replace("i", "")
                            .Replace("o", "").Replace("u", "")
                    into noVowel
                    where noVowel.Length > 2
                    orderby noVowel
                    select noVowel;   // Result: Dck, Hrry, Mry

我们只能在select和group子句后面使用into关键字,它会重新开始一个查询,让我们可以继续引入where, orderby和select子句。尽管表面上看,我们重新创建了一个新的查询,但当上面的查询被翻译成方法语法时,它只是一个查询,一个链接了多个运算符的查询,所以上面的写法不会造成性能问题。

需要注意的是,所有的查询变量在into关键字之后都不再可见,下面的例子就说明了这一点:

 var query =
                from n1 in names
                select n1.ToUpper()
                into n2                     //into之后只有n2可见
                    where n1.Contains("x")  //Error: n1不可见
                    select n2;

要理解其原因,我们只要看看它编译器为它翻译成对应的方法语法就能知晓:

 var query = names
                .Select(n1 => n1.ToUpper())
                .Where(n2 => n1.Contains("x")); //Error: n1不再可见,lambda表达式中只有n2

包装查询

渐进式查询创建方式可以通过在一个查询中嵌入另一个查询来改写,这样可以把多个查询组合成单个查询,即:

var tempQuery = tempQueryExpr

var finalQuery = from … in (tempQuery)

可以改写为:

var query = from … in (tempQueryExpr)

上面两种方式以及into关键字的工作方式是一样的,编译器都会把他们翻译成一个链接查询运算符。请看下面的示例:

string[] names = { "Tom", "Dick", "Harry", "Mary", "Jay" };

            // 渐进式查询(Progressive query building)
            IEnumerable<string> query =
                from n in names
                select Regex.Replace(n, "[aeiou]", "");

            query = from n in query where n.Length > 2 orderby n select n;

            // 用包装查询方式进行改写(Wrapping Queries)
            IEnumerable<string> query2 =
                from n1 in
                    (
                        from n2 in names
                        select Regex.Replace(n2, "[aeiou]", "")
                    )
                where n1.Length > 2
                orderby n1
                select n1;

            // 与上面等价的方法语法
            IEnumerable<string> query3 = names
                .Select(n => Regex.Replace(n, "[aeiou]", ""))
                .Where(n => n.Length > 2)
                .OrderBy(n => n);

数据转换

LINQ中的数据转换,也叫结果投影,是指LINQ查询select的输出。到目前为止,我们还只是看到了输出单个标量元素的示例。通过使用对象初始化器,我们可以输出更为复杂的结果类型。比如下面的示例,当我们在把姓名中的元音字母去掉之后,我还需要保存姓名的原始版本:

  class TempProjectionItem
        {
            public string Original;
            public string Vowelless;
        }

        static void TestProjectionStrategy()
        {
            string[] names = { "Tom", "Dick", "Harry", "Mary", "Jay" };

            IEnumerable<TempProjectionItem> temp =
                from n in names
                select new TempProjectionItem
                {
                    Original = n,
                    Vowelless = Regex.Replace(n, "[aeiou]", "")
                };
            //我们可以继续在结果中查询
            IEnumerable<string> query =
                from item in temp
                where item.Vowelless.Length > 2 //按去除元音字母版本过滤
                select item.Original;           //结果为姓名原始版本
        }

匿名类型

上面我们自己定义了类型TempProjectionItem来存放查询的结果。通过使用匿名类型,我们可以省去这种中间类型的定义,而由编译器来帮我们完成:

 var intermediate = from n in names
                               select new
                               {
                                   Original = n,
                                   Vowelless = Regex.Replace(n, "[aeiou]", "")
                               };
            IEnumerable<string> query = from item in intermediate
                                        where item.Vowelless.Length > 2
                                        select item.Original;

需要注意的是,因为匿名类型的确切类型名是由编译器自动产生的,因此intermediate的类型为:IEnumerable 。我们来声明这种类型的唯一方式就是使用var关键字,这时,var不只是更加简洁,而且也是必需的手段。

let关键字

let关键字让我们可以在保持范围变量的同时引入新的查询变量。比如上面的示例,我们可以用let关键字作如下改写:

 string[] names = { "Tom", "Dick", "Harry", "Mary", "Jay" };
            var query = from n in names
                        let Vowelless = Regex.Replace(n, "[aeiou]", "")
                        where Vowelless.Length > 2
                        select n;   //正是因为使用了let,此时n仍然可见

let关键字非常灵活和方便,就像例子看到的那样。而且,我们可以使用多个let关键字,并且后面的 let表达式可以引用前一个let关键字引入的变量。
本系列LINQ之路文章到此已经对LINQ to Objects进行了比较详细的讨论,接下去的打算是对解释查询(LINQ to SQL, LINQ to XML、entity framework等)以及更多的查询运算符进行讨论和学习。希望系列文章能对阅者有些帮助,也期待大家的意见和提议^_^。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值