如何在.NET Rest API中按需加载/不加载School的嵌套Student实体?
问题描述
我拥有一个带控制器的.NET Rest API,该控制器负责处理实体School的请求。数据库中存在多个School,每个School关联多个Student。
有时我希望查询返回包含对应Student的所有School,有时仅需返回School列表。
我尝试在查询中添加布尔参数includeStudents,通过Entity Framework的Include方法修改内部SQL查询以关联Students表,实现代码如下:
public class SchoolsController:ApiController { [System.Web.Http.HttpGet] public IEnumerable<SchoolsDTO> Index(int? id = null, bool? includeStudents=null) { using (MyDatabase db = new MyDatabase()) { string includeString = ""; if(includeStudents.HasValue && includeStudents.Value) { includeString = ".Students"; } var schools = db.Schools.Include(includeString).AsQueryable(); if (id.HasValue) { schools = schools.Where(x => x.Id == id); } return AutoMapper.Mapper.Map<List<SchoolsDTO>>(schools.ToList()); } } }
但无论是否使用Include方法,查询始终会解析并序列化所有School的Student数据到JSON中。
补充说明:我使用AutoMapper处理DTO,但这些实体的DTO是EF原始对象的完全副本。
解决方案
问题根源在于EF延迟加载自动触发和AutoMapper默认全量映射,可以通过以下步骤解决:
- 关闭EF延迟加载与代理创建
EF默认开启延迟加载,即便未调用Include,序列化器访问School.Students属性时,EF会自动查询数据库加载关联数据。在数据库上下文构造函数中关闭该功能:
public class MyDatabase : DbContext { public MyDatabase() { this.Configuration.LazyLoadingEnabled = false; this.Configuration.ProxyCreationEnabled = false; // 禁用动态代理,避免序列化时的额外问题 } // DbSet<School> Schools { get; set; } // DbSet<Student> Students { get; set; } }
- 优化查询逻辑,条件添加Include
原代码中Include("")属于无效调用,改为条件判断是否添加关联查询,同时使用强类型Include避免字符串拼写错误:
var schoolsQuery = db.Schools.AsQueryable(); if(includeStudents.HasValue && includeStudents.Value) { schoolsQuery = schoolsQuery.Include(s => s.Students); } if(id.HasValue) { schoolsQuery = schoolsQuery.Where(s => s.Id == id); } var schoolsList = schoolsQuery.ToList();
- 控制AutoMapper的映射规则
由于DTO是EF实体的完全副本,AutoMapper默认会映射所有属性,需要根据参数决定是否映射Students:
- 方式一:映射时动态忽略属性
if(includeStudents.HasValue && includeStudents.Value) { return AutoMapper.Mapper.Map<List<SchoolsDTO>>(schoolsList); } else { return AutoMapper.Mapper.Map<List<SchoolsDTO>>(schoolsList, opt => opt.ForMember(dest => dest.Students, opt => opt.Ignore()) ); }
- 方式二:提前定义两套映射配置
// 初始化映射 AutoMapper.Mapper.Initialize(cfg => { // 不包含Students的映射 cfg.CreateMap<School, SchoolsDTO>(); // 包含Students的映射 cfg.CreateMap<School, SchoolsDTO>() .ForMember(dest => dest.Students, opt => opt.MapFrom(src => src.Students)) .Named("IncludeStudents"); }); // 控制器中调用对应映射 if(includeStudents.HasValue && includeStudents.Value) { return AutoMapper.Mapper.Map<List<SchoolsDTO>>(schoolsList, opt => opt.UseNamedProfile("IncludeStudents")); } else { return AutoMapper.Mapper.Map<List<SchoolsDTO>>(schoolsList); }
内容的提问来源于stack exchange,提问作者E. Williams
相关产品推荐
相关产品推荐

