首页 > 技术文章 > 【C#】LINQ

aaabbbcccddd 2018-10-23 08:24 原文

一、什么是LINQ

长期以来,开发社区形成以下的格局:

1、面向对象与数据访问两个领域长期分裂,各自为政。

2、编程语言中的数据类型与数据库中的数据类型形成两套不同的体系,例如:

  C#中字符串用string数据类型表示。

  SQL中字符串用NVarchar/Varchar/Char数据类型表示。

3、SQL编码体验落后

  没有智能感知效果。

  没有严格意义上的强类型和类型检查。

4、SQL和XML都有各自的查询语言,而对象没有自己的查询语言。

上面描述的问题,都可以使用LINQ解决,那么究竟什么是LINQ呢?

LINQ(Language Integrated Query)即语言集成查询。

LINQ是一组语言特性和API,使得你可以使用统一的方式编写各种查询。用于保存和检索来自不同数据源的数据,从而消除了编程语言和数据库之间的不匹配,以及为不同类型的数据源提供单个查询接口。

LINQ总是使用对象,因此你可以使用相同的查询语法来查询和转换XML、对象集合、SQL数据库、ADO.NET数据集以及任何其他可用的LINQ提供程序格式的数据。

LINQ主要包含以下三部分:

1、LINQ to Objects      主要负责对象的查询。

2、LINQ to XML           主要负责XML的查询。

3、LINQ to ADO.NET   主要负责数据库的查询。

  LINQ to SQL

  LINQ to DataSet

  LINQ to Entities

 

 

LINQ教程二:LINQ操作语法

LINQ查询时有两种语法可供选择:查询表达式语法(Query Expression)和方法语法(Fluent Syntax)。

一、查询表达式语法

查询表达式语法是一种更接近SQL语法的查询方式。

LINQ查询表达式语法如下:

1 from<range variable> in <IEnumerable<T> or IQueryable<T> Collection>
2 <Standard Query  Operators> <lambda expression>
3 <select or groupBy operator> <result   formation>

LINQ查询表达式

约束 LINQ查询表达式必须以from子句开头,以select或group子句介绍
关键字 功能
from....in...

指定要查询的数据源以及范围变量,多个from子句则表示从多个数据源查找数据。注意:C#编译器会把“复合from子句”的查询表达式转换为SelectMany()扩展方法。

join…in…on…equals… 指定多个数据源的关联方式
let 引入用于存储查询表达式中子表达式结果的范围变量。通常能达到层次感会更好,使代码更易于阅读。
orderby、descending 指定元素的排序字段和排序方式。当有多个排序字段时,由字段顺序确定主次关系,可指定升序和降序两种排序方式
where 指定元素的筛选条件。多个where子句则表示了并列条件,必须全部都满足才能入选。每个where子句可以使用谓词&&、||连接多个条件表达式。
group 指定元素的分组字段。
select 指定查询要返回的目标数据,可以指定任何类型,甚至是匿名类型。(目前通常被指定为匿名类型)
into

提供一个临时的标识符。该标识可以引用join、group和select子句的结果。

1)        直接出现在join子句之后的into关键字会被翻译为GroupJoin。(into之前的查询变量可以继续使用)

2)        select或group子句之后的into它会重新开始一个查询,让我们可以继续引入where, orderby和select子句,它是对分步构建查询表达式的一种简写方式。(into之前的查询变量都不可再使用)

查询语法从一个From子句开始,然后是一个Range变量。 From子句的结构类似于“From rangeVariableName in IEnumerablecollection”。 在英语中,这意味着,从集合中的每个对象。 它类似于foreach循环:foreach(student in studentList)。

在From子句之后,您可以使用不同的标准查询运算符来过滤,分组,连接集合的元素。 LINQ中有大约50个标准查询运算符。标准查询运算符后面通常跟一个条件,这个条件通常使用lambda表达式来表示。

LINQ查询语法总是以Select或Group子句结束。 Select子句用于对数据进行整形。 您可以选择整个对象,因为它是或只有它的一些属性。 在上面的例子中,我们选择了每个结果字符串元素。

例如:我们要从数组中查询出偶数,查询表达式示例代码如下:

var result = from p in ints where p % 2 == 0 select p;

查询表达式语法要点总结:

1、查询表达式语法与SQL(结构查询语言)语法相同。

2、查询语法必须以from子句开头,可以以Select或GroupBy子句结束 。

3、使用各种其他操作,如过滤,连接,分组,排序运算符以构造所需的结果。

4、隐式类型变量 - var可以用于保存LINQ查询的结果。

 

 

 

二、方法语法

方法语法(也称为流利语法)主要利用System.Linq.Enumerable类中定义的扩展方法和Lambda表达式方式进行查询,类似于如何调用任何类的扩展方法。

以下是一个示例LINQ方法语法的查询,返回数组中的偶数:

var result = ints.Where(p => p % 2 == 0).ToArray();

从上面的示例代码中可以看出:方法语法包括扩展方法和Lambda表达式。 扩展方法Where()在Enumerable类中定义。

如果你检查Where扩展方法的签名,你会发现Where方法接受一个谓词委托,如Func <Student,bool>。 这意味着您可以传递任何接受Student对象作为输入参数的委托函数,并返回一个布尔值,如下图所示。 lambda表达式作为在Where子句中传递的委托传递。 在下一节中学习lambda表达式。

 

三、查询表达式语法VS方法语法

查询表达式语法与方法语法存在着紧密的关系
1、CLR本身并不理解查询表达式语法,它只理解方法语法。
2、编译器负责在编译时将查询表达式语法翻译为方法语法。
3、大部分方法语法都有对应的查询表达式语法形式:如Select()对应select、OrderBy()对应orderby
4、部分查询方法目前在C#中还没有对应的查询语句:如Count()和Max(),这是只能采用以下替代方案:
  方法语法
  查询表达式语法+方法语法的混合方式

 

 

 

 

LINQ教程三:Lambda表达式解剖

C#3.0(.NET3.5)中引入了Lambda表达式和LINQ。Lambda表达式是使用一些特殊语法表示匿名方法的较短方法。

最基本的Lambda表达式语法如下:

(参数列表)=>{方法体}

说明:

1、参数列表中的参数类型可以是明确类型或者推断类型。

2、如果是推断类型,则参数的数据类型将由编辑器根据上下文自动推断出来。

让我们看看Lambda表达式是如何从匿名方法演变而来的。

相关示例:

1 delegate(int item) { return item % 2 == 0; };

 1、Lambda表达式从匿名方法演变,首先删除delegate关键字和参数类型并添加Lambda运算符=>,演变后的代码如下:

1 (item)=>{return item % 2 == 0;};

 2、如果我们只有一个返回值的语句,那么我们不需要花括号、返回和分号,所以我们可以去掉这些符号,演变后的代码如下:

1 (item)=>item %2 == 0;

 3、如果我们只有一个参数,我们也可以删除(),代码如下:

1 item=>item %2 == 0;

 因此,我们得到如下所示的Lambda表达式:

item => item % 2 == 0

其中item是参数,=>是Lambda运算符,item % 2 == 0是正文表达式。

二、具有多个参数的Lambda表达式

如果需要传递多个参数,那么必须将参数括在括号内,如下所示:

1 (ints, item) => ints.Contains(item);

 如果不想使用推断类型,那么可以给出每个参数的类型,如下所示:

1 (int[] ints, int item) => ints.Contains(item)

 三、不带任何参数的Lambda表达式

在Lambda表达式中可以没有参数,如下所示:

1 () => Console.WriteLine("这是一个不带任何参数的Lambda表达式");

 四、正文表达式中有多条语句

在前面讲过,如果正文表达式有一个语句,那么可以去掉方法体外面的大括号。如果正文表达式中有多条语句,那么必须用大括号将正文表达式括起来,如下所示:

(ints, item) =>
{
        Console.WriteLine("这是包含多条语句的Lambda表达式");
        return ints.Contains(item);
}; 

 五、表达式中的局部变量

你可以在表达式的主体中声明一个变量,以便在表达式主体的任何位置使用它,如下所示:

1 ints => 
2 {
3        int item = 10;
4        return ints.Contains(item);
5 };

 六、Lambda表达式中的内置泛型委托

1、Func委托

当你想从lambda表达式返回一些东西时,使用Func <> delegate。 

其中T是输入参数的类型,TResult是返回类型。

示例代码如下:

1 Func<int[], bool> isContains = p => p.Equals(10);
2 int[] ints = { 5, 2, 0, 66, 4, 32, 7, 1 };
3 bool isEquals = isContains(ints);

在上面的例子中,Func委托期望第一个输入参数是int[]类型,返回类型是boolean。Lambda表达式是p => p.Equals(10)。

2、Action委托

与Func委托不同,Action委托只能有输入参数。 当不需要从lambda表达式返回任何值时,请使用Action委托类型。

示例代码如下:

复制代码
1 Action<int[]> PrintItem = p => 
2 {
3        foreach (int item in p)
4        {
5              Console.WriteLine(item);
6        }
7 };
8 int[] ints = { 5, 2, 0, 66, 4, 32, 7, 1 };
9 PrintItem(ints);
复制代码

 七、在LINQ中使用Lambda表达式 

通常情况下,Lambda表达式与LINQ查询一起使用。枚举静态类包括接受Func <TSource,bool>的IEnumerable <T>的Where扩展方法。IEnumerable <Int>集合的Where()扩展方法需要传递Func <Student,bool>,如下所示:

现在,您可以将分配给Func委托的lambda表达式传递给方法语法中的Where()扩展方法,如下所示:

1 Func<int, bool> isContains = p =>p.Equals (4);
2 int[] ints = { 5, 2, 0, 66, 4, 32, 7, 1 };
3 var result = ints.Where(isContains).ToList();

 八、Lambda表达式要点总结

1、Lambda表达式是一种表示匿名方法的更短的方法。 

2、Lambda表达式语法:parameters =>正文表达式

3、Lambda表达式可以在()中具有零个或多个参数。 

4、Lambda表达式可以在大括号{}中的正文表达式中有一条或多条语句。 

5、Lambda表达式可以分配给Func,Action或Predicate委托。

6、Lambda表达式可以以类似的方式调用委托。

 

 

四  实例操作

 

 

 

一、什么是LINQ

 

      LINQ是Language Integrate Query的缩写,意为语言集成查询,是微软在.Net Framework 4.5版中推出的主要特性之一。

 

      它为开发人员提供了统一的数据查询模式,并与.Net开发语言(如C#和VB.Net)集成,很大程度上简化了数据查询的编码和调试工作,提供了数据查询的性能。

 

      LINQ中查询表达式访问的是一个对象,而该对象可以表示为各种类型的数据源。比如SQL Server数据库,XML文档,ADO.NET数据集,以及内存中的数据集合等。

 

      在.NET类库中,LINQ相关类库都在System.Linq命名空间中,该命名空间提供支持使用LINQ进行查询的类和接口,其中主要是以下两个接口和两个类:

 

      IEnumerable<T>接口:它表示可以查询的数据集合,一个查询通常是逐个对集合对象中的元素进行筛选操作,返回一个新的IEnumerable<T>对象,用来保存查询结果。

 

      IQueryable<T>接口:它继承自IEnumerable<T>接口,表示一个可以查询的表达式目录树。

 

      Enumerable类:它通过对IEnumerable<T>提供扩展方法,实现LINQ标准查询运算。包括过滤、导航、排序、关联、求和、求最大值、求最小值等操作。

 

      Queryable类:它通过对IQueryable<T>提供扩展方法,实现LINQ标准查询运算。包括过滤、导航、排序、关联、求和、求最大值、求最小值等操作。

 

     根据数据源类型,可以将LINQ技术分为以下几个主要技术方向:

 

      1.LINQ to Object:数据源为实现了接口IEnumerable<T>和IQueryable<T>的内存数据集合,这也是LINQ的基础,本文将介绍着方面的内容。

 

      2.LINQ to ADO.NET:数据源为ADO.NET数据集,这里将数据库中的表结构映射到类结构,并通过ADO.NET从数据库中获取数据集到内存,通过LINQ进行数据查询。

 

      3.LINQ to XML:数据源为XML文档,这里通过XElement、XAttribute等类将XML文档数据加载到内存中,通过LINQ进行数据查询。

 

二、LINQ查询表达式

 

      在进行LINQ查询的编写之前,首先要了解查询表达式。查询表达式是LINQ查询的基础,也是最常用的编写LINQ查询的方法。查询表达式由查询关键字和对应的操作数组成。其中,查询关键字是常用的查询运算符。

 

      在C# 3.0中可以直接使用的查询关键字和功能如下表:

 

 

        

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

     

 

     1.用from子句指定数据源

 

        每个LINQ查询表达式都以from子句开始,from子句包括以下两个功能。

 

        (1)指定查询将采用的数据源

 

        (2)定义一个本地变量,表示数据源中单个数据

 

      单个from子句的编写格式为: from localVar in dataSource,其中,dataSource表示数据源,localVar表示单个元素。    

 

       示例代码如下:

 

复制代码
            int[] array = { 1, 2, 4, 5, 7 };
            var query = from item in array 
select item; foreach (var item in query) { Console.WriteLine(item); }
复制代码

 

      2.使用select子句指定目标数据源

 

         select子句指定在执行查询是产生的结果类型,其格式为:select element,其中elment参数指定查询结果中元素的类型及初始化方式。

 

         在进一步介绍select子句之前,先介绍一下本文示例中要用到的实体类:

 

复制代码
public class LessonScore
    {
        /// <summary>
        /// 课程成绩
        /// </summary>
        public float Score { get; set; }
        /// <summary>
        /// 课程名称
        /// </summary>
        public string Lession { get; set; }

        public override string ToString()
        {
            return string.Format("{0}-----{1}分",Lession,Score);
        }
    }

public class Student
    {
        /// <summary>
        /// 学生名称
        /// </summary>
        public string Name { get; set; }
        /// <summary>
        /// 学生性别
        /// </summary>
        public string XingBie { get; set; }
        /// <summary>
        /// 学生年龄
        /// </summary>
        public int Age { get; set; }

        public List<LessonScore> Scores { get; set; }

        public override string ToString()
        {
             return string.Format("{0}---{1}---{2}",Name, XingBie,Age);
        }
    }
复制代码

 

       select子句中要选择的目标类型不仅可以为数据源中的元素,还可以是该元素的不同操作结果,包括属性、方法和运算等。示例代码如下:

 

复制代码
              Student[] students = {
                    new Student() {Name="乔峰",Age=20,XingBie="" },
                    new Student() {Name="欧阳修",Age=22,XingBie="" },
                    new Student() {Name="王五",Age=19,XingBie="" },
                    new Student() {Name="王丹",Age=20,XingBie="" },
                    new Student() {Name="倾国倾城",Age=18,XingBie="" }
                };
                var query1 = from student in students
                            select student;//整个元素作为查询结果
                foreach (var item in query1)
                {
                    Console.WriteLine(item);
                }

                var query2 = from student in students
                             select student.Name;//元素的属性作为查询结果
                foreach (var item in query2)
                {
                    Console.WriteLine(item);
                }

                var query3 = from student in students
                             select student.Name.Length;
                foreach (var item in query3)
                {
                    Console.WriteLine(item);
                }
复制代码

 

       在某些特殊的场合下,往往查询结果只是临时使用一下,而查询结果的数据包括很多字段,并非一个简单的属性、方法返回值等。这时,可在select子句中使用匿名类型来解决这类问题。

 

       示例代码如下:

 

复制代码
                //返回数据源中学生的姓名、年龄、姓名的长度
                //使用匿名类型方式返回
                var query4 = from student in students
                             select new {student.Name,student.Age,NameLen = student.Name.Length };
                foreach (var item in query4)
                {
                    Console.WriteLine(item);
                }
复制代码

 

   3.使用where子句指定筛选条件

 

      通常一个LING查询不会如前面的示例代码那么简单,通常还需要对数据源中的元素进行过滤。只有符合条件的元素,才能参与查询结果的计算。

 

      LINQ中,where子句格式为:where expression,其中,expression是一个逻辑表达式,返回布尔值。

 

     where子句中的条件表达式,可以用&&和||指定多个条件的逻辑运算关系。其中,&&表示逻辑并,||表示逻辑或。

 

    示例代码:

 

复制代码
                int[] array = { 1,3,9,5,16,20,54,60,18,37};
                var query1 = from item in array  //查询array中所以大于15的元素
                             where item > 15
                             select item;

                foreach (var item in query1)
                {
                    Console.Write("{0}, ",item);
                }
                Console.WriteLine();

                var query2 = from item in array  //查询array中所以大于10小于40的元素
                             where item > 10 && item < 40
                             select item;

                foreach (var item in query2)
                {
                    Console.Write("{0}, ", item);
                }
                Console.WriteLine();

                var query3 = from item in array  //查询array中小于10或者大于40的元素
                             where item < 10 || item > 40
                             select item;

                foreach (var item in query3)
                {
                    Console.Write("{0}, ", item);
                }
复制代码

 

     4.使用orderby子句进行排序

 

       在一些场合,还需要对查询结果进行排序。在LINQ中,通过orderby子句对查询结果进行排序操作。

 

      orderby子句格式为:orderby element [ascending|descending],其中element是要进行排序的字段,可以是数据源中的数据,也可以是对元素操作的结果。

 

     [ascending|descending]是排序类型,ascending为升序,descending为降序,默认请客下为ascending。

 

     示例代码:

 

复制代码
               int[] array = { 1, 3, 9, 5, 16, 20, 54, 60, 18, 37 };
                var query1 = from item in array //升序
                            orderby item
                            select item;
                foreach (var item in query1)
                {
                    Console.Write("{0}, ", item);
                }
                Console.WriteLine();

                var query2 = from item in array //降序
                             orderby item descending
                             select item;
                foreach (var item in query2)
                {
                    Console.Write("{0}, ", item);
                }
                Console.WriteLine();
复制代码

 

      在LINQ中,orderby子句可以同时指定多规排序元素,还可以为每个元素指定独立的排序类型。orderby语句后的第一个排序元素为主要排序,第二个为次要排序,以此类推。

 

     示例代码如下:

 

复制代码
               Student[] students = {
                    new Student() {Name="乔峰",Age=20,XingBie="" },
                    new Student() {Name="欧阳修",Age=22,XingBie="" },
                    new Student() {Name="王五",Age=19,XingBie="" },
                    new Student() {Name="王丹",Age=20,XingBie="" },
                    new Student() {Name="倾国倾城",Age=18,XingBie="" }
                };
                //主要按姓名长度从小到大,次要按年龄从大到小
                var query3 = from stu in students
                             orderby stu.Name.Length ascending, stu.Age descending
                             select stu;
                foreach (var item in query3)
                {
                    Console.WriteLine(item);
                }
复制代码

 

     5.使用group子句进行分组

 

        在LINQ中,用group子句实现对查询结果的分组操作。group子句的常用格式为:group element by key。其中,element表示作为查询结果返回的元素,key表示分组条件。

 

       group子句返回类型为IGrouping<TKey,TElement>的查询结果。

 

       示例代码如下:

 

复制代码
                Student[] students = {
                    new Student() {Name="乔峰",Age=22,XingBie="" },
                    new Student() {Name="欧阳修",Age=22,XingBie="" },
                    new Student() {Name="王五",Age=19,XingBie="" },
                    new Student() {Name="王丹",Age=19,XingBie="" },
                    new Student() {Name="倾国倾城",Age=19,XingBie="" }
                };
                //按学生性别分组
                var query1 = from stu in students
                             group stu by stu.XingBie;
                foreach (var grp in query1)
                {
                    Console.WriteLine(grp.Key);
                    foreach (var item in grp)
                    {
                        Console.WriteLine(item);
                    }
                }
                //按多条件分组,按性别和年龄分组
                var query2 = from stu in students
                             group stu by new { stu.XingBie, stu.Age };
                foreach (var grp in query2)
                {
                    Console.WriteLine("{0}---{1}",grp.Key.XingBie,grp.Key.Age);
                    foreach (var item in grp)
                    {
                        Console.WriteLine(item);
                    }
                }
复制代码

 

         有时候需要对分组的结果进行排序、再次查询等操作。这就需要使用into关键字将group查询的结果保存到一个临时变量,并且必须使用select子句对其进行重新查询。

 

         into关键字语法为:group element by key into grp,其中,tmpGrp表示一个本地变量,用来临时保存group产生的结果,提供后面的LINQ子句使用。

 

   示例代码如下:

 

复制代码

 

             Student[] students = {
                    new Student() {Name="乔峰",Age=22,XingBie="" },
                    new Student() {Name="欧阳修",Age=22,XingBie="" },
                    new Student() {Name="王五",Age=19,XingBie="" },
                    new Student() {Name="王丹",Age=19,XingBie="" },
                    new Student() {Name="倾国倾城",Age=19,XingBie="" }
                };

                var query3 = from stu in students
                             group stu by stu.Age into grp
                             orderby grp.Key descending
                             select grp;
                foreach (var grp in query3)
                {
                    Console.WriteLine("{0}岁的学生:", grp.Key);
                    foreach (var item in grp)
                    {
                        Console.WriteLine(item);
                    }
                }

 

推荐阅读