反骨仔

一个业余的 .NET Core 攻城狮

0%

LINQ 基础

简介

LINQ语言集成查询,是 Language INtegrated Query 的缩写。

集成查询:为查询跨各种数据源和格式的数据提供了一致的模型。

语言集成查询:由于这种查询并没有创造新的语言而只是在现有的语言基础上来进行实现。

基础知识

特点

  • LINQ 主要用于对集合(含 IEnumerate、ICollection 等)进行操作
  • 通过扩展方法达到目的

按功能划分

  1. LINQ to Object:查询内存集合。

  2. LINQ to Provider:查询自定义数据源。

    XML、JSON 等都可以作为 Provider 对应的数据源,数据源对应的 LINQ 查询叫 LINQ to <数据源>。

    比如:LINQ to XML。

按语法划分

  1. SQL 风格
  2. 函数风格

SQL 风格

1
2
3
var list = from user in users
where user.Name.Contains("Wang")
select user.Id;

函数风格

效果同上。

1
2
3
var list = users
.Where(u => user.Name.Contains("Wang"))
.Select(u => u.id);

方法运用

First、Last 和 Single 等

  • First、FirstOrDefault、Last、LastOrDefault、Single 和 SingleOrDefault 是快速查询集合中的第一个或最后一个元素的方法

  • 如果集合是空的,First、Last 和 Single 都会报错,这时可以使用 FirstOrDefault、LastOrDefault 和 SingleOrDefault

  • Single/SingleOrDefault 和其它方法的区别是,它限定查询结果只有一个元素,如果查询结果集合中包含多个元素时会报错

示例

1
2
3
4
5
6
7
8
new[] { "a", "b" }.First(x => x.Equals("b")); // 返回 ”b“
new[] { "a", "b" }.First(x => x.Equals("c")); // 抛出 InvalidOperationException 异常
new[] { "a", "b" }.FirstOrDefault(x => x.Equals("c")); // 返回 null

new[] { "a", "b" }.Single(x => x.Equals("b")); // 返回 ”b“
new[] { "a", "b" }.Single(x => x.Equals("c")); // 抛出 InvalidOperationException 异常
new[] { "a", "b" }.SingleOrDefault(x => x.Equals("c")); // 返回 null
new[] { "a", "a" }.Single(); // 抛出 InvalidOperationException 异常

备注

  • 如果要确保查询结果的唯一性(比如通过手机号查询用户),建议使用 Single/SingleOrDefaut
  • 如果只是判断元素是否存在,使用 Any 比 FirstOrDefault 更高效
  • 其它情况应尽量使用 First/FirstOrDefault

Except 取差集

取出集合中与另一个集合所有元素中不同的元素。

示例

1
2
3
4
5
6
7
8
9
int[] first = { 1, 2, 3, 4 };
int[] second = { 0, 2, 3, 5 };
IEnumerable<int> result = first.Except(second);
// result = { 1, 4 }

int[] second = { 0, 2, 3, 5 };
int[] third = { 1, 1, 1, 2, 3, 4 };
IEnumerable<int> result = third.Except(second);
// result = { 1, 4 }

注意

  • Except 方法会去除重复元素

SelectMany 集合降维

把多维集合降维,比如把二维的集合平铺成一个一维的集合。

示例

1
2
3
4
5
6
7
var collection = new int[][]
{
new int[] {1, 2, 3},
new int[] {4, 5, 6},
};
var result = collection.SelectMany(x => x);
// result = [1, 2, 3, 4, 5, 6]

示例 2 - 迪卡尔积运算

1
2
3
4
var list1 = new List<string> { "a1", "a2" };
var list2 = new List<string> { "b1", "b2", "b3" };
var result = list1.SelectMany(x => list2.Select(y => $"{x}{y}"));
// result = ["a1b1", "a1b2", "a1b3", "a2b1", "a2b2", "a2b3"]

Aggregate 聚合

对一个集合依次执行类似累加器的操作,就像滚雪球一样把数据逐步聚集在一起。

示例:累加

从 1 加到 10。

1
2
3
int[] numbers = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
int sum = numbers.Aggregate((prevSum, current) => prevSum + current);
// sum = 55

示例:拼接字符串

1
2
3
string[] stringList = { "Hello", "World", "!" };
string joinedString = stringList.Aggregate((prev, current) => prev + " " + current);
// joinedString = "Hello World !"

Join 关联查询

和 SQL 查询一样,LINQ 同样支持 Inner Join、Left Join、Right Join、Cross Join 和 Full Outer Join。

示例

数据源:

1
2
var first = new List<string>() { "a","b","c" }; // 左边
var second = new List<string>() { "a", "c", "d" }; // 右边

Inner Join

1
2
3
4
5
6
7
8
9
10
11
12
var result = from f in first
join s in second on f equals s
select new { f, s };

// 等同使用扩展方法:
var result = first.Join(second,
f => f,
s => s,
(f, s) => new { f, s });

// result: {"a","a"}
// {"c","c"}

Left Join

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
var result = from f in first
join s in second on f equals s into temp
from t in temp.DefaultIfEmpty()
select new { First = f, Second = t };

// 或者:
var result = from f in first
from s in second.Where(x => x == f).DefaultIfEmpty()
select new { First = f, Second = s };

// 等同使用扩展方法:
var result = first.GroupJoin(second,
f => f,
s => s,
(f, s) => new { First = f, Second = s })
.SelectMany(temp => temp.Second.DefaultIfEmpty(),
(f, s) => new { First = f.First, Second = s });

// result: {"a","a"}
// {"b", null}
// {"c","c"}

Right Join

1
2
3
4
5
6
7
8
9
10
var result = from s in second
join f in first on s equals f into temp
from t in temp.DefaultIfEmpty()
select new { First = t, Second = s };

// 其它和 Left Join 类似

// result: {"a","a"}
// {"c","c"}
// {null,"d"}

Cross Join

1
2
3
4
5
6
7
8
9
10
11
12
13
var result = from f in first
from s in second
select new { f, s };

// result: {"a","a"}
// {"a","c"}
// {"a","d"}
// {"b","a"}
// {"b","c"}
// {"b","d"}
// {"c","a"}
// {"c","c"}
// {"c","d"}

Full Outer Join

1
2
3
4
5
6
7
8
9
var leftJoin = from f in first
join s in second on f equals s into temp
from t in temp.DefaultIfEmpty()
select new { First = f, Second = t };
var rightJoin = from s in second
join f in first on s equals f into temp
from t in temp.DefaultIfEmpty()
select new { First = t, Second = s };
var fullOuterJoin = leftJoin.Union(rightJoin);

Skip & Take 分页

  • Skip 扩展方法用来跳过从起始位置开始的指定数量的元素读取集合

  • Take 扩展方法用来从集合中只读取指定数量的元素

示例

1
2
3
4
var values = new[] { 5, 4, 3, 2, 1 };
var skipTwo = values.Skip(2); // { 3, 2, 1 }
var takeThree = values.Take(3); // { 5, 4, 3 }
var skipOneTakeTwo = values.Skip(1).Take(2); // { 4, 3 }

扩展 - SkipWhile、TakeWhile

  • SkipWhile 从起始位置开始忽略元素,直到遇到不符合条件的元素则停止忽略,往后就是要查询的结果
  • TakeWhile 从起始位置开始读取符合条件的元素,一旦遇到不符合条件的就停止读取,即使后面还有符合条件的也不再读取

示例

1
2
3
4
5
6
7
8
9
// SkipWhile 
int[] list = { 42, 42, 6, 6, 6, 42 };
var result = list.SkipWhile(i => i == 42);
// result: 6, 6, 6, 42

// TakeWhile
int[] list = { 1, 10, 40, 50, 44, 70, 4 };
var result = list.TakeWhile(item => item < 50).ToList();
// result = { 1, 10, 40 }

Zip 拉链

操作的对象是两个集合,它就像拉链一样,根据位置将两个系列中的每个元素依次配对在一起。

示例

1
2
3
4
5
6
7
8
9
10
11
12
13
int[] numbers = { 3, 5, 7 };
string[] words = { "three", "five", "seven", "ignored" };
IEnumerable<string> zip = numbers.Zip(words, (n, w) => n + "=" + w);

foreach (string s in zip)
{
Console.WriteLine(s);
}

// 输出
// 3=three
// 5=five
// 7=seven

OfType 和 Cast 类型过滤与转换

  • OfType 用于筛选集合中指定类型的元素
  • Cast 可以把集合转换为指定类型

示例

数据源:

1
2
3
4
5
6
7
8
9
interface IFoo { }
class Foo : IFoo { }
class Bar : IFoo { }

var item0 = new Foo();
var item1 = new Foo();
var item2 = new Bar();
var item3 = new Bar();
var collection = new IFoo[] { item0, item1, item2, item3 };

OfType 示例

1
2
3
4
5
6
7
var foos = collection.OfType<Foo>(); // result: item0, item1
var bars = collection.OfType<Bar>(); // result: item2, item3
var foosAndBars = collection.OfType<IFoo>(); // result: item0, item1, item2, item3

// 等同于使用 Where
var foos = collection.Where(item => item is Foo); // result: item0, item1
var bars = collection.Where(item => item is Bar); // result: item2, item3

Cast 示例

1
2
3
var bars = collection.Cast<Bar>();  // InvalidCastException 异常
var foos = collection.Cast<Foo>(); // InvalidCastException 异常
var foosAndBars = collection.Cast<IFoo>(); // OK

ToLookup 索引式查找

可索引查找的数据结构,它是一个 ILookup 实例,所有元素根据指定的键进行分组并可以按键进行索引。

示例

1
2
3
4
5
6
7
string[] array = { "one", "two", "three" };
// 根据元素字符串长度创建一个查找对象
var lookup = array.ToLookup(item => item.Length);

// 查找字符串长度为 3 的元素
var result = lookup[3];
// result: one,two

示例二

1
2
3
4
5
6
7
8
9
10
11
int[] array = { 1,2,3,4,5,6,7,8 };
// 创建一个奇偶查找(键为 0 和 1)
var lookup = array.ToLookup(item => item % 2);

// 查找偶数
var even = lookup[0];
// even: 2,4,6,8

// 查找奇数
var odd = lookup[1];
// odd: 1,3,5,7

Distinct 去重

去除重复项。

示例

1
2
3
int[] array = { 1, 2, 3, 4, 2, 5, 3, 1, 2 };
var distinct = array.Distinct();
// distinct = { 1, 2, 3, 4, 5 }

ToDictionary 字典转换

把集合 IEnumerable<TElement> 转换为 Dictionary<TKey, TValue> 结构的字典,接收一个 Func<TSource, TKey> 参数用来返回每个元素指定的键与值。

示例

1
2
IEnumerable<User> users = GetUsers();
Dictionary<int, User> usersById = users.ToDictionary(x => x.Id);

Range 和 Repeat

用于生成简单的数字或字符串系列。

示例

1
2
3
4
5
// 生成 1-100 的数字,即结果为 [1, 2, ..., 99, 100]
var range = Enumerable.Range(1, 100);

// 生成三个重复的字符串“a”,即结果为 ["a", "a", "a"]
var repeatedValues = Enumerable.Repeat("a", 3);

Any 和 All

  • Any 用来判断集合中是否存在任一一个元素符合条件
  • All 用来判断集合中是否所有元素符合条件

示例

1
2
3
4
5
var numbers = new int[] {1, 2, 3, 4, 5 };
bool result = numbers.Any(); // true
bool result = numbers.Any(x => x == 6); // false
bool result = numbers.All(x => x > 0); // true
bool result = numbers.All(x => x > 1); // false

Concat 和 Union

用来拼接两个集合。

  • Concat:不会去除重复元素
  • Union:去除重复项

示例

1
2
3
4
5
6
7
8
9
10
List<int> foo = newList<int> { 1, 2, 3 };
List<int> bar = newList<int> { 3, 4, 5 };

// 通过 Enumerable 类的静态方法
var result = Enumerable.Concat(foo, bar).ToList(); // 1,2,3,3,4,5

// 通过扩展方法
var result = foo.Concat(bar).ToList(); // 1,2,3,3,4,5

var result = foo.Union(bar); // 1,2,3,4,5

GroupBy 分组

对集合进行分组。

示例

根据奇偶分组。

1
2
3
var list = new List<int>() { 1, 2, 3, 4, 5, 6, 7, 8, 9 };
var grouped = list.GroupBy(x => x % 2 == 0);
// grouped: [1, 3, 5, 7, 9] 和 [2, 4, 6, 8]

DefaultIfEmpty 空替换

表示在没有查询到指定条件的元素时使用元素的默认值代替。

示例

1
2
3
4
var chars = new List<string>() { "a", "b", "c", "d" };
chars.Where(s => s.Length > 1).DefaultIfEmpty().First(); // 返回 null
chars.DefaultIfEmpty("N/A").FirstOrDefault(); // 返回 "a"
chars.Where(s => s.Length > 1).DefaultIfEmpty("N/A").FirstOrDefault(); // 返回 "N/A"

SequenceEqual 集合相等

用于比较集合系列各个相同位置的元素是否相等。

1
2
3
4
5
6
int[] a = new int[] {1, 2, 3};
int[] b = new int[] {1, 2, 3};
int[] c = new int[] {1, 3, 2};

bool result1 = a.SequenceEqual(b); // true
bool result2 = a.SequenceEqual(c); // false

其它常用方法

  • OrderBy(排序)
  • Sum(求和)
  • Count(计数)
  • Reverse(反转)
  • Where(筛选、过滤)

参考

08:强大的LINQ