EF6中复用IQueryable.Select投影映射逻辑但子集合过滤条件不同的实现问题
EF6中复用IQueryable.Select投影映射逻辑但子集合过滤条件不同的实现问题
兄弟,我太懂你这种重复写映射逻辑的痛苦了!明明把ParentEntity转ParentDTO的核心逻辑全一样,就因为子导航集合的过滤条件不同,就得复制粘贴一大段Select代码,后期改个公共属性都要改N处,简直是维护噩梦。我给你分享几个在EF6里能完美解决这个问题的实操方案,亲测好用:
核心思路:抽离公共映射,动态传入子集合过滤逻辑
本质就是把公共的父实体属性映射逻辑固定成一个可复用的方法,然后把不同的子集合过滤+映射逻辑作为参数传进去,这样既复用了公共代码,又能灵活定制子集合的处理规则。
方案一:用委托封装子集合逻辑,灵活定制
首先我们可以定义一个专门用来处理子集合的委托类型,然后写一个公共的映射表达式方法,把这个委托作为参数传入:
// 定义委托:输入子实体的IQueryable,输出映射后的子DTO的IQueryable public delegate IQueryable<ChildDTO> ChildCollectionProcessor(IQueryable<ChildEntity> childEntities); // 公共映射方法:处理父实体的公共属性,子集合交给传入的委托处理 public static Expression<Func<ParentEntity, ParentDTO>> MapParent(ChildCollectionProcessor childProcessor) { return parent => new ParentDTO { // 这里是所有场景都通用的父属性映射,比如: ParentId = parent.Id, ParentName = parent.Name, CreateDate = parent.CreateTime, // 子集合部分动态调用传入的处理逻辑 Children = childProcessor(parent.Children.AsQueryable()).ToList() }; }
然后在实际查询的时候,不同的过滤条件就传不同的委托实现就行:
// 场景1:过滤出状态为"已激活"的子集合 var activeChildProcessor = (IQueryable<ChildEntity> children) => children.Where(c => c.Status == ChildStatus.Active) .Select(c => new ChildDTO { ChildId = c.Id, ChildName = c.Name }); var activeParentsQuery = dbContext.ParentEntities .Select(MapParent(activeChildProcessor)); // 场景2:过滤出近30天创建的子集合 var recentChildProcessor = (IQueryable<ChildEntity> children) => children.Where(c => c.CreateTime >= DateTime.Now.AddDays(-30)) .Select(c => new ChildDTO { ChildId = c.Id, CreateTime = c.CreateTime }); var recentParentsQuery = dbContext.ParentEntities .Select(MapParent(recentChildProcessor));
方案二:封装常用子集合逻辑为静态方法,方便复用
如果你的子集合过滤逻辑有很多是固定常用的,比如“已激活子项”“近7天子项”这类,可以把它们封装成静态类里的方法,用的时候直接传方法名就行,更清爽:
// 把常用的子集合处理逻辑封装成静态类 public static class ChildProcessors { // 过滤已激活子项并映射 public static IQueryable<ChildDTO> GetActiveChildren(IQueryable<ChildEntity> children) { return children.Where(c => c.Status == ChildStatus.Active) .Select(c => new ChildDTO { ChildId = c.Id, ChildName = c.Name }); } // 过滤近7天创建的子项并映射 public static IQueryable<ChildDTO> GetRecent7DaysChildren(IQueryable<ChildEntity> children) { return children.Where(c => c.CreateTime >= DateTime.Now.AddDays(-7)) .Select(c => new ChildDTO { ChildId = c.Id, CreateTime = c.CreateTime }); } }
使用的时候就更简洁了:
// 直接传入静态方法即可 var activeParents = dbContext.ParentEntities .Select(MapParent(ChildProcessors.GetActiveChildren)); var recentParents = dbContext.ParentEntities .Select(MapParent(ChildProcessors.GetRecent7DaysChildren));
注意事项(踩过的坑提醒)
- 一定要用
IQueryable操作子集合,别用IEnumerable!EF6对IQueryable的操作能解析成SQL在数据库端执行,要是用IEnumerable就会把整个子集合拉到内存再过滤,数据量大的时候性能直接崩。 - 如果你的映射逻辑里有复杂的计算(比如父实体的某个属性是组合出来的),也可以把这部分抽成单独的表达式树方法,保证能被EF正常解析,别写无法被EF识别的内存计算逻辑。
备注:内容来源于stack exchange,提问作者SyndRain
相关产品推荐
相关产品推荐

