LINQ(Language Intergrated Query,语言集成查询)在C#编程语言中集成了查询语法,可以用相同的语法访问不同的数据源
1. LINQ概述
1.1 LINQ查询
1. 查询表达式必须以from子句开头,以select或group子句结束,在这两个子句之间,可以使用where,orderby,join,let和其他from子句
var query = from r in Racer.ToListRacersInfo()
where r.name == "aaa"
orderby r.age descending
select r;
1.2 扩展方法
使用Enumerable类中的扩展方法Where(), OrderByDescending()和Select()。这些方法都返回IEnumerable<Source>
var cham = new List<Racer>();
IEnumerable<Racer> brazcham = cham.Where(r =< r.name == "aaa").
OrderByDescending(r =< r.age).
Select(r =< r);
1.3 推迟查询的执行
2. 标准的查询操作符
下面查询要用的数据源:
//定义一个Student类
public class Student
{
public int Id;
public string Name;
public string Gender;
public int Age;
}
//定义一个 Person类
public class Person
{
public int Num;
public double salary;
public string address;
public int worktime;
}
List<Student> students = new List<Student>();
students.Add(new Student() { Id = 1, Name = "张三", Gender = "男", Age = 23 });
students.Add(new Student() { Id = 2, Name = "李四", Gender = "女", Age = 27 });
students.Add(new Student() { Id = 3, Name = "王五", Gender = "男", Age = 21 });
students.Add(new Student() { Id = 4, Name = "赵六", Gender = "女", Age = 25 });
students.Add(new Student() { Id = 3, Name = "老王", Gender = "男", Age = 21 });
students.Add(new Student() { Id = 4, Name = "麻子", Gender = "女", Age = 25 });
List<Person> person = new List<Person>();
person.Add(new Person() { Num = 1, salary = 1000, address = "番豆花园", worktime = 1 });
person.Add(new Person() { Num = 1, salary = 2000, address = "番豆大街", worktime = 2 });
person.Add(new Person() { Num = 3, salary = 3000, address = "番豆别墅", worktime = 3});
person.Add(new Person() { Num = 4, salary = 2000, address = "番豆超市", worktime = 2 });
person.Add(new Person() { Num = 2, salary = 4000, address = "番豆宠物店", worktime = 4 });
person.Add(new Person() { Num = 2, salary = 3000, address = "番豆幼儿园", worktime = 3 });
object[] obj = { "aa", "bb", "cc0", "dd", 1, 2, 3 };
string[] str = { "aa", "bba", "cc0", "dd" };
2.1 筛选(Where)
使用Where子句,可以合并多个表达式,筛选出符合条件的
1. 在一个查询中where子句可以出现多次
2. Where子句的表达式的结果类型是bool类型
//筛选出students中s.Id大于1并且s.Name包含王的数据
var wherestu = from s in students
where s.Id > 1 && s.Name.Contains("王")
select s;
//下面是使用Where扩展方法的写法
//把students中Id为1或5的数据筛选出来,相当于sql里的in用法:select * from 表 where Id in (1,5)
var query = students.Where(s => "1,5".Contains(s.Id.ToString()));
//var query = students.Where(a => a.Id == 1 || a.Id == 5).ToList();
//where子句和let一起使用,let可以让变量u = s.Count()
var wherestuquery = from s in str
where s.Contains("a")
let u = s.Count()
where u >0
select u;
2.2 用索引筛选(Where(r, index))
1. index是数据源中数据的索引, index从0开始索引
//Where(r,index)中的r就是List<Student>中的一个Student类的对象,index是students列表中的数据索引
//筛选出students中index为3的第四条数据,索引从0开始
var query = students.Where((r, index) => index == 3).ToList();
//返回name以A开头,索引为偶数的值
var qeury = from r in Racer.GetStuInfoList()
where ((r, index) => r.name.StartWith("A") && index % 2 == 0);
2.3 类型筛选(OfType())
根据类型筛选出所需数据,比如OfType<int>()就是筛选出所有的int类型的数据
//OfType<T>()方法根据类型筛选:找出obj数组中数据类型为int的数据
var oftypequery = obj.OfType<int>();
2.4 复合的from子句
如果要根据对象的一个成员进行筛选,而该成员本身是一个系列(比如集合),就使用复合from子句
//students来自List<Student>中,而s.classnames又来自Student其中一个参数List<ClassName>中,可以用from复合语句,可以访问Student里包含的List<ClassName>的数据
var mutifromquery = from s in students
from c in s.classnames
//select new{stuName是s.Name的别名,这个new了一个新名字stuName给s.Name,claName也是给c.classname的新名字}
select new { stuName =s.Name, claName = c.classname };
1. 把复合的from子句和LINQ查询转换为SelectMany()扩展方法,它可以用于迭代序列的序列
2.5 排序(Orderby)
descending降序
ascending升序
var qeury = from r in Racer.GetStuInfoList()
where r.age > 18 && (r.name == "apple" || r.sex == "female")
orderby r.age, r.sex descending
select r;
//所有r先按照age排名,再按照sex排名,再按照name排名,然后再用Take()扩展方法返回前10个结果
var qeury2 = Racer.GetStuInfoList().
OrderBy(r => r.age).ThenBy(r => r.sex).ThenBy(r => r.name).Take(10);
2.6 分组(group)
对查询结果进行分组,使用group子句
var qeury = from r in Racer.GetStuInfoList()
group r by r.age into g
orderby g.Count() descending g.Key
where g.Count()>2
select new
{
age = g.Key,
Count = g.Count()
};
var qeury2 = Racer.GetStuInfoList().
GroupBy(r=>r.age).
OrderByDescending(g=>g.Count()).
ThenBy(g=>g.Key).
Where(g=>g.Count()>2).
Select(g => new { age = g.Key,
Count = g.Count()});
2.7 LINQ查询中的变量(let)
let允许在LINQ查询中定义变量
var letquery = from r in students
group r by r.Id into g
//let让count等于g.Count()
let count = g.Count()
orderby count descending, g.Key
where count >= 2
select new
{
id = g.Key,
Count = count
};
2.8 对嵌套的对象分组
2.9 内连接
使用join子句可以根据特定的条件合并两个数据源,但之前要获得两个要链接的列表
1. 最终生成的结果集:先遍历第一个集合中的每一个元素,然后从第一个元素开始,就会根据指定条件去第二个集合中查找,如果匹配,则第一个元素和第二个集合找到的这个元素都添加到结果集中,直到第一个集合中的所有元素遍历完
2. 只有根据特定条件匹配到第二个集合中的数据,才会展示对应的第一个集合和第二个集合的数据。如果第一个集合中的某个元素在第二个集合中没有匹配元素,则它不会出现在结果集内
var joinquery = from s in students
//join p in person on 后面再跟特定的判断条件
//判断如果s.Id和p.Num相等,则数据添加到结果集中
join p in person on s.Id equals p.Num
//在select new {sid和pid是新生成的s.Id和pid的别名,把s.Id和p.Num的值赋给sid和pid}
select new { sid = s.Id, pid = p.Num };
2.10 左外连接
左外连接用join子句和DefaultIfEmpty方法定义。如果查询的数据没有匹配的数据,就使用DefaultIfEmpty方法定义其右侧的默认值(引用为null,值为0)
1. 不管第一个集合中的元素在第二个集合中有没有匹配的数据,都返回第一个集合中的的每一个元素(就直接返回第一个集合中的所有元素,不管第二个集合啥样)
//返回第一个集合中的所有name,如果和第二个集合中多次匹配,每匹配一次就输出一次
var leftjoinquery = from s in students
join p in person on s.Id equals p.Num into result
//DefaultIfEmpty操作符,为实序列提供一个默认的元素
from stuper in result.DefaultIfEmpty()
select new { s.Name, pName = (stuper == null ? String.Empty : stuper.address) };
2.11 组链接
分组联接可用于产生分层数据结构。 它将第一个集合中的每个元素与第二个集合中的一组相关元素进行配对(把第一个集合中的每一个元素依次拿出来,然后和第二个集合的所有元素依次匹配(join.....on特定的匹配条件)例如第一个集合中的第一个元素匹配到了第二个集合中第一,第二,第三个元素,则结果集中,就是第一个集合中的第一个元素,然后对应第二个集合中第1,2,3元素的数组)
var groupjoinquery = from s in students
join p in person on s.Id equals p.Num into gj
select new
{
name = s.Name,
claname = gj
};
foreach (var item in groupjoinquery)
{
//输出name
Console.WriteLine("{0}:", item.name);
//输出name对应的classname
foreach (var c in item.claname)
Console.WriteLine(" {0}", c.address);
}
//类似,List<string,List<Person>>,string为Student类中的name,
2.12 集合操作( Distinct()、Union()、Intersect()和Except())
扩展方法Distinct()、Union()、Intersect()和Except()都是集合操作
IList<string> first = new List<string>() { "One", "Two", "Three", "Two", "Three", "Four","Apple" };
IList<string> second = new List<string>() { "www", "Tssswo", "Three","Two"};
//如果,该数据元素实现了 IEquatable<T> 接口,优先从按该接口实现的 Equals 方法去重;
//当然,也可以向好多其他筛选方法一样,传一个 实现 IEqualityCompare<T> 的对象,这种方式的优先级最高.
//从first中删除first和second都有的元素,同时first中剩下的其他元素,会按照数据类型的默认比较方式去重;重复的数据在集合中只会留一个
//集合以first为主
var firstExcept = first.Except(second);
//firstExcept集中的值为:One Four Apple
//firstExcept.ToList().ForEach(f => Console.WriteLine(f));
返回first,second的交集,同样,也会按要求去重
///interfs交集中的值是first和second中相同的的值:Two Three
var interfs = first.Intersect(second);
//interfs.ToList().ForEach(f => Console.WriteLine(f));
返回first和second的并集,同样,也会按要求去重
///unionf交集中的值是first和second中所有不重复的值
var unionfs = first.Union(second);
unionfs.ToList().ForEach(f => Console.WriteLine(f));
//Distinct()去重:disfs的值是first中不重复的值
var disfs = first.Distinct();
2.13 分区(Take,TakeWhile,Skip,SkipWhile)
2.13.1 Take
Take(int n)表示将从序列的开头返回数量为n的连续元素,常用于分页。
1. 该方法只接受一个整数,表示要返回的结果的数量
//取students数据的前三个
var stutake = students.Take(3);
2.13.2 TakeWhile
TakeWhile操作符用于从输入序列中返回指定数量且满足一定条件的元素。(根据条件碰到的第一个元素不返回false,则继续往下比较,如果遇到了返回false的元素,则退出比较,返回前面所有的元素)
1. TakeWhile方法执行时将逐个比较序列中的每个元素是否满足指定条件,直到碰到不符合指定条件的元素时,返回前面所有的元素组成的序列
2. 当TakeWhile操作符在查找过程中,如果遇到的第一个元素就返回false则立即停止执行,跳出,不管后面还有没有符合条件的元素,即使后面有符合条件的元素也不会要了
//筛选出students中Id大于1的值
var stutakewhile = students.TakeWhile(a => a.Id > 1);
2.13.3 Skip
Skip操作符用于从输入序列中跳过指定数量的元素,返回由序列中剩余的元素所组成的新序列
1. 只接受一个整形的参数,表示跳过的元素数量
// 跳过3个元素
var skipstu = students.Skip(3);
//跳过2个元素,然后紧接着取2个元素
var skiptakestu = students.Skip(2).Take(2);
2.13.4 SkipWhile
SkipWhile操作符用于从输入序列中跳过满足一定条件指定数量的元素
1. 当SkipWhile操作符被调用时,会将输入序列中的元素走位参数传递给委托predicate,只要predicate的返回值为true,该元素就会被跳过,继续下一个元素,直到遇到一个使predicate返回值为false的元素,此元素以及输入序列中剩余的元素将组合一个新的序列返回。注意后面的不再判断,直接添加到返回序列
// 跳过students中Id值等于4的元素
var skipwhilestu = students.SkipWhile(a => a.Id == 4);
2.14 聚合操作符
聚合操作符(如Count、Sum、Min、Max、Average和Aggregate操作符)不返回一个序列,而返回一个值
详细看:linq操作符:聚合操作符 - .NET开发菜鸟 - 博客园 (cnblogs.com)
2.15 转换操作符
查询可以推迟到访问数据项时再执行。在迭代使用查询时,查询会执行。而使用转换操作符会立即执行查询,把查询结果放在数组、列表或字典中。
ToList()扩展方法, Lookup<TKey,TElement>, Dictionary<TKey,TValue>
2.16 生成操作符
生成操作符Range()、Empty()和Repeat()不是扩展方法,而是返回序列的正常静态方法
3. 并行LINQ
详细可见:LINQ 查询示例 - LINQ教程 - 菜鸟教程 (cainiaojc.com)