65.9K
CodeProject 正在变化。 阅读更多。
Home

C# LINQ 初学者指南

starIconstarIconstarIconstarIconstarIcon

5.00/5 (2投票s)

2022年12月16日

CPOL

8分钟阅读

viewsIcon

23018

让我们来了解一下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 数据库
  • 其他,只要您使用IQueryableIEnumerable

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”,那么只有《黑客帝国》和《冰河时代》会被放入resultQueryWhere方法是仅从列表中返回与输入序列匹配的值的方法(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。它将是0false,因为整数和布尔值不能为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

啊,这是我在找新工作面试时经常被问到的一个问题。

“你能告诉我IEnumerableList的区别吗?”

通常,我只是说一个接口,另一个是对象……我没说错。但这不是他们想听的。

如果您正在使用或开始使用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”有一些警告。

展开“Results View”将枚举IEnumerable。

这意味着,如果您展开它,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; }
}

所有属性都是可选的。如果查询为空,则不按查询过滤。如果hasBeenSeenNULL,则显示已观看和未观看的电影。现在是方法的实现:

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是否有值。如果有,我再添加一个Wheremovies

现在,我对实体执行了三个LINQ操作,但数据尚未从实际数据库中检索。这仅发生在最后一行(return movies.ToList())。

如果您使用数据库分析器,在最后一行(return movies.ToList())设置断点,并启动应用程序,您会注意到在您通过该行之前,没有任何内容在数据库上执行。

结论

好了,这涵盖了大多数LINQ的基础知识。我希望您注意到您可以相当轻松地使用它。网上有大量的文档可以帮助您解决LINQ方面的挑战。如果您想了解更多关于该主题的信息,请查看Microsoft Learn

历史

  • 2022年12月16日:初稿
© . All rights reserved.