You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在LINQ一对多关系中高效获取关联集合的计数

问题描述

我有一个包含UsersMarked集合的Book实体:

public class Book
{
    [Key]
    public int Id { get; set; }

    [Required]
    public string Title { get; set; } = string.Empty;

    public int Date { get; set; }

    public int AuthorId { get; set; }

    public Author? Author { get; set; }

    public string Cover { get; set; } = string.Empty;

    public string? Description { get; set; }

    public List<User> UsersMarked { get; set; } = new();

    public BookGenre Genre { get; set; }
}

需要将其转换为包含ReadersCount(标记书籍的用户数量)的BookPageDto:

public record BookPageDto(
    string Title,
    int Date,
    int ReadersCount,
    string Cover,
    BookGenre Genre,
    string Description);

目前我通过Include加载整个UsersMarked集合来获取计数:

public async Task<Book?> GetBookByTitleAsync(string title)
{
        return await _context.Books
            .Include(b=>b.UsersMarked)
            .FirstOrDefaultAsync(b => b.Title.ToLower() == title);
}

这种方式会把整个集合加载到内存中,内存效率很低。而且无法直接用_context.Books.Include(b=>b.UsersMarked.Count())(会报错),想知道除了给Book实体添加Count字段外,还有哪些方法可以获取UsersMarked的计数。

解决方案

1. 投影查询直接映射到DTO(推荐)

直接在查询时通过Count()统计关联用户数,同时投影到目标DTO,EF Core会生成高效的SQL,在数据库层面完成计数,不会加载整个UsersMarked集合:

public async Task<BookPageDto?> GetBookPageDtoByTitleAsync(string title)
{
    return await _context.Books
        .Where(b => b.Title.ToLower() == title)
        .Select(b => new BookPageDto(
            b.Title,
            b.Date,
            b.UsersMarked.Count(),
            b.Cover,
            b.Genre,
            b.Description ?? string.Empty))
        .FirstOrDefaultAsync();
}

这种方式一步完成查询和DTO映射,内存占用最少,性能最优。

2. 利用EF Core的计数导航属性(EF Core 5+)

可以通过Fluent API配置一个数据库计算列,让数据库自动维护计数:
首先在Book实体中添加属性:

public int ReadersCount { get; set; }

然后在DbContext的OnModelCreating方法中配置计算列:

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder.Entity<Book>()
        .Property(b => b.ReadersCount)
        .HasComputedColumnSql("(SELECT COUNT(*) FROM [UsersMarked] WHERE [UsersMarked].[BookId] = [Book].[Id])");
}

此后查询Book实体时,ReadersCount会直接从数据库获取统计值,不需要手动计数,也不会加载关联集合。不过这种方式依赖数据库支持计算列,适合频繁需要统计的场景。

3. 单独查询计数

如果已经获取了Book实体但未加载UsersMarked,可以单独发起计数查询:

public async Task<int> GetUsersMarkedCountAsync(int bookId)
{
    // 假设是多对多关系,直接查询中间表效率更高
    return await _context.Set<BookUser>() // 替换为你的多对多中间表实体
        .CountAsync(bu => bu.BookId == bookId);
    
    // 如果没有显式中间表,也可以通过User侧查询
    // return await _context.Users.CountAsync(u => u.MarkedBooks.Any(b => b.Id == bookId));
}

这种方式会多一次数据库请求,适合零散需要计数的场景,不如投影方式高效。

内容的提问来源于stack exchange,提问作者JackFord

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.25 19:53:09