使用AutoMapper ProjectTo投影EF Core实体触发SQL行大小超限异常
问题描述
我使用AutoMapper的ProjectTo扩展方法,基于DTO类的IQueryable查询EF Core 6实体。实体是较大的对象图,多数投影场景运行正常,但查询Foo实体时,当过滤数据满足特定条件,EF Core会收到SQL Server的错误提示:
Microsoft.Data.SqlClient.SqlException (0x80131904): Cannot create a row of size 8310 which is greater than the allowable maximum row size of 8060.
数据库通过Code First从EF Core 3.1迁移创建,已执行ALTER TABLE [dbo].[Foos] REBUILD重建所有表,但问题依旧。直接查询实体(不通过DTO投影)时无此异常。
相关代码结构
public class Foo { public List<Bar> Bars { get; set; } public List<Baz> Bazs { get; set; } } public class Bar { public string Description { get; set; } //... 其他属性和集合 } public class Baz { public int Quantity { get; set; } //... 其他属性和集合 } // 投影查询代码 IQueryable<FooDTO> foosQuery = dbContext.Foos .AsNoTracking() .ProjectTo<FooDTO>(mapperConfiguration);
不同投影配置的测试结果
- 包含Foo、Bar、Baz的完整投影配置会触发异常:
var mapperConfiguration = new MapperConfiguration(cfg => { cfg.CreateProjection<Foo, FooDTO>(); cfg.CreateProjection<Bar, BarDTO>(); cfg.CreateProjection<Baz, BazDTO>(); });
- 单独忽略Bar的所有属性,仍会触发异常:
var mapperConfiguration = new MapperConfiguration(cfg => { cfg.CreateProjection<Foo, FooDTO>(); cfg.CreateProjection<Bar, BarDTO>() // 逐个忽略所有属性,示例如下: .ForMember(x => x.Description, opt => opt.Ignore()); cfg.CreateProjection<Baz, BazDTO>(); });
- 忽略Foo的Bars集合,不会触发异常:
var mapperConfiguration = new MapperConfiguration(cfg => { cfg.CreateProjection<Foo, FooDTO>() .ForMember(x => x.Bars, opt => opt.Ignore()); cfg.CreateProjection<Bar, BarDTO>(); cfg.CreateProjection<Baz, BazDTO>(); });
- 直接用Include关联集合查询实体,不会触发异常:
IQueryable<Foo> foosQuery = dbContext.Foos .Include(x => x.Bars).Include(x => x.Bazs).AsNoTracking();
问题根源与解决方法
核心原因
AutoMapper的ProjectTo处理集合导航属性时,会生成JOIN关联表的SQL语句,把关联表的字段拼接到主查询的行中。即使你忽略了Bar的所有属性,只要没有明确忽略Foo的Bars集合,AutoMapper仍会保留JOIN逻辑——SQL Server会将关联表的所有列(哪怕未被SELECT)计入行总大小,当Bar表包含大尺寸字段(如长字符串、大二进制数据)时,就会触发行大小超限错误。
而EF Core的Include默认采用拆分查询(EF Core 3.1+默认开启),会将关联集合的查询拆分为多个独立SQL,不会把关联表字段合并到主行中,因此不会触发该错误。
解决方案
- 明确忽略Foo的Bars集合:采用测试中的第3种配置,直接在Foo到FooDTO的投影中忽略Bars导航属性,避免生成JOIN Bar的SQL;
- 仅投影必要字段:若需要映射Bars集合,确保在Bar的投影配置中只选择实际需要的字段,避免引入大尺寸字段;
- 启用EF Core拆分查询:在查询中添加
AsSplitQuery(),强制EF Core将关联集合的查询拆分为多个SQL,避免行大小超限:IQueryable<FooDTO> foosQuery = dbContext.Foos .AsSplitQuery() .AsNoTracking() .ProjectTo<FooDTO>(mapperConfiguration);
内容的提问来源于stack exchange,提问作者Superman.Lopez

