LINQ之路 4:LINQ方法语法

书写LINQ查询时又两种语法可供选择:方法语法(Fluent Syntax)和查询语法(Query Expression)。

LINQ方法语法是非常灵活和重要的,我们在这里将描述使用链接查询运算符的方式来创建复杂的查询,方法语法的本质是通过扩展方法和Lambda表达式来创建查询。C# 3.0对于LINQ表达式还引入了声明式的查询语法,通过查询语法写出的查询比较类似于SQL查询。本篇会对LINQ方法语法进行详细的介绍。

当然,.NET公共语言运行库(CLR)并不具有查询语法的概念。所以,编译器会在程序编译时把查询表达式转换为方法语法,即对扩展方法的调用。所以使用方法语法会让我们更加接近和了解LINQ的实现和本质,并且一些查询只能表示为方法调用,如检索序列中的最大值、最小值元素的查询,他们在查询语法中就没有对应的实现。但另一方面,查询语法通常会比较简单和易读。不管怎样,这两种语法和互相补充和兼容的,我们可以在一个查询中混合使用方法语法和查询语法。

链接查询运算符

在LINQ介绍中,我们示范了使用单个查询运算符创建的查询。如果需要创建更加复杂的查询,我们可以在表达式之后添加其他查询运算符,产生一个查询链。如下例:查询出所有含有字母”a”的姓名,按长度进行排序,然后把结果全部转换成大写格式。

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

            IEnumerable<string> query = names                .Where(n => n.Contains("a"))                .OrderBy(n => n.Length)                .Select(n => n.ToUpper());

            foreach (string name in query) Console.WriteLine(name);        }

        // Result:        JAY        MARY        HARRY

就像在本例中所示,当链接使用查询运算符时,一个运算符的输出sequence会成为下一个运算符的输入sequence,其结果形成了一个sequence的传输链,如图所示:

图:链接查询运算符

Where, OrderyBy, Select这些标准查询运算符对应Enumerable类中的相应扩展方法。Where产生一个经过过滤的sequence;OrderBy生成输入sequence的排序版本;Select得到的序列中的每个元素都经过了给定lambda表达式的转换。

下面是Where, OrderBy, Select这几个扩展方法的签名:

        public static IEnumerable<TSource> Where<TSource>            (this IEnumerable<TSource> source, Func<TSource, bool> predicate)

        public static IEnumerable<TSource> OrderBy<TSource, TKey>            (this IEnumerable<TSource> source, Func<TSource, TKey> keySelector)

        public static IEnumerable<TResult> Select<TSource, TResult>            (this IEnumerable<TSource> source, Func<TSource, TResult> selector)

扩展方法的重要性

在LINQ表达式中,正是扩展方法让LINQ查询运算符的链接成为了可能。因为运算符本身是对IEnumerable<T>类型的扩展,并且返回IEnumerable<T>类型的结果。我们可以比较一下使用扩展方法和使用静态方法的区别,结果会一目了然。扩展方法非常自然地反映了从左到右的数据流向,同时保持lambda表达式与查询运算符的位置一致性。

            //   extension methods make LINQ elegant            IEnumerable<string> query = names                .Where(n => n.Contains("a"))                .OrderBy(n => n.Length)                .Select(n => n.ToUpper());

            //   static methods lose query‘s fluency            IEnumerable<string> query2 =                Enumerable.Select(                    Enumerable.OrderBy(                        Enumerable.Where(names, n => n.Contains("a")                        ), n => n.Length                    ), n => n.ToUpper()                );

创建Lambda表达式

在上例中,我们向Where运算符提供了如下Lambda表达式:n => n.Contains(“a”)。对各个查询运算符来说,Lambda表达式的目的不尽相同。对于Where,它决定了一个element是否包含在结果sequence中;对于OrderBy,它把每个element映射到比较的键值;而对于Select,lambda表达式则决定了输入sequence中的元素要怎么样的转换再放入输出sequence中。

关于Lambda表达式的详细介绍,请参考LINQ 之路3:C# 3.0的语言功能(下)。

通常来说,查询运算符会对每一个输入sequence中的element来调用我们提供的Lambda表达式,这样就给了我们一个实现自己的逻辑,从而得到自己需要结果的机会。我们可以看一下.NET Framework对Enumerable.Where的实现:

        public static IEnumerable<TSource> Where<TSource>(            this IEnumerable<TSource> source, Func<TSource, bool> predicate)        {            foreach (TSource element in source)                if (predicate(element))                    yield return element;        }

标准查询运算符使用了通用的Func委托,Func是一组定义在System.Linq(谢谢 A_明~坚持的指正,此处应为System)命名空间中的通用委托。它接受一系列的输入参数和一个返回值,返回值对应最后一个参数定义。所以,Func<TSource, bool>委托匹配 TSource => bool表达式,接受TSource输入参数,返回一个bool值。

Lambda表达式和元素类型

标准查询运算符使用统一的类型名称:


通用类型名称


用途


TSource


Input sequence中的element类型


TResult


Output sequence中的element 类型(如果和TSource不相同)


TKey


使用sorting、grouping、joining等的键值类型

TSource由输入sequence决定,而TResult和TKey则从我们提供的Lambda表达式推断得到。

比如:Select查询运算符的签名如下:

        public static IEnumerable<TResult> Select<TSource,TResult>(            this IEnumerable<TSource> source, Func<TSource,TResult> selector)

Func<TSource,TResult>匹配TSource => TResult的Lambda表达式,接受一个输入参数TSource,返回TResult。因为TSource和TResult是不同的类型,所以我们的Lambda表达式甚至可以改变输入element的数据类型。下面的示例就把string类型元素转换为int类型元素:

        static void TestSelectOperator(){            string[] names = { "Tom", "Dick", "Harry", "Mary", "Jay" };            // 编译器将会从Lambda表达式 n => n.Length推断出TResult为int类型            IEnumerable<int> query = names.Select(n => n.Length);

            foreach (int length in query)                Console.Write(length + "|");    //  3|4|5|4|3        }

而对Where查询运算符来讲,它并不需要对输出element进行类型推断,因为它只是对输入elements进行过滤而不作转换,因此输出element和输入element具有相同的数据类型。

对于OrderBy查询运算符来讲,Func<TSource, TKey>把输入元素映射至一个排序键值。TKey由Lambda表达式的结果推断出来,比如我们可以按长度或按字母顺序对names数组进行排序:

            string[] names = { "Tom", "Dick", "Harry", "Mary", "Jay" };            IEnumerable<string> sortedByLength, sortedAlphabetically;            sortedByLength = names.OrderBy(n => n.Length);  // int key            sortedAlphabetically = names.OrderBy(n => n);   // string key

 

其他查询运算符

并不是所有的查询运算符都返回一个sequence。元素(element)运算符会从输入sequence中获取单个元素,如:First,Last和ElementAt:

            int[] numbers = { 10, 9, 8, 7, 6 };              int firstNumber = numbers.First();                  // 10            int lastNumber = numbers.Last();                    // 6            int secondNumber = numbers.ElementAt(1);            // 9            int lowestNumber = numbers.OrderBy(n => n).First(); // 6

集合(aggregation) 运算符返回一个标量值,通常是数值类型:

            int count = numbers.Count();    // 5            int min = numbers.Min();        // 6

判断运算符返回一个bool值:

            bool hasTheNumberNine = numbers.Contains(9);    // true            bool hasElements = numbers.Any();               // true            bool hasAnOddElement = numbers.Any(n => (n % 2) == 1);  //true

因为这些运算符并不是返回一个sequence,所以我们不能再这些运算符之后链接其他运算符,换句话讲,他们一般出现在查询的最后面。

还有一些查询运算符接受两个输入sequence,比如Concat把一个sequence添加到另外一个sequence后面;Union与Concat类似,但是会去除相同的元素:

            int[] seq1 = { 1, 2, 2, 3 };            int[] seq2 = { 3, 4, 5 };            IEnumerable<int> concat = seq1.Concat(seq2);    // { 1, 2, 2, 3, 3, 4, 5 }            IEnumerable<int> union = seq1.Union(seq2);      // { 1, 2, 3, 4, 5 }

本篇只是对几个常用的查询运算符做了介绍,在后续篇章中,我会对更多地运算符进行更详细的讨论。

时间: 2024-10-18 09:46:57

LINQ之路 4:LINQ方法语法的相关文章

LINQ:开始使用 LINQ(五)- LINQ 中的查询语法和方法语法

开始使用 LINQ(五)- LINQ 中的查询语法和方法语法 在表示语言集成查询 (LINQ) 使用 LINQ 性查询语法,文档中的多数查询编写.但是,编译代码时,必须将查询语法转换为方法,这就需要 .NET 公共语言运行时 (CLR).这些方法调用标准查询运算符的名称类似 Where.Select.GroupBy.Join.Max和 Average.可以调用这些方法直接使用方法语法而不是查询语法. 查询语法和方法语法语义相同,但是,许多人员发现查询语法更简单.更易于阅读.某些查询必须表示为方法

LINQ之路 7:子查询、创建策略和数据转换

在前面的系列中,我们已经讨论了LINQ简单查询的大部分特性,了解了LINQ的支持计术和语法形式.至此,我们应该可以创建出大部分相对简单的LINQ查询.在本篇中,除了对前面的知识做个简单的总结,还会介绍几种创建更复杂查询的方式,让我们在面对更复杂的场景时也能轻松面对,包括:子查询.创建策略和数据转换. 子查询 在创建一个复杂的查询时,通常我们需要用到子查询.相信大家都记得SQL查询里的子查询,在创建LINQ查询时也是如此.在LINQ中,对于方法语法,一个子查询包含在另外一个查询的lambda表达式

linq to entity不识别方法"System.String ToString()"

将班级id以字符串形式输入如:"1111,1112,1113".数据库里的id为int型,在数据路里找到匹配的相应班级转换成列表.在这里爆出问题:不识别方法"System.String ToString()",跪求大神提出解决方案.public IEnumerable<Class1> FindClassesByIDs(string ids)        {            var r = from c in this.DbContext.Clas

LINQ之路 5:LINQ查询表达式

书写LINQ查询时又两种语法可供选择:方法语法(Fluent Syntax)和查询表达式(Query Expression). LINQ方法语法的本质是通过扩展方法和Lambda表达式来创建查询.C# 3.0对于LINQ表达式还引入了声明式的查询表达式,也叫查询语法,通常来讲,它是创建LINQ查询的更加快捷的方式.尽管通过查询语法写出的查询比较类似于SQL查询,但实际上查询表达式的产生并不是建立在SQL之上,而是建立在函数式编程语言如LISP和Haskell中的list comprehensio

LINQ之路 1: LINQ介绍

LINQ是.NET Framework 3.5的新特性,其全称是 Language Integrated Query,即语言集成查询,是指将查询功能和语言结合起来.从而为我们提供一种统一的方式,让我们能在C#或VB.NET语言中直接查询和操作各种数据. LINQ的作用 作为软件开发人员,我们很大一部分时间都花在了获取和操作数据上面.而说到数据,我们会自然而然地想到包含在关系数据库里的信息.使用的XML文档.保存在本地的DataSet.内存中的List列表等等.通常我们会对数据进行过滤和定位,查询

LINQ中in的实现方法-LINQ To Entities如何实现查询 select * from tableA where id in (1,2,3,4)

如果用in是字符串类型无问题,可以直接这样用 var result = SNFService.Instance.ModuleService.GetList(UserInfo).Where(entity => entity.DeletionStateCode == 0 ).Where(entity => urls.Contains((entity.NavigateUrl == null ? "" : entity.NavigateUrl).ToLower())).OrderB

LINQ to Entities 不识别方法“System int string 转换的问题

这个问题困扰了挺久,网上找了挺多方法 都太好使. 分几种情况. 1.如果查询结果 转换,那比较容易. var q = from c in db.Customers where c.Country == "UK" || c.Country == "USA" select new { Phone = c.Phone, InternationalPhone = PhoneNumberConverter(c.Country, c.Phone) }; public strin

LINQ to Entities 不识别方法“System.String ToString() 的解决方法

今天在做一个页面的时候出现了LINQ to Entities 不识别方法"System.String ToString()"的错误,对于源码IQueryable<SelectListItem> items = roleInfoServer.Get(r => true).Select(r => new SelectListItem() { Value = r.Id.ToString(), Text = r.RoleName });找了好长的时间没有找到原因.无奈之

LINQ to Entities 不识别方法“Int32 Parse(System.String)”,因此该方法无法转换为存储表达式。解决

  问题描述 最近在用LINQ to Entities,看看下面的代码 //获取分页数据 var admins = from aa in db.VAccountAdmins select aa; //处理过滤规则 if (null != filterRules) { JArray roles = (JArray) JsonConvert.DeserializeObject(filterRules); foreach (var fr in roles) { string field = fr["f