理解 C# 中的 IQueryable<T>

2025-06-11

理解 C# 中的 IQueryable<T>

你好!👋🏻

你可能用过,如果你之前用过 C# 写过代码,IEnumerable<T>那肯定也用过。这两个函数非常流行,而且在你学习这门语言的初期就会接触到。但是你听说过吗?这个函数稍微高级一些,但它的功能非常强大,你应该了解一下,这样才能充分利用它。 女士们先生们,我们将踏上一段揭秘界面的旅程,所以……来杯咖啡,我们开始吧!☕️List<T>IQueryable<T>
IQueryable

什么IQuyerable<T>?🤔

IQueryable接口是 LINQ 的基石,而 LINQ 是 C# 最强大的功能之一。该接口专为从实现 的各种数据源IQueryable<T>(例如 SQL 数据库或类似 的内存集合)查询数据而设计List<T>IQueryable其独特之处在于其强大的功能,使其在数据查询方面功能多样且高效。

首先让我们来重点介绍一下这个界面的特性,正是这些特性赋予了它特殊性和独特性。

1:延迟执行🦥

延迟执行恰好是 的一个特性IEnumerable,也被 继承IQueryable。简单来说,就是将查询的执行延迟到真正需要数据的时候。还没明白?继续阅读。

让我们看一下这个基本的代码片段,以更实际的方式了解延迟执行的含义:


List<FamousPerson> famousPeople =
[
    new FamousPerson(1, "Sandy Cheeks", false),
    new FamousPerson(2, "Tony Stark", true),
    new FamousPerson(3, "Captain Marvel", true),
    new FamousPerson(4, "Captain America", true),
    new FamousPerson(5, "SpongeBob SquarePants", false),
    new FamousPerson(6, "Hulk", false)
];

IQueryable<FamousPerson> famousAndCanFly = famousPeople
    .AsQueryable()
    .Where(x => x.CanFly);

foreach (var fp in famousAndCanFly)
{
    Console.WriteLine($"{fp.Name} can FLY!");
}

record FamousPerson(int Id, string Name, bool CanFly);

Enter fullscreen mode Exit fullscreen mode

我希望你复制代码,并使用调试器查看 的值的famousAndCanFly计算结果。你可能认为它会是一个包含 3 个人的集合,但实际上并非如此。你会看到该值不包含任何数据,但一旦你进入循环foreach,结果就会被执行。这就是延迟执行的含义,执行被延迟到实际需要数据时(即在循环内枚举查询结果foreach)。

2:表达式树🌳

我已经将一些额外的查询过滤器链接到前一个查询,所以现在它看起来像这样:


IQueryable<FamousPerson> famousAndCanFly = famousPeople
    .AsQueryable()
    .Where(x => x.CanFly);

famousAndCanFly = famousAndCanFly
    .Where(x => x.Id < 3);

famousAndCanFly = famousAndCanFly.
    Where(x => x.Name.Contains("s", StringComparison.OrdinalIgnoreCase));

Console.WriteLine(famousAndCanFly.Expression);

Enter fullscreen mode Exit fullscreen mode

这里只是基本的 LINQ 扩展方法调用,但需要注意的是,最后一行的控制台日志。Expression的属性是什么IQueryable?它本质上是一个表达式树,IQueryable在你编写查询时会组合在一起。它允许数据源提供程序抓取查询表达式树,并将其转换为可用于该数据源的内容。因此,数据源提供程序必须提供 的实现IQueryable

如果您运行前面的代码,它会打印如下内容:
System.Collections.Generic.List1[FamousPerson].Where(x => x.CanFly).Where(x => (x.Id < 3)).Where(x => x.Name.Contains("s", OrdinalIgnoreCase))

你看,它只是一堆链式查询表达式和 where 语句,在我们的例子中,数据源恰好是内存中的集合,并且由于它实现了IQueryable,你可以打赌它知道如何正确地将其转换为内存集合可以理解的内容。

IQueryable功能示例

我之前说过,这IQueryable是命名空间的一部分System.Linq,所有你能用 LINQ 处理其他集合的操作,都可以在这个接口上完成。通过使用WhereOrderBy以及Select其他任何操作,你可以不断地链接方法调用,从而编写出你能想象到的最复杂的查询。你实际上只是受限于你对 LINQ 的了解程度。


var filtered = query.Where(x => x.Age > 30);

Enter fullscreen mode Exit fullscreen mode

var orderedDesc = query.OrderByDescending(x => x.Name);

Enter fullscreen mode Exit fullscreen mode

 var projected = query.Select(x => new { x.Name, x.Age });

Enter fullscreen mode Exit fullscreen mode

var firstOrDefault = query.FirstOrDefault();
var lastOrDefault = query.LastOrDefault();
var single = query.Single(); 

Enter fullscreen mode Exit fullscreen mode

3:如此多的优化⚙️

由于查询被构造为表达式,因此提供程序(例如Entity Framework Core)可以采用该表达式树并将其转换为适合数据存储的查询语言,例如SQL ServerPostgreSQLSQL,或内存数据存储的LINQ 。

由于转换由提供程序处理,因此它可以优化查询以获得更好的性能和效率。这种优化可能包括将查询转换为更高效的 SQL 语句、应用索引或其他特定于数据库的优化。这使您可以高效地查询数据存储,而无需手动优化每个查询。

扩展 IQueryable

我正在为一个博客项目编写一个存储库,我想在方法中添加排序、分页和按标题筛选的功能GetLatestPosts。虽然可以将它们塞进同一个方法中,但如果能将这些方法集中到一起,然后链式调用它们就能组成完美的查询,那就更好了。这就是扩展方法!

⚠️ 扩展方法并不是 IQueryable 独有的,它们可以用于扩展任何类型。

在向您展示扩展方法代码之前,我想向您展示重构的代码,以及改进版本的实际样子:


public async Task<PaginatedReadOnlyCollection<Post>>
    GetLatestPostsAsync(int pageNumber, int pageSize, string? title, PostSortOption sortOption, CancellationToken cancellationToken)
{
    try
    {
        var query = db.Posts.AsQueryable();

        var filteredQuery = query.ApplyFilter(title);
        var sortedQuery = filteredQuery.ApplySorting(sortOption);

        var totalCount = await filteredQuery.CountAsync(cancellationToken);
        var posts = await sortedQuery
            .ApplyPagination(pageNumber, pageSize)
            .Execute(cancellationToken);

        var paginatedPosts = new PaginatedReadOnlyCollection<Post>(
            totalCount,
            pageNumber,
            pageSize,
            posts.AsReadOnly()
        );

        return paginatedPosts;
    }
    catch (OperationCanceledException)
    {
        logger.LogInformation("Loading posts was cancelled");
        return PaginatedReadOnlyCollection<Post>.Empty(pageNumber, pageSize);
    }
}

Enter fullscreen mode Exit fullscreen mode

我知道有很多代码,但这里主要关注的是 4 种非 LINQ 方法
ApplyFilter(string),,,ApplySorting(PostSortOption)ApplyPagination(int, int)Execute(CancellationToken)

所有这些方法都是我编写的扩展方法,用于扩展IQueryable<Post>,这使得在数据模型上编写和组成大型查询变得更加清晰和简洁Post

PostsQueryExtensions.cs下面是承载我们刚刚讨论过的扩展方法的代码:


public static class PostsQueryExtensions
{
    public static IQueryable<Post> ApplyFilter(this IQueryable<Post> query, string? title)
    {
        if (!string.IsNullOrEmpty(title))
        {
            query = query.Where(post => post.Title.Contains(title, StringComparison.OrdinalIgnoreCase));
        }

        return query;
    }

    public static IOrderedQueryable<Post> ApplySorting(this IQueryable<Post> query, PostSortOption sortOption)
    {
        return sortOption switch
        {
            PostSortOption.MostComments => query.OrderByDescending(p => p.Comments.Count),
            PostSortOption.MostLiked => query.OrderByDescending(p => p.LikeCount),
            PostSortOption.MostViews => query.OrderByDescending(p => p.Views),
            _ => query.OrderByDescending(p => p.PublishedOn)
        };
    }

    public static IQueryable<Post> ApplyPagination(this IQueryable<Post> query, int pageNumber, int pageSize)
    {
        return query
            .Skip((pageNumber - 1) * pageSize)
            .Take(pageSize);
    }

    public static async Task<List<Post>> Execute(this IQueryable<Post> query, CancellationToken cancellationToken)
        => await query.ToListAsync(cancellationToken);
}

Enter fullscreen mode Exit fullscreen mode

如您所见,所有这些方法都扩展了查询并返回了它的更新版本,本质上,在Execute方法最终触发之前组成了整个查询,然后调用ToListAsync它,获取整个查询的结果并枚举它们,以便调用代码可以读取和显示结果。

结论

在本文中,我们IQuyerable从基础开始讲解了该接口,重点介绍了其主要功能,并展示了其使用示例代码,最后还介绍了如何针对特定数据模型扩展该接口,从而简化并减少复杂查询代码的冗余,使其更加流畅简洁。
希望本文能够很好地介绍这个强大的接口,希望它对您有所帮助,并让您有所收获!

如果您对帖子中提供的代码有任何反馈,请随时指出!

感谢阅读!

鏂囩珷鏉ユ簮锛�https://dev.to/rasheedmozaffar/understanding-iqueryable-in-c-4n37
PREV
日志记录最佳实践
NEXT
Data visualization: Creating charts using REST API's in React.js Modifying the application Takeaway Conclusion References