C#每日一课(三十六)

40 篇文章 1 订阅

LINQ基础知识
LINQ是微软.NET Framework 4.5提出的特征之一。它为开发人员提供了统一的数据查询模式,并且与.NET开发语言集成,很大程度上简化了数据查询的编程调试工作,提高了数据处理的性能。

  • LINQ是什么?
    通常来说,针对数据的查询是用简单的字符串文本来编写的查询语句,如:SQL查询语句,没有编译时的类型检查,在安全性和方便性上都不是很友好。同样的对于不同的数据源需要使用不同的查询语法,这些增加了查询的难度。
    对于上面的问题,微软提供了LINQ(Language Integrate Query),它在对象和数据之间建立了一种对应关系,可以使用访问内存对象的方式查询数据集合。
    LINQ不是简单地在C#中嵌套查询表达式,而是把查询表达式作为C#的一种语法。查询表达式访问的数据源是包含一组数据的集合对象(IEnumerable或IQueryable类型),返回的查询结果也是包含一组数据的集合对象。
    在LINQ中查询表达式访问的是一个对象,这个对象本身可以表示各种类型的数据源。
    在.NET类库中,LINQ相关类库都在System.Linq命名空间下,它主要有两个类和两个接口
    接口:
    1.IEnumerable接口:它表示可以查询的数据集合,一个查询通常是逐个对集合中的元素进行筛选操作,返回一个新的IEnumerable对象,用来保存查询结果。
    2.IQueryable接口:它继承了IEnumerable接口,表示一个可以查询的表达式目录树。
    类:
    1.Enumerable类:
    2.Queryable类:

根据数据源类型,可以把LINQ技术分成以下几个主要的技术方向
LINQ to Object
LINQ to ADO.NET
LINQ to XML

  • LINQ的使用
    LINQ作为一种数据查询编码方式,它本身是不能独立的开发语言,也不可以进行应用程序开发,可以在C#中集成LINQ查询代码。
    在C#中要使用LINQ查询功能,必须引用System.Linq命名空间
    如果要使用LINQ to XML要引用 System.Xml.Linq
    如果要使用LINQ to ADO.NET要引用 System.Data.Linq

在C#中嵌入LINQ查询代码非常简单,只需要把LINQ查询看成普通的对象代码即可。
作用Visual Studio新建C#控制台应用程序
在Main方法中添加如下代码

           //定义数组,查询数据源
            int[] arr = { 1,2,3,4,5,6,7,8};
            //定义查询语句
            var query1 = from val in arr select val;
            //打印输出查询结果
            foreach (var item in query1)
            {
                Console.Write("{0}", item);
            }
            Console.ReadKey();

注意:程序开头需要使用using System.Linq;引用

  • LINQ查询
    LINQ使得对文本查询与对象操作完美集成,可以让查询数据与操作对象一样安全轻松。
    传统的查询,如SQL查询语法中,使用的是一定的语义的文本,比如:select name from student;LINQ中的查询与传统的查询有相似之处,它同样可以用一定的语义的文本来表示,如上面的var query1 = from val in arr select val; 这种方式在LINQ当中称为查询表达式。另外LINQ中查询同时是一个类型为为 IEnumerable或 IQueryable的对象,所
    以可以通过一种使用对象的方式(使用属性、调用方法等)使用它,这种方式在 LINQ 中
    称为查询方法。
    LINQ 查询的目的是从指定的数据源中查询满足符合特定条件的数据元素,并且根据需要对这些查询的元素进行排序、连接等操作。
    LINQ 查询包括如下几个主要元素:
    1.数据源:数据源表示LINQ查询从哪里去查找数据,它通常是一个或多个数据集合,每个数据集合包含一系列的元素。数据集是一个类型为 IEnumerable或IQueryable的对象,可以对它进行枚举,遍历每一个元素。
    2.目标数据:目标数据用来指定查询的具体想要的是什么数据。在 LINQ 中,它定义了查询结果数据集中元素的具体类型
    3.筛选条件:筛选条件定义了对数据源中元素的过滤条件。只有满足条件的元素才作为查询结果返回。筛选条件可以是简单的逻辑表达式表示,也可以用具有复杂逻辑的函数来表示。
    4.附加操作:附加操作表示一些其他的具体操作。比如,对查询结果进行排序、计算查询结果的最值和求和、对查询结果进行分组等。
    注意:数据源和目标数据是 LINQ 查询的必备元素,筛选条件和附加操作是可选元素

LINQ查询表达式
查询表达式是LINQ查询的基础,也是最常用的编写LINQ查询方法。
查询表达式由查询关键字和对应的操作数组成的表达式整体。
查询关键字:常用的查询运算符
from:指定要查找的数据源及范围变量,多个from子句则表示从多个数据源中查找数据
select:指定查询要返回的目标数据,可以指定任何类型,甚至是匿名类型
where:指定元素的筛选条件,多个where子句则表示并列条件,必须全部满足才可以
orderby:指定元素的排序字段和排序方式,当有多个排序字段时,由字段顺序确定主次关系,可以有升序和降序两种排序方式。
group:指定元素的分组字段
join:指定多个数所源的关联方式

  • 使用from子句指定数据源
    数据源是LINQ查询中必不可少的元素,数据源是实现泛型接口IEnumerable或IQueryable的类对象。
    可以把IEnumerable简单理解为一个包含多个元素的列表(或数据库中的表),可以使用foreach来遍历它的所有元素。
    .NET类库中,列表类,集合类,数组类都实现了接口IEnumerable,可以直接把这些数据对象作为数据源在LINQ查询中使用。
    每一个LINQ查询都是以from子句开始的,它包含以下两个功能
    1.指定查询将采用数据源
    2.定义一个本地变量,表示数据源中单个元素
    相关格式如下:
from localVar in dataSource

一般情况下,不用为from子句的localVar元素指定数据类型,编译器会根据数据源类型来自动为它分配合适的类型。
特殊情况下,还是需要为本地变量指定数据类型的,比如把数据源的类型做为object类型进行处理时。
注意:编译器是不会检查本地变量localVar的具体类型的,所以当指定类型不正确的时候,编译器并不会报错。

  • select子句指定目标数据
    在LINQ查询中,select子句和from子句都是必备的。LINQ查询表达式必须以select或group子句结束,select子句指定在执行查询时产生的结果的数据集中元素的类型,它的格式如下:
select element

使用Visual Studio新建C#控制台应用程序
1.新增一个类LessonScore

/*成绩单类*/
    class LessonScore
    {
        private string lesson;
        private float score;

        public string Lesson
        {
            get { return lesson; }
            set { lesson = value; }
        }

        public float Score
        {
            get { return score; }
            set { score = value; }
        }
        /*创建成绩单,构造函数*/
        public LessonScore(string les, float scr)
        {
            Lesson = les;
            Score = scr;
        }
        /*重写ToString方法*/
        public override string ToString()
        {
            string str;
            str = string.Format("{0} -- {1}分",Lesson,Score);
            return str;
        }
    }

2.新增一个学生类

/*学生类*/
    class Student
    {
        public string Name { get; }
        public string Sex { get; }
        public int Age { get; }
        /*学生成绩单。一个LessonScore列表*/
        public List<LessonScore> Scores { get; }

        /*构造函数*/
        public Student(string name, string sex, int age, List<LessonScore> scores)
        {
            Name = name;
            Sex = sex;
            Age = age;
            Scores = scores;
        }
        public Student(string name, string sex, int age)
        {
            Name = name;
            Sex = sex;
            Age = age;
            Scores = null;
        }
        /*重写ToString方法*/
        public override string ToString()
        {
            string str;
            str = string.Format("{0} -- {1} -- {2}",Name,Age,Sex);
            return str;
        }
    }

3.新增一个Tester类,其中Main方法为主测试方法

class Tester
    {
        static void Main(string[] args)
        {
            Student[] stuArray =
            {
                new Student("张三","男",20),
                new Student("李四","男",22),
                new Student("刘小华","女",23),
                new Student("王武","男",21),
                new Student("欧阳丹丹","女",18),
            };
            //查询query1返回数据stuArray中所有元素
            var query1 = from val1 in stuArray select val1;
            //打印出query1中的所有元素
            foreach (Student stu in query1)
            {
                Console.WriteLine(stu);
            }
            Console.ReadKey();
        }
    }

经过Main方法中的查询把所有学生信息都查询出来并打印出学生信息如下:
运行结果

select 为关键字,element参数则指定查询结果中元素的类型及初始化方式。
select子句中如果不指定元素的具体类型,编译器会把查询中元素类型自动设置为select子句中元素的具体类型。
select子句中要选择的目标数据不仅可以为数据源中的元素,还可以是这个元素的不同操作结果。如下例:
在测试类的Main方法中加入如下两个查询

//查询query2返回数据源中学生姓名
            var query2 = from val2 in stuArray select val2.Name;
            //打印出query2中的所有元素
            foreach (string item in query2)
            {
                Console.Write("{0},", item);
            }
            Console.WriteLine();

            //查询query3中返回查询学生姓名的字符长度
            var query3 = from val3 in stuArray select val3.Name.Length;
            foreach (int item in query3)
            {
                Console.Write("{0},", item);
            }
            Console.WriteLine();

运行结果如下:
运行结果
在有些场合下,查询只是临时使用,而且查询的结果包含很多字段,这个时候可以在select子句中使用匿名类来解决这类问题,如下例所示
在Main方法中新增一个查询

 //查询query4select 子句中使用匿名类型
            var query4 =
                from val4 in stuArray
                select new { val4.Name, val4.Age, NameLen = val4.Name.Length };
            foreach (var item in query4)
            {
                Console.WriteLine(item);
            }

编译运行结果如下:
运行结果

  • where子句指定筛选条件
    上面的一些例子都是对所有的数据进行查询处理,往往还需要对数据源中元素进行过滤操作。只有符合条件的元素,才能参与查询结果的计算。在LINQ中,使用where子句可以进行查询过滤。
    格式:where expression
    其中expression是一个逻辑表达式,返回值“真”或“假”
    当被查询的元素参与表达式运算返回结果为真的时候,这个元素参与查询结果运算
    where 子表达式中,可以使用&&或||指定多个条件逻辑关系运算
    在Tester类的Main方法中加入如下代码
Console.WriteLine();
            //查询query5 使用where进行条件筛选
            var query5 = from val5 in stuArray where val5.Name.Length>=3 && val5.Age<20 select val5;
            foreach (var item in query5)
            {
                Console.WriteLine(item);
            }

上面的查询加上了where子句,表示对查询出来的数据进行筛选,筛选 条件是名字的字符长度大于等3,并且年龄小于20的学生信息
编译运行的结果如下:
运行结果
在使用where子句时,还可以用多个并列的where子句来进行多个条件的过滤,这个时候就必须要保证多个where子句都满足时才能作为查询结果
比如上面的代码可以修改为如下:

//查询query5 使用where进行条件筛选
            var query5 = from val5 in stuArray
                         where val5.Name.Length>=3
                         where val5.Age<20
                         select val5;
            foreach (var item in query5)
            {
                Console.WriteLine(item);
            }
  • orderby子句进行排序
    在LINQ中,通过orderby子句对查询结果进行排序。
    语法格式如下:
    orderby element [sortType]
    element:表示要做排序的字段
    sortType:可选参数,表示排序类型,包含升序ascending和降序desending,默认情况下是ascending
    在Tester类的Main方法中可以加入如下代码进行测试
Console.WriteLine();
            //使用orderby进行排序
            var query6 = from val6 in stuArray
                         where val6.Sex == "男"
                         orderby val6.Age descending
                         select val6;
            foreach (var item in query6)
            {
                Console.WriteLine(item);
            }

query6 这个查询是查询所有性别为“男”的学生,并且按学生年龄进行降序排列
编译运行的结果如下:
运行结果

  • group子句进行分组
    在LINQ中,用group子句实现对查询结果的分组操作
    语法格式如下:
    group element by key
    element:表示作为查询结果返回的元素
    key:表示分组的条件
    group子句返回的类型是IGrouping<TKey,TElement>的查询结果。TKey的类型为参数key的数据类型,TElement的类型是参数element的数据类型
    注意:IGrouping<TKey,TElement>可以看作是一个Hashtable内嵌了一个List列表的数据结果。
    在Tester类的Main方法中加入如下代码进行测试
Console.WriteLine();
            //使用group子句
            var query7 = from val7 in stuArray
                         where val7.Age > 20
                         group val7 by val7.Sex;
            foreach (var grp in query7)
            {
                Console.WriteLine(grp.Key);
                foreach (var val in grp)
                {
                    Console.WriteLine("\t{0}", val);
                }
            }

上面的查询query7是查询年龄大于20岁的学生,并使用性别进行分组
编译运行后的结果如下:
运行结果

有时候不仅仅只是做分组,还需要对分组的结果进行排序或再次查询等操作,这个时候就要使用into关键字把group查询的结果保存到一个临时变量中,再使用新的select功group子句对它进行重新查询,也可以使用orderby进行排序、where进行过滤等操作。
语法格式如下:
group element by key into tmpGrp
向Tester类的Main方法中添加如下代码进行测试

Console.WriteLine();
            //对学生类数组中再添加几个学生
            List<Student> stuList = stuArray.ToList();
            stuList.Add(new Student("赵小军", "男", 20));
            stuList.Add(new Student("雷洛", "男", 20));
            stuList.Add(new Student("谢华", "女", 18));
            //使用group子句
            var query8 = from val8 in stuList
                         group val8 by val8.Age into stuGrp
                         orderby stuGrp.Key descending
                         select stuGrp;
            foreach (var grp in query8)
            {
                Console.WriteLine("{0}岁的学生:",grp.Key);
                var query9 = from val9 in grp
                             orderby val9.Name.Length descending
                             select val9;
                foreach (var item in query9)
                {
                    Console.WriteLine("\t{0}",item);
                }
            }

编译运行结的结果
运行结果
程序中先使用年龄进行分组,并且对分组的结果进行了排序,在最后展示每一个分组下的数据时进行了一次按名称字符长度进行排序查询操作。

  • from子句进行复合查询
    在实际开发中,往往需要对多个数源进行查询,这个时候就要使用多个from子句进行复合查询
    修改Tester类中的Main方法如下,测试多个from子句进行复合查询
Student[] stuArray = {
                new Student("张三", "男", 20,
                    new List<LessonScore> { new LessonScore("英语", 80.5f), new LessonScore("数学", 75.5f),
                new LessonScore("语文", 90f)}),
            new Student("刘小华", "女", 23,
                new List<LessonScore> { new LessonScore("英语", 78.5f), new LessonScore("数学", 85.5f),
                new LessonScore("语文", 78f)}),
            new Student("王武", "男", 21,
                new List<LessonScore> { new LessonScore("英语", 81.5f), new LessonScore("数学", 87.5f),
                new LessonScore("语文", 88.5f)}),
            new Student("欧阳丹丹", "女", 18,
                new List<LessonScore> { new LessonScore("英语", 90.5f), new LessonScore("数学", 76.5f),
                new LessonScore("语文", 88.5f)}),
            new Student("赵小军", "男", 20,
                new List<LessonScore> { new LessonScore("英语", 77.5f), new LessonScore("数学", 76.5f),
                new LessonScore("语文", 79.5f)}),
            new Student("谢华", "女", 18,
                new List<LessonScore> { new LessonScore("英语", 90.5f), new LessonScore("数学", 98.5f),
                new LessonScore("语文", 85.5f)})};

            //使用多个from来做复合查询
            //第二个from子句的查询从第一个from子句的结果中再次进行查询
            var query =
                from st in stuArray
                from scr in st.Scores
                where scr.Score > 80
                group new { st.Name, scr } by st.Name;
            foreach (var grp in query)
            {
                Console.WriteLine(grp.Key);
                foreach (var item in grp)
                {
                    Console.WriteLine("\t{0}",item);
                }
            }
            Console.ReadKey();

编译运行的结果如下:
运行结果

另一种类型from复合子句是在多个数据源上进行查询,每个from都从一个数据源上查询数据,一般还会带有where子句对数据进行过滤。
在Tester类的Main方法中加入如下代码进行测试

 //from复合查询
            //多from查询多个数据源
            /*两个整型数据组作为两个数据源*/
            int[] intAry1 = { 5, 15, 25, 30, 33, 50 };
            int[] intAry2 = { 10, 20, 30, 40, 50, 60, 70, 80, 90, 100 };
            var query1 =
                from val1 in intAry1
                from val2 in intAry2
                where val2 % val1 == 0
                group val2 by val1;
            //遍历查询结果
            foreach (var grp in query1)
            {
                Console.Write("{0}:\t",grp.Key);
                foreach (var val in grp)
                {
                    Console.Write("{0}\t", val);
                }
                Console.WriteLine();
            }

编译运行结果如下:
运行结果
注意:多个from子句会增加代码的复杂度,并且会降低可读性和可维护性,一般来说不要使用超过3个以上的from子句。

  • join子句进行联接
    在查询语言中,通常需要使用联接操作,在LINQ中可以通过join子句实现联接操作。join子句可以是来自不同的数据源并且在对象模型中没有直接关系统的元素相关联,但是要求每个源中的元素需要共享某个可以进行比较,以判断是否相等的值。
    join子句可以实现三种类型的联接:
    1.内部联接
    2.分组联接
    3.左外部联接

内部联接
格式如下:
join element in dataSource on exp1 equals exp2
dataSource:数据源,它是联接要使用的第二个数据集
element:存储dataSource中元素的本地变量
exp1和exp2:两个表达式,它们具有相同的数据类型,可以使用equals进行比较。如果exp1和exp2相等,则当前的元素将添加到查询结果。

在Tester类Main方法中添加如下代码:

//使用join进行内部联接
            var query2 =
                from val1 in intAry1
                join val2 in intAry2 on val1 % 5 equals val2 % 15
                select new { Element1 = val1, Element2 = val2 };
            foreach (var item in query2)
            {
                Console.WriteLine(item);
            }

编译运行结果如下:
运行结果

分组联接
分组联接格式如下:
join element in dataSource on exp1 equals exp2 into grpName
into:这个关键字表示把这些数据分组并保存到grpName中
grpName:保存一组数据集合
这种分组联接,它将第一个集合中的每个元素与第二个集合是的一组相关元素进行匹配。即使是第一个集合中的元素在第二个集合中没有匹配的元素,也会为它产生一个空的分组对象。
在Tester类的Main方法中添加如下测试代码:

//使用join进行分组联接
            var query3 = 
                from val1 in intAry1
                join val2 in intAry2 on val1 % 5 equals val2 % 15 into val2Grp
                select  new { VAL1 = val1, VAL2Grp = val2Grp };
            foreach (var obj in query3)
            {
                Console.Write("{0}:",obj.VAL1);
                foreach (var val in obj.VAL2Grp)
                {
                    Console.Write("{0} ", val);
                }
                Console.WriteLine();
            }

编译运行结果如下:
运行结果

左外部联接
它返回的是第一个集合的所有元素,不管它是否在第二个集合中有没有相关元素。在LINQ中,通过对分组联接的结果调用DefaultEmpty()方法来执行左外部联接。
DefaultEmpty()方法从列表中获取指定元素,如果列表为空则返回默认值。
在Tester类的Main方法中添加如下测试代码:

//使用join进行左外部联接
            var query4 =
                from val1 in intAry1
                join val2 in intAry2 on val1 % 5 equals val2 % 15 into val2Grp
                from grp in val2Grp.DefaultIfEmpty()
                select new { VAL1 = val1, VAL2Grp = grp };
            foreach(var obj in query4)
            {
                Console.WriteLine("{0}",obj);
            }

编译运行结果如下:
运行结果
左外部联接与分组联接相似,但是左外部联接只需要遍历一次,它在join后还有一个from子句进行查询。而分组联接使用了两层foreach进行遍历它的结果。

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值