Linq 是 微软提供的简化数据查询的技术, Linq从技术上来说,确实是通过扩展方法来完成的, 从程序的设计上来说,就是把不变的业务逻辑转移;把固定的业务逻辑封装;整合起来---Linq;
题外话;以往人们查询数据库中的数据需要在代码中拼接sql 语句来实现查询,这样做代码量大同时也不易检查到sql语句中拼写错误,如何能让程序员操纵对象实体来实现查询数据那Entity Framework框架就诞生了 这里广泛应用了linq技术 同时 实体映射框架有 sqlsuger等(最好了解一下)
要了解linq 首先要学习lambda, 他是C#中的语法,同时他可以让我们尽心函数式编程,减少代码量;
说到lambda 时就需要提及委托,如需要了解委托知识点请看上一章委托和事件总结;
委托规定了方法的返回值和参数类型,我们可以利用委托传递方法,参数 然后我们可以调用方法一样调用委托变量或者invoke
委托不仅可以指向普通方法也可以指向 匿名方法(就是没有名称的方法)例如
public delegate void NoReturnNoParaOutClass(); public delegate void GenericDelegate<T>(); public class LambdaShow { public delegate void NoReturnNoPara(); public delegate void NoReturnWithPara(int x, string y);//1 声明委托 public delegate int WithReturnNoPara(); public delegate string WithReturnWithPara(out int x, ref int y); public void Show() { //Lambda前世今生; { .Netframework1.0/1.1时代 //int j = 0; //NoReturnNoPara method = new NoReturnNoPara(DoNothing); //NoReturnWithPara method1 = new NoReturnWithPara(Study); //method1.Invoke(123, "Richard"); } //.NetFramework2.0 匿名方法 增加了一个delegate关键字,可以访问到除了参数以外的局部变量 int i = 0; { //NoReturnWithPara method = new NoReturnWithPara(delegate (int x, string y) //{ // Console.WriteLine(x); // Console.WriteLine(y); // Console.WriteLine(i); //}); } //.NetFramework3.0 去掉delegate关键了,在参数的后增加了一个=> goes to { //NoReturnWithPara method = new NoReturnWithPara((int x, string y) => //{ // Console.WriteLine(x); // Console.WriteLine(y); // Console.WriteLine(i); //}); } { .NetFramework3.0 后期,去掉了匿名方法的参数类型--为什么可以去掉? 语法糖:编译器提供的便捷功能;可以推导出类型的参数由编译器自行推断 //NoReturnWithPara method = new NoReturnWithPara((x, y) => //{ // Console.WriteLine(x); // Console.WriteLine(y); // Console.WriteLine(i); //}); } { 如果匿名方法体中只有一行代码,可以省略方法体的大括号; //NoReturnWithPara method = new NoReturnWithPara((x, y) => Console.WriteLine(x)); //NoReturnWithPara method1 = (x, y) => Console.WriteLine(x); Lambada } { //一个参数的时候;如果只有一个参数---参数的小括号也可以省略掉 Action<string> method = s => Console.WriteLine("匿名方法简写"); method.Invoke("坚持。。"); } { 如果有返回值? 如果lambda表达式中只有一行代码,且有返回值,可以省略return; //Func<string> func1 = new Func<string>(() => //{ // return ""; //}); //Func<string> func2 = () => //{ // return ""; //}; //Func<string> func3 = () => "";
//Func<string> func = () => "坚持"; //Func<int, string> func5 = i => i.ToString(); } } } |
上述就是lambda 的由来
扩展方法--
扩展就是让功能变得更多,新增方法;同时不影响已存在的方法;
设想如果一个已存在的方法需求变更那该如何处理,
一般来讲就要修改原来的类,但是类的结构一发生改变就会导致类不稳定,所以我们一般不要修改之前的代码要通过增加类或者增加程序集去修改
所以 更好的办法就是新增一个类,吧当前需要增加的行为的类当作参数传递到新增类的方法中,宅这个犯法中就可以获取到传递过来的这类的数据;
1.增加一个类;
2 新增类为静态类;
3 将要扩展的类当作第一个参数并用this修饰
4 如果增加了扩展方法同时在类的内部增加了有一个同样的方法 ,在调用时有限调用类内部声明的方法
public static string IntToString(this int i) { return i.ToString(); } |
任何类都可以有扩展方法;但同时扩展的类型包括的范围越大就需要注意他的类型安全问题;(泛型,object)
常用的集合类的扩展方法
linq中的关键功能提供了集合类的扩展方法 注意 所有 实现了Ienumerable<T>接口的类都可以使用这个方法 但 这些方法不是Ienumerable<T> 中的方法 而是以扩展方法的形式存在 System.linq命名空间的静态类中;
1.数据过滤。wher方法 适用于根据条件进行数据过滤的
IEnumerable<TSource> Where<TSource>(this IEnumerable<TSource> source, Func<TSource, bool> predicate)
|
2 获取数据条数, Count 他有两个重载方法一个没有参数,一个有一个Func<Ts,bool>Predicate 形同于 where扩展的参数,
1 int count1 = list.Count(e => e.Salary > 5000 || e.Age < 30); 2 int count2 = list.Where(e => e.Salary > 5000 || e.Age < 30).Count(); |
3判断 是否有一条满足条件 Any 方法 他也是有两种重载方法一个无参数一个有参数用法类似于count
4 获取一条数据。LINQ中有4组获取一条数据的方法,分别是Single、SingleOrDefault、First和FirstOrDefault。这4组方法的返回值都是符合条件的一条数据,每组方法也同样有两个重载方法,一个没有参数,另一个有一个
Func<TSource,bool>predicate参数。下面解释一下这4组方法的区别。
·Single:如果确认有且只有一条满足要求的数据,那么就用Single方法。如果没有满足条件的数据,或者满足条件的数据多于一条,Single方法就会抛出异常。 ·SingleOrDefault:如果确认最多只有一条满足要求的数据,那么就用SingleOrDefault方法。如果没有满足条件的数据,SingleOrDefault方法就会返回类型的默认值。如果满足条件的数据多于一条,SingleOrDefault方法就会抛出异常。 ·First:如果满足条件的数据有一条或者多条,First方法就会返回第一条数据;如果没有满足条件的数据,First方法就会抛出异常。 ·FirstOrDefault:如果满足条件的数据有一条或者多条,FirstOrDefault方法就会返回第一条数据;如果没有满足条件的数据,FirstOrDefault方法就会返回类型的默认值。 |
5)排序。OrderBy方法可以对数据进行正向排序,而OrderByDescending方法则可以对数据进行逆向排序
6)限制结果集。限制结果集用来从集合中获取部分数据,其主要应用场景是分页查询,比如从第2条开始获取3条数据。Skip(n)方法用于跳过n条数据,Take(n)方法用于获取n条数据。
(7)聚合函数。我们知道,SQL中有Max(最大值)、Min(最小值)、Avg(平均值)、Sum(总和)、Count(总数)等聚合函数。LINQ中也有对应的方法,它们的名字分别是Max、Min、Average、Sum和
Count,这些方法也可以和Where、Skip、Take等方法一起使用,
(8)分组。LINQ中支持类似于SQL中的group by实现的分组操作。GroupBy方法用来进行分组,其声明比较复杂,所以可以使用 var 进行接收
(var声明必须初始化,必须能推断出类型,不允许作为方法参数, 在复杂类型或者不知道具体类型是什么的时候可以使用,为的是偷懒,简化代码,尽量使用明确类型)
(9)投影。可以对集合使用Select方法进行投影操作,通俗来说就是把集合中的每一项逐项转换为另外一种类
型,Select方法的参数是转换的表达式
在select类型中也可以使用匿名类型, 当为匿名类型时就需要使用var接受
还有其他的一些linq写法,,,
将在代码中简单展示
public class LinqShow { #region Data Init private List<Student> GetStudentList() { #region 初始化数据 List<Student> studentList = new List<Student>() { new Student() { Id=1, Name="数据1", ClassId=2, Age=35 }, new Student() { Id=1, Name="在在", ClassId=2, Age=23 }, new Student() { Id=1, Name="扩展", ClassId=2, Age=27 }, new Student() { Id=1, Name="美丽的夜色", ClassId=2, Age=26 }, new Student() { Id=1, Name="火飞蛾", ClassId=2, Age=25 }, new Student() { Id=1, Name="兔兔骑士", ClassId=2, Age=24 }, new Student() { Id=1, Name="咋回事", ClassId=2, Age=21 }, new Student() { Id=1, Name="妹子", ClassId=2, Age=22 }, new Student() { Id=1, Name="辉光", ClassId=2, Age=34 }, new Student() { Id=1, Name="都胃口", ClassId=2, Age=30 }, new Student() { Id=1, Name="菲菲", ClassId=2, Age=30 }, new Student() { Id=1, Name="毛毛虫", ClassId=2, Age=30 }, new Student() { Id=1, Name="消费共", ClassId=2, Age=28 }, new Student() { Id=1, Name="小飞棍", ClassId=2, Age=30 }, new Student() { Id=3, Name="阿帅", ClassId=3, Age=30 }, new Student() { Id=4, Name="咋没", ClassId=4, Age=30 } , new Student() { Id=4, Name="咋没", ClassId=4, Age=30 } , new Student() { Id=4, Name="淑妃", ClassId=4, Age=30 }, new Student() { Id=4, Name="hello", ClassId=4, Age=30 }, new Student() { Id=4, Name="world", ClassId=4, Age=22 }, new Student() { Id=4, Name="kkzzll", ClassId=4, Age=23 }, new Student() { Id=4, Name="畅", ClassId=4, Age=25 }, new Student() { Id=4, Name="码数", ClassId=4, Age=26 }, new Student() { Id=4, Name="码数", ClassId=4, Age=28 }, new Student() { Id=4, Name="飞天", ClassId=4, Age=30 }, new Student() { Id=4, Name="春心", ClassId=4, Age=30 }, new Student() { Id=4, Name="无纺布", ClassId=4, Age=30 }, new Student() { Id=4, Name="杜蕾斯", ClassId=4, Age=30 }, new Student() { Id=4, Name="昏天魔王", ClassId=4, Age=30 } }; #endregion return studentList; } #endregion public void Show() { List<Student> studentList = this.GetStudentList(); #region Linq 扩展方法&表达式 { var list = studentList.Where<Student>(s => s.Age < 30); //list里面必然是符合要求的数据; foreach (var item in list) { Console.WriteLine("Name={0} Age={1}", item.Name, item.Age); } } { Console.WriteLine("********************"); var list = from s in studentList where s.Age < 30 select s; //list里面必然是符合要求的数据; foreach (var item in list) { Console.WriteLine("Name={0} Age={1}", item.Name, item.Age); } } #endregion #region linq to object Show { Console.WriteLine("********************"); var list = studentList.Where<Student>(s => s.Age < 30) .Select(s => new //投影:可以做一些自由组装+ new 一个匿名类,也可以new 具体类; { IdName = s.Id + s.Name, ClassName = s.ClassId == 2 ? "高级班" : "其他班" }); foreach (var item in list) { Console.WriteLine("Name={0} Age={1}", item.ClassName, item.IdName); } } { Console.WriteLine("********************"); var list = from s in studentList where s.Age < 30 select new { IdName = s.Id + s.Name, ClassName = s.ClassId == 2 ? "高级班" : "其他班" }; foreach (var item in list) { Console.WriteLine("Name={0} Age={1}", item.ClassName, item.IdName); } } { Console.WriteLine("********************"); var list = studentList.Where<Student>(s => s.Age < 30)//条件过滤 .Select(s => new//投影 { Id = s.Id, ClassId = s.ClassId, IdName = s.Id + s.Name, ClassName = s.ClassId == 2 ? "高级班" : "其他班" }) .OrderBy(s => s.Id)//排序 升序 .ThenBy(s => s.ClassName) //多重排序,可以多个字段排序都生效 .OrderByDescending(s => s.ClassId)//倒排 .Skip(2)//跳过几条 //必须要先排序 .Take(3)//获取几条 //必须要先排序 ; foreach (var item in list) { Console.WriteLine($"Name={item.ClassName} Age={item.IdName}"); } } {//group by· Console.WriteLine("********************"); var list = from s in studentList where s.Age < 30 group s by s.ClassId into sg select new { key = sg.Key, maxAge = sg.Max(t => t.Age) }; foreach (var item in list) { Console.WriteLine($"key={item.key} maxAge={item.maxAge}"); } //group by new {s.ClassId,s.Age} //group by new {A=s.ClassId>1} } { Console.WriteLine("********************"); var list = studentList.GroupBy(s => s.ClassId).Select(sg => new { key = sg.Key, maxAge = sg.Max(t => t.Age) }); foreach (var item in list) { Console.WriteLine($"key={item.key} maxAge={item.maxAge}"); } } { var list = studentList.GroupBy(s => s.ClassId); foreach (var date in list) ///实现了IEnumerable { Console.WriteLine(date.Key); foreach (var item in date) { Console.WriteLine(item.Age); } } } List<Class> classList = new List<Class>() { new Class() { Id=1, ClassName="nan" }, new Class() { Id=2, ClassName="nv" }, new Class() { Id=3, ClassName="zhong" }, }; { //Join var list = from s in studentList join c in classList on s.ClassId equals c.Id //只能使用equals 不能使== select new { Name = s.Name, CalssName = c.ClassName }; foreach (var item in list) { Console.WriteLine($"Name={item.Name},CalssName={item.CalssName}"); } } { var list = studentList.Join(classList, s => s.ClassId, c => c.Id, (s, c) => new { Name = s.Name, CalssName = c.ClassName }); foreach (var item in list) { Console.WriteLine($"Name={item.Name},CalssName={item.CalssName}"); } } {//左连接 var list = from s in studentList join c in classList on s.ClassId equals c.Id into scList from sc in scList.DefaultIfEmpty()// select new { Name = s.Name, CalssName = sc == null ? "无班级" : sc.ClassName//c变sc,为空则用 }; foreach (var item in list) { Console.WriteLine($"Name={item.Name},CalssName={item.CalssName}"); } Console.WriteLine(list.Count()); } { var list = studentList.Join(classList, s => s.ClassId, c => c.Id, (s, c) => new { Name = s.Name, CalssName = c.ClassName }).DefaultIfEmpty();//为空就没有了 foreach (var item in list) { Console.WriteLine($"Name={item.Name},CalssName={item.CalssName}"); } Console.WriteLine(list.Count()); } { //左连接和右链接 就是链接对象交换一下即可; } #endregion } } |
Linq 原理
//1.存在一个集合,如果想过滤其中的数据;a.定义一个新集合, b.把之前的集合循环, c 判断 条件,把符合条件的对象 添加到这个新集合中;
//问题:1.三个条件的筛选:有很多的重复代码---封装
// 2.条件不一样:都需要来这么一波判断+循环
//2.开始改变:把循环这些动作转移到方法中,多发方法还是有很多重复代码:
//特点:重复的代码在每个方法中都有---固定的业务逻辑;
// 唯一不同的就是条件; 条件是可变的;
//可以把不变的业务逻辑保留,把可变的,不固定的业务逻辑转移出去,让外面给我指定---怎么转移? 把一个行为转移出去---就是把一个行为从外面给传递过来? 就可以用委托包装一个方法传递过来;
//3.以下CustomWhere就是Linq的演变过程
//4.思路:通过方法封装+不变的逻辑封装在方法中,可变的逻辑,通过委托传递+ 扩展方法----Linq语句
//5.我们这里是查询的是Student,如果要查询其他的类型呢?过滤Teacher...,就需要一个方法满足不同类型的需求;---泛型方法嘛
//6.Linq中的Where 跟我们这个是一样的吗?-Linq的底层都是通过迭代器来实现就是支持循环yield;
//技术手段叫来说:是通过扩展方法来完成的;
//7.在扩展IEnumable的时候,可以使用yield 关键字,yield关键字必须和IEnumable 成套使用:---状态机的实现,yield关键做到了按需获取,判断的时候,只要是符合条件的,就返回了,如果不符合就继续往后判断;
//8.以上所说其实是Linq to Object:通过方法封装+不变的逻辑封装在方法中,可变的逻辑,通过委托传递+ 扩展方法,IEnumerable 通常认为他是一个内存数据:还有IQueryable
迭代器yield ,查询到一个就直接返回,不会把集合全部查询完成一起返回