- LINQ既定义了查询语法(是指:类似于SQL查询的LINQ查询子句where等)
- LINQ也定义扩展方法(是指:查询操作符Where()等)
- 并不是所有的查询都可以用LINQ查询语法完成
- 并不是所有的LINQ扩展方法都可以映射到LINQ查询子句上
- 高级查询需要使用LINQ扩展方法
一些查询任务:
- 找出赢得至少15场比赛的巴西和奥地利赛车手
- 返回姓氏以A开头,索引为偶数的赛车手
Enumerable类定义的标准查询操作符
筛选操作
定义了返回元素的条件
- 包括:
Where()
和OfType<TResult>
Where
- 可以使用谓词,返回
bool
值- 例如:lamda表达式定义的谓词
Where(r => ...)
Where((r, index) => ...)
筛选条件里可以使用索引index
//使用LINQ扩展方法
public static void FilteringWithMethods()
{
var racers = Formula1.GetChampions()
.Where(r => r.Wins > 15 && (r.Country == "Brazil" || r.Country == "Austria"));
foreach (var r in racers)
{
Console.WriteLine($"{r:A}");
}
}
作为对比:
//使用LINQ查询语法:where子句
public static void Filtering()
{
var racers = from r in Formula1.GetChampions()
where r.Wins > 15 && (r.Country == "Brazil" || r.Country == "Austria")
select r;
foreach (var r in racers)
{
Console.WriteLine($"{r:A}");
}
}
用索引筛选:
public static void FilteringWithIndex()
{
var racers = Formula1.GetChampions()
.Where((r, index) => r.LastName.StartsWith("A") && index % 2 != 0);
foreach (var r in racers)
{
Console.WriteLine($"{r:A}");
}
}
OfType<TResult>
- 根据类型筛选元素
- 值返回
TRsult
类型的元素
public static void TypeFiltering()
{
object[] data = { "one", 2, 3, "four", "five", 6 };
//筛选出string类型
var query = data.OfType<string>();
foreach (var s in query)
{
Console.WriteLine(s);
}
}
投射操作
把对象转换成另一个类型的新对象
Select 和 SelectMany
- 根据函数来投射
- c#编译器会将复合from子句转化为
SelectMany()
扩展方法 SelectMany()
可以用来迭代序列中的序列,例如:Racer中的CarsSelectMany()
的一个重载:- 入参1:
Func<TSource, IEnumerable<TCollection>> collectionSelector
- 例如:
r => r.Racers
- 入参是
TSource
类型, 即Racer
类型的r
- 返回值是
IEnumerable<TCollection>
类型, 即IEnumerable<string>
类型的r.Racers
- 例如:
- 入参2:
Func<TSource, TCollection, TResult> resultSelector
- 例如:
(r, c) => new { Racer = r, Car = c}
- 入参是
TSource
类型和TCollection
类型,即Racer r
和string c
- 返回值是
TRsult
类型,即匿名类型,包含两个属性Racer
和Car
- 例如:
- 入参1:
public static void CompoundFromWithMethods()
{
var ferrariDrivers = Formula1.GetChampions()
.SelectMany(r => r.Cars, (r1, cars) => new { Racer1 = r1, Cars1 = cars })
.Where(item => item.Cars1.Contains("Ferrari"))
.OrderBy(item => item.Racer1.LastName)
.Select(item => $"{item.Racer1.FirstName} {item.Racer1.LastName}");
}
如果需要根据对象的一个成员进行筛选,而该成员本身是一个集合,就可以使用复合from子句
public static void CompoundFrom()
{
//复合from子句
var ferrariDrivers = from r in Formula1.GetChampions()
from c in r.Cars
where c == "Ferrari"
orderby r.LastName
select $"{r.FirstName} {r.LastName}";
}
排序操作
排序查询操作符 | 效果 |
---|---|
OrderBy | 升序排序 |
OrderByDescending | 降序排序 |
ThenBy | 第一次排序,其中有两项相同的话,可以进行第二次排序,升序 |
ThenByDescending | 第一次排序,其中有两项相同的话,可以进行第二次排序,降序 |
Reverse | 翻转顺序 |
- 上面这些扩展方法的返回值都是
IOrderEnumerable<TSource>
类型- 此类型派生自
IEnumerable<TSource>
- 多了一个
CreateOrderedEnumerable<TSource>()
方法
- 此类型派生自
- ThenBy和ThenByDescending
- 都需要在
IOrderEnumerable<TSource>
上工作 - 同时也返回
IOrderEnumerable<TSource>
- 可以添加任意多个
- 都需要在
orderby子句
解析为OrderBy()
方法orderby descending子句
解析为OrderByDescending()
方法
public static void SortDescendingWithMethods()
{
var racers = Formula1.GetChampions()
.Where(r => r.Country == "Brazil")
.OrderByDescending(r => r.Wins);
}
//可以添加任意多个ThenBy和ThenByDescending
public static void SortMultipleWithMethods()
{
var racers = Formula1.GetChampions()
.OrderBy(r => r.Country)
.ThenBy(r => r.LastName)
.ThenBy(r => r.FirstName)
.Take(10);
}
public static void SortDescending()
{
var racers = from r in Formula1.GetChampions()
where r.Country == "Brazil"
orderby r.Wins descending
select r;
}
public static void SortMultiple()
{
var racers = (from r in Formula1.GetChampions()
orderby r.Country, r.LastName, r.FirstName
select r).Take(10);
}
连接操作
合并不直接相关的集合
连接查询操作符 | 效果 |
---|---|
Join | 根据函数选择键,连接两个集合 |
GroupJoin | 根据函数选择键,连接两个集合,组合其结果 |
内连接
两个集合相当于求交集,得到c区域
扩展方法的写法:
public static void InnerJoinWithMethods()
{
var racers = Formula1.GetChampions()
.SelectMany(r => r.Years, (r1, year) =>
new
{
Year = year,
Name = $"{r1.FirstName} {r1.LastName}"
});
racers.Dump("将Racer按Year平铺");
var teams = Formula1.GetConstructorChampions()
.SelectMany(t => t.Years, (t, year) =>
new
{
Year = year,
t.Name
});
teams.Dump("将Team按Year平铺");
var racersAndTeams = racers.Join(
teams,
r => r.Year,
t => t.Year,
(r, t) =>
new
{
r.Year,
Champion = r.Name,
Constructor = t.Name
}).OrderBy(item => item.Year);
racersAndTeams.Dump("按Year合并");
}
扩展方法的输出:
子句语法的写法:
public static void InnerJoin()
{
var racers = from r in Formula1.GetChampions()
from y in r.Years
select new
{
Year = y,
Name = r.FirstName + " " + r.LastName
};
racers.Dump("将Racer按Year平铺");
var teams = from t in Formula1.GetConstructorChampions()
from y in t.Years
select new
{
Year = y,
t.Name
};
teams.Dump("将Team按Year平铺");
var racersAndTeams =
(from r in racers
join t in teams on r.Year equals t.Year
orderby t.Year
select new
{
r.Year,
Champion = r.Name,
Constructor = t.Name
}).Take(10);
racersAndTeams.Dump("按Year合并");
}
左外连接
左外连接,包括了b,c 区域
扩展方法的写法中,
GroupJoin()
来实现左外连接
public static void LeftOuterJoinWithMethods()
{
var racers = Formula1.GetChampions()
.SelectMany(r => r.Years, (r1, year) =>
new
{
Year = year,
Name = $"{r1.FirstName} {r1.LastName}"
});
var teams = Formula1.GetConstructorChampions()
.SelectMany(t => t.Years, (t, year) =>
new
{
Year = year,
Name = t.Name
});
var racersAndTeams =
racers.GroupJoin(
teams,
r => r.Year,
t => t.Year,
(r, ts) => new
{
Year = r.Year,
Champion = r.Name,
Constructors = ts
}).Dump("GroupJoin执行结束,两个集合merge在了一起")
.SelectMany(
item => item.Constructors.DefaultIfEmpty(), //取出merge完的表的item, 再取Constructors属性
(r, t) => new
{
Year = r.Year,
Champion = r.Champion,
Constructor = t?.Name ?? "no constructor championship"
});
racersAndTeams.Dump("按Year, 左外连接Racer集合和Team集合");
}
输出:
- 子句语法的写法中,使用
join
子句和DefaultIfEmpty
来实现左外连接
public static void LeftOuterJoin()
{
var racers = from r in Formula1.GetChampions()
from y in r.Years
select new
{
Year = y,
Name = r.FirstName + " " + r.LastName
};
var teams = from t in Formula1.GetConstructorChampions()
from y in t.Years
select new
{
Year = y,
t.Name
};
var racersAndTeams =
(from r in racers
join t in teams on r.Year equals t.Year into rt //用into, 给merge后的表一个名字rt
from t in rt.DefaultIfEmpty() //DefaultIfEmpty()表明进行左外连接
orderby r.Year
select new
{
r.Year,
Champion = r.Name,
Constructor = t == null ? "no constructor championship" : t.Name //指定,t没有对应的Year时,如何填充Year
});
racersAndTeams.Dump("按Year,将Racer和Team集合进行左外连接");
}
输出:
组连接
子句语法的写法:
public static void GroupJoin()
{
var racers = from cs in Formula1.GetChampionships()
from r in new List<(int Year, int Position, string FirstName, string LastName)>()
{
(cs.Year, Position: 1, FirstName: cs.First.FirstName(), LastName: cs.First.LastName()),
(cs.Year, Position: 2, FirstName: cs.Second.FirstName(), LastName: cs.Second.LastName()),
(cs.Year, Position: 3, FirstName: cs.Third.FirstName(), LastName: cs.Third.LastName())
}
select r;
racers.Dump("将1,2,3名平铺");
var q = (from r in Formula1.GetChampions()
join r2 in racers on //使用了元组,同时比较FirstName和LastName
(
r.FirstName,
r.LastName
)
equals
(
r2.FirstName,
r2.LastName
)
into yearResults //into子句,将第二个集合中的结果,添加到变量yearResults中
select //又使用了元组,创建了包含所需信息的新元组类型
(
r.FirstName,
r.LastName,
r.Wins,
r.Starts,
Results: yearResults
));
q.Dump("各人的获奖情况");
}
扩展方法的写法:
- 第二个参数和第三个参数,用来匹配两个集合,来产生新的第二个集合?
- 第四个参数接收第一个集合和第二个集合
public static void GroupJoinWithMethods()
{
var racers = Formula1.GetChampionships()
.SelectMany(cs => new List<(int Year, int Position, string FirstName, string LastName)>
{
(cs.Year, Position: 1, FirstName: cs.First.FirstName(), LastName: cs.First.LastName()),
(cs.Year, Position: 2, FirstName: cs.Second.FirstName(), LastName: cs.Second.LastName()),
(cs.Year, Position: 3, FirstName: cs.Third.FirstName(), LastName: cs.Third.LastName())
});
var q = Formula1.GetChampions()
.GroupJoin(racers,
r1 => (r1.FirstName, r1.LastName),
r2 => (r2.FirstName, r2.LastName),
(r1, r2s) => (r1.FirstName, r1.LastName, r1.Wins, r1.Starts, Results: r2s));
}
组合操作
把数据放在组中
- 根据一个关键字对查询结果分组 | 组合查询操作符 | 效果 | | --- | --- | | GroupBy | 组合有公共键的元素 | | ToLookup | 通过创建一个多对字典,来组合元素 |
GroupBy()
- 入参:
Func<TSource, TKey> keySelector
- 返回值:
IEnumerable<IGrouping<TKey, TSource>
- 其中,
IGrouping
定义了属性Key
,用来访问分组时指定的关键字
- 其中,
public static void GroupingWithMethods()
{
var countries = Formula1.GetChampions()
.GroupBy(r => r.Country)
.OrderByDescending(g => g.Count())
.ThenBy(g => g.Key)
.Where(g => g.Count() >= 2)
.Select(g => new
{
Country = g.Key,
Count = g.Count()
});
}
使用group子句
public static void Grouping()
{
var countries = from r in Formula1.GetChampions()
group r by r.Country into g
orderby g.Count() descending, g.Key
where g.Count() >= 2
select new
{
Country = g.Key,
Count = g.Count()
};
}
操作分组后, 组中的items
- 子句,可以在select子句上继续用操作组中的items
- 扩展方法可以用
IGrouping
的Group
属性来访问组中的items
public static void GroupingAndNestedObjects()
{
var countries = from r in Formula1.GetChampions()
group r by r.Country into g
let count = g.Count()
orderby count descending, g.Key
where count >= 2
select new
{
Country = g.Key,
Count = count,
Racers = from r1 in g
orderby r1.LastName
select r1.FirstName + " " + r1.LastName
};
}
public static void GroupingAndNestedObjectsWithMethods()
{
var countries = Formula1.GetChampions()
.GroupBy(r => r.Country)
.Select(g => new
{
Group = g,
g.Key,
Count = g.Count()
})
.OrderByDescending(g => g.Count)
.ThenBy(g => g.Key)
.Where(g => g.Count >= 2)
.Select(g => new
{
Country = g.Key,
g.Count,
Racers = g.Group.OrderBy(r => r.LastName).Select(r => r.FirstName + " " + r.LastName)
});
}
在LINQ查询中定义变量
- 可以用
let
子句 - 可以用
Select()
扩展方法来创建匿名类型 - 不过,在查询大列表的时候,创建的大量变量,可能需要以后进行垃圾回收,对性能产生很大影响
如下例子:定义变量,避免多次调用
Count()
方法
public static void GroupingWithVariables()
{
var countries = from r in Formula1.GetChampions()
group r by r.Country into g
let count = g.Count() // let子句定义变量count
orderby count descending, g.Key
where count >= 2
select new
{
Country = g.Key,
Count = count
};
}
public static void GroupingWithAnonymousTypes()
{
var countries = Formula1.GetChampions()
.GroupBy(r => r.Country)
.Select(g => new { Group = g, Count = g.Count() }) //匿名类型
.OrderByDescending(g => g.Count)
.ThenBy(g => g.Group.Key)
.Where(g => g.Count >= 2)
.Select(g => new
{
Country = g.Group.Key,
g.Count
});
}
限定操作
如果元素满足特定条件,限定操作符就返回布尔值
限定查询操作符 | 效果 |
---|---|
Any | 确定集合中是否有满足谓词的元素 |
All | 确定集合中的元素是否都满足谓词 |
Contains | 检查某个元素是否在集合中 |
分区操作
返回集合的一个子集
分区查询操作符 | 效果 |
---|---|
Take | 必须指定要从集合中提取的元素个数 |
Skip | 跳过指定个数个元素,提取其他元素 |
TakeWhile | 提取条件为真的元素。还可以传递一个谓词,根据谓词的结果提取 |
SkipWhile | 跳过条件为真的元素。还可以传递一个谓词,根据谓词的结果跳过 |
实现分页
Take
和Skip
结合起来,可以用来实现“分页”- 分页在Web应用程序中很有用,可以只显示一部分数据给用户
- 由于查询会【】在每个页面上执行,因此,改变底层的数据会影响结果
public static void Partitioning()
{
int pageSize = 5;
int numberPages = (int)Math.Ceiling(Formula1.GetChampions().Count() /
(double)pageSize);
numberPages.Dump("页数");
for (int page = 0; page < numberPages; page++)
{
var racers =
(from r in Formula1.GetChampions()
orderby r.LastName, r.FirstName
select r.FirstName + " " + r.LastName).
Skip(page * pageSize).Take(pageSize);
racers.Dump($"第{page+1}页");
}
}
Set操作符
返回一个集合
- 集合操作通过实体类的
GetHashCode()
和Equals()
来比较对象。 - 对于自定义的比较,还可以传递一个实现了
IEqualityComparer<T>
接口的对象
Set查询操作符 | 效果 |
---|---|
Distinct | 作用在单个集合上,从集合中删除重复元素 |
Union | 作用在两个集合上,返回出现在其中一个集合中的唯一元素 |
Intersect | 作用在两个集合上,返回两个集合中都出现的元素 |
Except | 作用在两个集合上,返回只出现在一个集合中的元素 |
Zip | 作用在两个集合上,把两个集合合并为一个 |
Intersect
//找出开法拉利和迈凯伦的冠军
public static void SetOperations()
{
//定义了一个本地方法racersByCar,以方便在此小小作用域内重用
IEnumerable<Racer> racersByCar(string car) =>
from r in Formula1.GetChampions()
from c in r.Cars
where c == car
orderby r.LastName
select r;
Console.WriteLine("World champion with Ferrari and McLaren");
foreach (var racer in racersByCar("Ferrari").Intersect(racersByCar("McLaren")))
{
Console.WriteLine(racer);
}
}
Zip 合并
允许用一个谓词函数,把两个相关序列合并为一个
- 这两个相关序列需要,使用相同的筛选和排序。这很重要,因为:
- 两个集合的,第一项和第一项合并,第二项和第二项合并,以此类推
- 如果项数不等,则到大项数较小的集合末尾时停止
Func<TFirst, TSecond, TResult>
类型,用来定义如何合并TFirst
为元素类型的集合和TSecond
为元素类型的集合- 下例,实现为一个lamda表达式:
(first, second) => first.Name + ", starts: " + second.Starts
- 下例,实现为一个lamda表达式:
public static void ZipOperation()
{
var racerNames = from r in Formula1.GetChampions()
where r.Country == "Italy"
orderby r.Wins descending
select new
{
Name = r.FirstName + " " + r.LastName
};
racerNames.Dump("racerNames");
var racerNamesAndStarts = from r in Formula1.GetChampions()
where r.Country == "Italy"
orderby r.Wins descending
select new
{
r.LastName,
r.Starts
};
racerNamesAndStarts.Dump("racerNamesAndStarts");
var racers = racerNames.Zip(racerNamesAndStarts, (first, second) => first.Name + ", starts: " + second.Starts);
racers.Dump("Zip之后的结果");
}
输出:
元素操作符
仅返回一个元素
元素查询操作符 | 效果 |
---|---|
First | 返回第一个满足条件的元素 |
FirstOrDefault | 返回第一个满足条件的元素,如果没找到,就返回类型的默认值 |
Last | 返回最后一个满足条件的元素 |
LastOrDefault | 返回最后一个满足条件的元素, 如果没找到,就返回类型的默认值 |
ElementAt | 指定了要返回的元素的位置 |
ElementAtOrDefault | 指定了要返回的元素的位置, 如果没找到,就返回类型的默认值 |
Single | 只返回一个满足条件的元素,如果有多个元素满足条件,就抛出一个异常 |
SingleOrDefault | 只返回一个满足条件的元素,如果有多个元素满足条件,就抛出一个异常, 如果没找到,就返回类型默认值 |
聚合操作符
计算集合的一个值。它不返回一个序列,而是返回一个值。
- Count
- Sum
- Min
- Max
- Average
- Aggregate:可以传递一个lamda表达式,该表达式对所有值进行聚合
public static void AggregateCount()
{
var query = from r in Formula1.GetChampions()
let numberYears = r.Years.Count()
where numberYears >= 3
orderby numberYears descending, r.LastName
select new
{
Name = r.FirstName + " " + r.LastName,
TimesChampion = numberYears
};
}
public static void AggregateSum()
{
var countries = (from c in
from r in Formula1.GetChampions()
group r by r.Country into c
select new
{
Country = c.Key,
Wins = (from r1 in c
select r1.Wins).Sum()
}
orderby c.Wins descending, c.Country
select c).Take(5);
}
转换操作符
将集合转换成数组:IEnumerable、IList、IDictionary等
- ToArray
- AsEnumerable
- ToList
- ToDictionary
Cast<TResult>
使用转换操作符会立即执行查询,将查询结果放到数组、列表或字典中
public static void ToList()
{
//查询立即执行
List<Racer> racers = (from r in Formula1.GetChampions()
where r.Starts > 200
orderby r.Starts descending
select r).ToList();
}
ToLookup()
- 一个键,对应,多个值
cr => cr.Car
是键选择器cr => cr.Racer
是元素选择器ToLookup(cr => cr.Car, cr => cr.Racer)
public static void ToLookup()
{
var racers = (from r in Formula1.GetChampions()
from c in r.Cars
select new
{
Car = c,
Racer = r
}).ToLookup(cr => cr.Car, cr => cr.Racer);
if (racers.Contains("Williams"))
{
foreach (var williamsRacer in racers["Williams"])
{
Console.WriteLine(williamsRacer);
}
}
}
Cast()
如果需要在非类型化集合(如 ArrayList)上使用LINQ查询,就可以使用
Cast()
public static void ConvertWithCast()
{
var list = new System.Collections.ArrayList(Formula1.GetChampions() as System.Collections.ICollection);
var query = from r in list.Cast<Racer>()
where r.Country == "USA"
orderby r.Wins descending
select r;
}
生成操作符
返回一个新集合
Empty()
,Range()
,Repeat()
- 不是扩展方法,而是正常的静态方法
- 实际上,返回的不是填充了值的集合,而是返回迭代器。是推迟执行的查询,其中只有一个
yield return
语句,来递增值。 - 可用于
Enumerable
类
public static void GenerateRange()
{
var values = Enumerable.Range(1, 20);
}
var values = Enumerable.Range(1,20).Select(n => n * 3);
生成查询操作符 | 效果 |
---|---|
Empty | 空集合。返回一个不返回值的迭代器。常做入参用 |
Range | 返回一系列数字 |
Repeat | 返回一个始终重复一个值的集合 |