EF Core:如何避免Include导致返回数据库行数激增?
针对你遇到的Include导致数据库返回大量重复行、同时需要保留IQueryable<T>支持分页筛选的问题,有两种实用方案:
1. 投影到DTO(推荐)
直接通过Select构造只包含网格所需字段的DTO(数据传输对象),EF Core会生成高效的SQL,避免笛卡尔积导致的重复行,同时保持IQueryable特性,让分页、筛选逻辑推送到数据库执行。
示例代码:
首先定义适配网格的DTO:
public class TransactionGridDto { public int Id { get; set; } public decimal Amount { get; set; } public string SourceName { get; set; } // 交易来源名称 public string TerminalId { get; set; } // 支付终端ID public string LessorName { get; set; } // 终端出租方名称 }
然后构造查询:
// 替换原有的Include写法,直接投影到DTO var query = dbContext.Transactions .Select(t => new TransactionGridDto { Id = t.Id, Amount = t.Amount, SourceName = t.Source.Name, TerminalId = t.Terminal.TerminalId, LessorName = t.Terminal.Lessor.Name });
将这个IQueryable<TransactionGridDto>绑定到Telerik Grid,网格的分页、筛选、排序会自动转换为对应的SQL查询,数据库仅返回分页所需的30行数据,且每行只包含你指定的字段,没有冗余重复。
如果关联的是集合型数据(比如一个交易有多条明细),同样可以在投影中处理:
public class TransactionGridDto { // 基础字段... public List<TransactionDetailDto> Details { get; set; } } public class TransactionDetailDto { public int LineId { get; set; } public int Quantity { get; set; } } var query = dbContext.Transactions .Select(t => new TransactionGridDto { Id = t.Id, Amount = t.Amount, // 投影集合数据 Details = t.Details.Select(d => new TransactionDetailDto { LineId = d.Id, Quantity = d.Quantity }).ToList() });
EF Core 3.0+支持这种集合投影,会通过子查询获取集合数据,不会重复主表的交易行。
2. 使用AsSplitQuery拆分查询
如果不想定义DTO,希望直接使用实体类,可以用EF Core的AsSplitQuery()方法,将多Include的查询拆分为多个独立的SQL查询,避免笛卡尔积导致的重复行。
示例代码:
var query = dbContext.Transactions .Include(t => t.Source) .Include(t => t.Terminal) .Include(t => t.Terminal.Lessor) .AsSplitQuery();
执行后,SQL Server会生成多个查询:
- 第一个查询获取分页的30条交易数据
- 后续查询分别获取这30条交易对应的来源、终端、出租方数据
总返回行数会远少于笛卡尔积的结果(比如30+30+30+30=120行,而非笛卡尔积的30*N行),EF Core会自动将这些查询的结果合并为实体对象,同时保持IQueryable的分页、筛选能力。
网格绑定注意事项
无论是用DTO还是实体类,只要绑定的是IQueryable<T>,Telerik Grid的分页、筛选、排序都会自动推送到数据库执行,无需手动实现逻辑。只需在网格列配置中指定对应的字段即可,比如:
<TelerikGrid Data="@GridQuery" Pageable="true" PageSize="30" Filterable="true" Sortable="true"> <GridColumns> <GridColumn Field="@nameof(TransactionGridDto.Id)" Title="交易ID" /> <GridColumn Field="@nameof(TransactionGridDto.Amount)" Title="金额" /> <GridColumn Field="@nameof(TransactionGridDto.SourceName)" Title="交易来源" /> </GridColumns> </TelerikGrid>
内容的提问来源于stack exchange,提问作者Avrohom Yisroel

