IEnumerable 和 IQueryable 的区别及其内存使用

作者 : 慕源网 本文共5934个字,预计阅读时间需要15分钟 发布时间: 2023-03-1 共315人阅读

IEnumerable 和 IQueryable 是 .NET 中用于处理数据集合的两个最常用的接口。虽然这两个接口都提供了一种访问数据集合的方法,但它们在实现和使用以及内存使用方面有所不同。

IEnumerable 是一个接口,它是 System.Collections 命名空间的一部分。它用于表示可以枚举的元素集合。可枚举集合只是一个可以在 foreach 语句中循环的集合。可枚举集合的一个示例是列表、数组或字典。

另一方面,IQueryable 是一个更专业的接口,它是 System.Linq 命名空间的一部分。它用于表示可查询的数据集合。可查询集合是可以使用 LINQ(语言集成查询)进行查询以过滤、排序和投影数据的集合。可查询集合的一个示例是实体框架 DbSet 或 LINQ to SQL 表。

IEnumerable 和 IQueryable 之间的主要区别在于它们处理数据检索的方式。

使用 IEnumerable 时,将在内存中检索和处理数据。这意味着如果您收集了大量数据,则需要将所有数据加载到内存中,这可能会导致性能问题。 

另一方面,IQueryable 允许您对数据执行操作,而不必将所有数据加载到内存中。相反,IQueryable 生成表示查询的表达式树,并且仅在执行查询时从源中检索数据。这意味着 IQueryable 在处理大量数据时效率更高。 

下面是实体框架如何使用表达式树来优化 LINQ 查询的示例。假设我们有一个带有 Blog 实体和 Posts 实体的简单 DbContext,其中每个博客可以有很多帖子,

public class Blog
{
    public int BlogId { get; set; }
    public string Name { get; set; }
    public ICollection<Post> Posts { get; set; }
}

public class Post
{
    public int PostId { get; set; }
    public string Title { get; set; }
    public string Content { get; set; }
    public int BlogId { get; set; }
    public Blog Blog { get; set; }
}

public class BloggingContext : DbContext
{
    public DbSet<Blog> Blogs { get; set; }
    public DbSet<Post> Posts { get; set; }
}

现在假设我们要检索特定博客的所有博客文章,按创建日期排序,

int blogId = 1;
using(var context = new BloggingContext()) {
    var query = context.Posts.Where(p => p.BlogId == blogId).OrderByDescending(p => p.DateCreated).ToList();
}

执行此查询时,实体框架将生成一个表示 LINQ 查询的表达式树,如下所示:

var queryExpression = Expression.Call(
    typeof(Queryable),
    "Where",
    new Type[] { typeof(Post) },
    Expression.Constant(context.Posts),
    Expression.Lambda<Func<Post, bool>>(predicate, parameterExpression));

queryExpression = Expression.Call(
    typeof(Queryable),
    "OrderByDescending",
    new Type[] { typeof(Post), typeof(DateTime) },
    queryExpression,
    Expression.Lambda<Func<Post, DateTime>>(orderExpression, parameterExpression));

var query = context.Posts.Provider.CreateQuery<Post>(queryExpression).ToList();

queryExpression 是表示整个查询的表达式树,包括 Where 和 OrderByDescending 子句。然后使用此表达式树生成将针对数据库执行的 SQL。

向您展示另一个类似的示例,其中表达式树用于生成将针对数据库执行的 SQL。Entity Framework 生成的表达式树如何用于生成 SQL 并对数据库执行查询的示例:

int blogId = 1;
using (var context = new BloggingContext())
{
    var query = context.Posts
        .Where(p => p.BlogId == blogId)
        .OrderByDescending(p => p.DateCreated)
        .ToList();
}

Entity Framework 为这个查询生成的表达式树是:

var queryExpression = Expression.Call(
    typeof(Queryable),
    "Where",
    new Type[] { typeof(Post) },
    Expression.Constant(context.Posts),
    Expression.Lambda<Func<Post, bool>>(predicate, parameterExpression));

queryExpression = Expression.Call(
    typeof(Queryable),
    "OrderByDescending",
    new Type[] { typeof(Post), typeof(DateTime) },
    queryExpression,
    Expression.Lambda<Func<Post, DateTime>>(orderExpression, parameterExpression));
var query = context.Posts.Provider.CreateQuery<Post>(queryExpression).ToList();

此表达式树表示整个查询,包括 Where 和 OrderByDescending 子句。实体框架使用此表达式树生成将针对数据库执行的 SQL。

以下是 Entity Framework 可能为此查询生成的 SQL 示例:

SELECT * FROM Posts
WHERE BlogId = 1
ORDER BY DateCreated DESC

此 SQL 查询由 Entity Framework 通过分析表达式树并将其转换为 SQL 生成。然后对数据库执行此 SQL 查询,并将结果作为 Post 对象列表返回到应用程序。

Entity Framework 使用表达式树来分析和优化 LINQ 查询,然后根据将针对数据库执行的表达式树生成 SQL。这允许在处理大型数据集时进行更高效的查询和更好的性能。实体框架使用表达式树来分析查询并优化生成的 SQL。例如,它可能会生成一个结合了 Where 和 OrderByDescending 子句的 SQL 语句,而不是执行两个单独的 SQL 语句。这可以减少执行查询所需的数据库往返次数并提高性能。虽然这只是一个简单的示例,但 Entity Framework 可以使用表达式树来优化更复杂的查询,使其成为查询数据库的强大工具。

使用 IQueryable 还可以提高性能,因为数据检索和处理被推迟到需要实际结果时,这可以减少需要从数据源检索的数据量。IEnumerable 和 IQueryable 之间的另一个区别是它们处理数据操作的方式. 使用 IEnumerable 时,数据操作是在内存中执行的。您对数据所做的任何更改都将在内存中执行,不会反映在数据源中。例如,如果你有一个整数列表,想过滤掉所有的偶数,你可以使用 Where 方法来过滤数据。Where 方法将创建一个新的内存中集合,其中仅包含与指定条件匹配的元素。这个新集合将是一个独立于原始列表的对象,所做的任何更改都不会反映在原始数据源中。

如果要对数据进行排序,可以使用OrderBy方法对内存中的数据进行排序。OrderBy 方法将创建一个新的内存中集合,其中包含已排序的元素。同样,对此集合所做的任何更改都不会反映在原始数据源中。使用 IEnumerable 时,数据操作是在内存中执行的。对数据所做的任何更改都是在数据的内存表示中完成的,而不是反映在底层数据源中。另一方面,IQueryable 允许您在数据源中执行数据操作,这意味着您对数据所做的任何更改都将反映在数据源中。

IQueryable 如何处理数据操作

使用 IQueryable 时,数据操作是在基础数据源中而不是在内存中执行的。这意味着您对数据所做的任何更改都会反映在基础数据源中,而不仅仅是数据在内存中的表示形式。例如,如果您有实体框架 DbSet 或 LINQ to SQL 表,则可以使用 LINQ 对底层数据库中的数据进行筛选、排序和投影。

让我在这里分享一个示例代码以便更好地理解,一个如何使用 IQueryable 在博客和帖子示例上执行数据操作的示

using (var context = new BloggingContext())
{
    // Get a queryable list of all posts
    IQueryable<Post> queryablePosts = context.Posts;

    // Sort the list by date created
    IQueryable<Post> sortedPosts = queryablePosts.OrderBy(post => post.DateCreated);

    // Filter the list to only include posts with the word "Entity Framework" in the title
    IQueryable<Post> filteredPosts = sortedPosts.Where(post => post.Title.Contains("Entity Framework"));

    // Take the first 10 posts from the filtered list
    IQueryable<Post> pagedPosts = filteredPosts.Take(10);

    // Materialize the results into a list
    List<Post> result = pagedPosts.ToList();
}

在此示例中,我们从数据库中获取所有 Post 对象的可查询列表。然后,我们使用 OrderBy 方法按 DateCreated 属性对列表进行排序,这会生成一个表示排序操作的表达式树。然后,我们使用 Where 方法过滤排序后的列表,使其仅包含标题中带有“Entity Framework”一词的帖子,从而生成另一个表示过滤操作的表达式树。最后,我们使用 Take 方法从筛选列表中取出前 10 个帖子,生成另一个表示分页操作的表达式树。然后,我们使用 ToList 方法将结果具体化为一个列表,该方法针对数据库执行整个表达式树并返回结果。这使我们可以对数据库中的 Post 对象执行数据操作,而不必先将所有对象加载到内存中,从而获得更好的性能和更有效的资源利用。如果您对实体框架为我之前提供的上述示例生成的表达式树感兴趣:

var queryablePosts = context.Posts;
var sortedPosts = queryablePosts.OrderBy(post => post.DateCreated);
var filteredPosts = sortedPosts.Where(post => post.Title.Contains("Entity Framework"));
var pagedPosts = filteredPosts.Take(10);

var expression = pagedPosts.Expression;

Entity Framework最终生成的表达式树为:

.Call(System.Linq.Queryable.Take(
    .Call(System.Linq.Queryable.Where(
        .Call(System.Linq.Queryable.OrderBy(
            .Call(System.Linq.Queryable.Select(
                .Constant(value: System.Data.Entity.Internal.Linq.InternalSet`1[ConsoleApp1.Post]),
                '($it) => $it'
            )),
            '($it) => $it.DateCreated'
        )),
        '($it) => $it.Title.Contains("Entity Framework")'
    )),
    10)
)

此表达式树表示整个查询,包括 OrderBy、Where 和 Take 子句。实体框架使用此表达式树生成将针对数据库执行的 SQL。以下是 Entity Framework 可能为此查询生成的 SQL 示例:

SELECT TOP 10
    [PostTable].[Id] AS [Id],
    [PostTable].[Title] AS [Title],
    [PostTable].[Content] AS [Content],
    [PostTable].[DateCreated] AS [DateCreated],
    [PostTable].[BlogId] AS [BlogId]
FROM
    (SELECT
        [PostTable].[Id] AS [Id],
        [PostTable].[Title] AS [Title],
        [PostTable].[Content] AS [Content],
        [PostTable].[DateCreated] AS [DateCreated],
        [PostTable].[BlogId] AS [BlogId]
     FROM
        [dbo].[Posts] AS [PostTable]
     WHERE
        [PostTable].[Title] LIKE N'%Entity Framework%'
     ORDER BY
        [PostTable].[DateCreated] DESC
    ) AS [PostTable]

此 SQL 查询由 Entity Framework 通过分析表达式树并将其转换为 SQL 生成。然后对数据库执行此 SQL 查询,并将结果作为 Post 对象列表返回到应用程序。 

结论 

IEnumerable 和 IQueryable 都是处理数据集合的有用接口,但它们在实现、用法和内存使用方面有所不同。IEnumerable 最适用于简单的数据枚举,而 IQueryable 最适用于查询大型数据集合。处理大量数据时,建议使用 IQueryable 以利用其对数据执行操作的能力,而无需将所有数据加载到内存中。


慕源网 » IEnumerable 和 IQueryable 的区别及其内存使用

常见问题FAQ

程序仅供学习研究,请勿用于非法用途,不得违反国家法律,否则后果自负,一切法律责任与本站无关。
请仔细阅读以上条款再购买,拍下即代表同意条款并遵守约定,谢谢大家支持理解!

发表评论

开通VIP 享更多特权,建议使用QQ登录