C# LINQ 初学者指南





5.00/5 (2投票s)
让我们来了解一下LINQ的基础知识以及如何使用它。
引言
LINQ自2007年推出以来,越来越受欢迎。它是一种快速迭代和过滤列表的绝佳方式,无论是数据值列表还是对象列表。如果您使用Entity Framework,那么您很可能正在使用LINQ。但是,LINQ是什么?这个框架的基础知识又是什么?
什么是LINQ?
LINQ是Language Integrated Query(语言集成查询)的缩写,它是对传统的查询方式的替代。我们过去将查询写成string
,这使得它们对拼写敏感,不易更改,并且无法使用IntelliSense,而且它们只是一个巨大的string
。LINQ解决了这些“问题”。
LINQ基本上由多个扩展组成,您可以在不同类型上使用它们。但我们主要在列表上使用LINQ(IEnumerable, List<>
,List
,数组,IQueryable, Dictionary<>
等)。
可以对不同的数据源使用LINQ
- 对象
- ADO
- XML
- 实体框架
- SQL 数据库
- 其他,只要您使用
IQueryable
或IEnumerable
LINQ有两种变体:Lambda表达式和查询表达式。查询表达式看起来最像SQL语句。在性能方面,它们之间没有区别。这更多地取决于您想如何使用它们以及您的偏好。
Lambda表达式与查询表达式
正如我所说,从性能上看,Lambda和查询表达式之间没有区别。但在语法上却有很大的不同。请看下面的代码
internal class LinqDemo
{
private List<string> movieTitles =
new() { "The Matrix", "Shrek", "Inception", "Ice Age", "John Wick" };
public List<string> Search(string query)
{
var resultLambda = movieTitles.Where
(x => x.Contains(query, StringComparison.CurrentCultureIgnoreCase));
var resultQuery = from titles in movieTitles
where titles.Contains(query)
select titles;
return null;
}
}
让我们仔细看看
var resultLambda = movieTitles.Where
(x => x.Contains(query, StringComparison.CurrentCultureIgnoreCase));
上面的那一行是Lambda表达式。它有一个列表上的扩展和一个表达式体。
var resultQuery = from titles in movieTitles where titles.Contains(query) select titles;
这是一个查询表达式。如果您熟悉TSQL,这段代码应该看起来很熟悉。
这两行代码返回相同的结果,即一个IEnumerable
。唯一的区别是语法。我个人更喜欢Lambda。纯粹是因为对我来说,写起来更简单。我只在需要连接列表时使用查询表达式。
所以,Lambda
- 创建一个匿名函数
- 其特征是Lambda运算符。
- 具有执行Lambda并保留内存的方法
查询
- 看起来像TSQL
- 易于识别
- 被转录为Lambda
- 仅在读取或使用时执行
没有哪个更好,因为这取决于个人喜好。对我来说,Lambda效果更好。所以接下来的例子将使用Lambda。
Lambda解释
让我们看看之前的代码
var resultLambda = movieTitles.Where(x => x.Contains
(query, StringComparison.CurrentCultureIgnoreCase));
这里发生了什么?简而言之:resultLamba
包含所有包含查询中存储的值的电影。假设查询包含“a
”,那么只有《黑客帝国》和《冰河时代》会被放入resultQuery
。Where
方法是仅从列表中返回与输入序列匹配的值的方法(x.Contains(….)
,其中x
代表列表中的一个项)。
另一个使用Where
的例子
List<Movie> movieTitles = new()
{
new()
{
Id = 1,
Title = "The Matrix",
HasBeenSeen = true
},
new()
{
Id = 2,
Title = "Shrek",
HasBeenSeen = true
},
new()
{
Id = 3,
Title = "Inception",
HasBeenSeen = true
},
new()
{
Id = 4,
Title = "Ice Age",
},
new()
{
Id = 5,
Title = "John Wick"
}
};
var seenMovies = movieTitles.Where(x => x.HasBeenSeen);
foreach (var item in seenMovies)
{
Console.WriteLine(item.Title);
}
public class Movie
{
public int Id { get; set; }
public string Title { get; set; }
public bool HasBeenSeen { get; set; }
}
我创建了一个包含Id、标题和一个布尔值HasBeenSeen
的电影列表,该列表表示我是否已经看过这部电影(true
)或没有(false
)。
让我们放大以下一行
var seenMovies = movieTitles.Where(x => x.HasBeenSeen);
在这里,我使用Where
方法来过滤掉已经看过的电影。X
代表movieTitles
列表中的一部电影。
Where
方法基本上是For
-Each
循环的简写。我可以重写这一行,不使用Lambda表达式,而是使用简单的for
-each
循环。
List<Movie> seenMovies = new();
foreach (var movie in movieTitles)
{
if (movie.HasBeenSeen)
seenMovies.Add(movie);
}
foreach (var item in seenMovies)
{
Console.WriteLine(item.Title);
}
正如您所见,LINQ不仅适用于列表,而且还使代码更简洁。
最常用的LINQ语句
以下是常用LINQ语句的小列表。
其中
根据谓词过滤列表。请参阅上面的示例。
movieTitles.Where(x => x.HasBeenSeen);
Select
选择要返回的字段或属性,并将它们放入一个新的(匿名)对象中。或返回单个属性的列表。
var seenMovies = movieTitles.Select(x => new
{
x.Title,
NeedToSee = x.HasBeenSeen,
});
var seenMovies = movieTitles.Select(x => x.Title);
Single(OrDefault)
返回一个元素,由谓词过滤。如果找不到元素,将抛出异常。为避免异常,请使用SingleOrDefault
。如果找不到元素,返回值将为NULL
。如果有多个元素,即使使用SingleOrDefault
,也将抛出异常。
如果单个元素是int
或布尔值,则默认值将不是NULL
。它将是0
或false
,因为整数和布尔值不能为NULL
。
var found = movieTitles.SingleOrDefault(x => x.HasBeenSeen); // Throws exception
var found = movieTitles.SingleOrDefault(x => x.Title.Contains("z")); // returns NULL
var found = movieTitles.SingleOrDefault
(x => x.Title.Equals("The Matrix")); // returns the movie The Matrix.
任意
any
用于检查特定过滤器是否为true
。例如:您想检查是否有尚未观看的电影。而不是使用Where
并检查项目数量是否大于1,您可以使用Any
方法。
if(movieTitles.Any(x=> !x.HasBeenSeen))
{
Console.WriteLine("There are still movies to be seen.");
}
请注意第1行上的感叹号(!)。这意味着HasBeenSeen
必须为false
。
OrderBy(Desc)
根据特定属性对列表进行排序。有两种变体:
OrderBy
按A-Z或0-9排序OrderByDesc
按Z-A或9-0排序
OrderBy(Desc)
返回一个新类型的对象IOrderedQueryable
。
var ordered = movieTitles.OrderBy(x => x.Title);
First(OrDefault)/Last(OrDefault)
First语句返回列表中的第一个项。如果列表为空,它将抛出异常。为避免此异常,您可以使用FirstOrDefault
,它与SingleOrDefault
有相同的概念。
Last语句返回列表中的最后一个项。如果列表为空,它将抛出异常。为避免此异常,您可以使用LastOrDefault
,它与SingleOrDefault
有相同的概念。
var lastItem = movieTitles.Last();
var firstItem = movieTitles.First();
var firstItemWithPredicate = movieTitles.First(x => x.HasBeenSeen);
最后一行在过滤HasBeenSeen
后返回列表中的第一个项,该过滤仅显示已观看的电影。
链式调用LINQ语句
LINQ最让我喜欢的地方之一是您可以链式调用方法。这意味着您可以将所有LINQ语句放在一起,并且很少需要创建新变量。示例:
Movie first = movieTitles.OrderBy(x => x.Title).Where(x => x.Id > 3).First();
在上面的示例中,我在一行中组合了多个LINQ语句。
IEnumerable与List
啊,这是我在找新工作面试时经常被问到的一个问题。
“你能告诉我IEnumerable
和List
的区别吗?”
通常,我只是说一个接口,另一个是对象……我没说错。但这不是他们想听的。
如果您正在使用或开始使用LINQ,您应该多了解一些区别。特别是LINQ返回IEnumerable
值的原因。
IEnumerable
尚未执行。List()
会在IEnumerable
上执行查询。- 您可以继续在
IEnumerable
上进行过滤,而无需执行过滤。 IEnumerable
提供了延迟执行
直接返回值是一个存储执行操作所需所有信息的对象。此方法表示的查询直到对象被枚举时才执行,无论是直接调用其GetEnumerator方法,还是在Visual C#中使用foreach,或在Visual Basic中使用For Each。
所以,基本上,IEnumerable
存储稍后将执行的信息。但是……为什么呢?让我们来看看数据库。
LINQ与Entity Framework
如果您使用Entity Framework,您很可能通过LINQ从实体获取数据、过滤它们或其他操作。想象一下有一个包含电影的数据库表。该表包含100,000部电影。假设只有10部电影的标题中包含“The”……您是从数据库中获取100,000部电影,然后再进行过滤吗?最好是在SQL查询中进行过滤,如下所示:
SELECT * FROM Movies WHERE Title like '%the%'
但我们正在使用Entity Framework。过滤“the”将通过Where
语句完成,看起来像这样:
DataContext dbContext = new();
var seenMovies = dbContext.Movies
.Where(x => x.Title.Contains("the"));
让我们启动应用程序并调试“seenMovies
”。请注意,seenMovies
变量仍然是一个IEnumerable
。
seenMovies
的类型是EntityQueryable
,它继承自IEnumerable
。属性“Results View”有一些警告。
这意味着,如果您展开它,IEnumerable
的查询将在(SQL)服务器上执行。
现在,让我们执行相同的代码,但有一个小的更改:
DataContext dbContext = new(); var seenMovies = dbContext.Movies .Where(x => x.Title.Contains("the")) .ToList();
结果不同:您现在有一个包含一部电影的电影列表(这是预期的)。IEnumerable
的查询已执行,并产生一个List<Movie>
。
那么,延迟执行查询的最大优点是什么?嗯,您可以在执行之前实现多个LINQ语句。假设我们创建一个带有扩展过滤器的方法。过滤器如下所示:
public class Filter
{
public string query { get; set; }
public bool? hasBeenSeen { get; set; }
public string OrderBy { get;set; }
}
所有属性都是可选的。如果查询为空,则不按查询过滤。如果hasBeenSeen
为NULL
,则显示已观看和未观看的电影。现在是方法的实现:
public List<Movie> Filter(Filter filter)
{
IEnumerable<Movie> movies = dbContext.Movies;
if (!string.IsNullOrEmpty(filter.query))
movies = movies.Where(x => x.Title.Contains
(filter.query, StringComparison.CurrentCultureIgnoreCase));
if (filter.hasBeenSeen.HasValue)
movies = movies.Where(x => x.HasBeenSeen == filter.hasBeenSeen.Value);
return movies.ToList();
}
首先,我从上下文(dbContext.Movies
)获取电影。嗯,不完全是。我建立了一个链接(非LINQ)到电影。然后我检查查询是否已填充。如果是,我将Where
添加到movies
。之后,我检查hasBeenSeen
是否有值。如果有,我再添加一个Where
到movies
。
现在,我对实体执行了三个LINQ操作,但数据尚未从实际数据库中检索。这仅发生在最后一行(return movies.ToList()
)。
如果您使用数据库分析器,在最后一行(return movies.ToList()
)设置断点,并启动应用程序,您会注意到在您通过该行之前,没有任何内容在数据库上执行。
结论
好了,这涵盖了大多数LINQ的基础知识。我希望您注意到您可以相当轻松地使用它。网上有大量的文档可以帮助您解决LINQ方面的挑战。如果您想了解更多关于该主题的信息,请查看Microsoft Learn。
历史
- 2022年12月16日:初稿