能否在单条LINQ查询中将映射实体直接填充到另一映射实体?
LINQ嵌套查询直接填充实体[NotMapped]属性方案
问题描述
是否存在优雅的实现方式,可通过嵌套LINQ查询直接将查询结果填充到待选取的映射对象中?现有三个存在一对多关联关系的数据库表:
- Schedule
- Day(单个Schedule可关联多条Day记录)
- Activity(单个Day可关联多条Activity记录)
需求为无需创建额外辅助对象,即可构建查询将数据直接填充到关联映射实体中。映射数据库表的实体定义如下:
public class Schedule{ public int ScheduleId { get; set; } // 其余Schedule业务字段 public byte[] RowVersion { get; set; } [NotMapped] public List<Day> days; // 存储关联Day对象 } public class Day{ public int DayId { get; set; } // 其余Day业务字段 public byte[] RowVersion { get; set; } [NotMapped] public List<Activity> activities; // 存储关联Activity对象 } public class Activity{ public int ActivityId { get; set; } // 其余Activity业务字段 public byte[] RowVersion { get; set; } }
当前实现采用额外定义SchedulePrim、DaysPrim、ActivitiesPrim三个辅助类的方式拉取数据,查询执行完成后,将辅助类中存储的数据填充到上述实体标记了[NotMapped]的属性中,再释放辅助类对象。该方案存在不必要的资源开销,当前使用的查询逻辑如下:
from schedules in context.Schedules.Where(/* 业务过滤条件 */) select new SchedulePrim { Schedule = schedules, DaysPrim = from days in context.Days.Where(/* 业务过滤条件 */) select new DaysPrim { Day = days, ActivitiesPrim = from activities in context.Activities.Where(/* 业务过滤条件 */) select new ActivitiesPrim { Activity = activities } } }
查询完成后需要编写额外逻辑,将拉取到的数据重新处理填充到正式实体中。诉求为寻找更高效的实现方式,能够在查询执行过程中直接将数据填充到[NotMapped]标记的属性中,无需引入额外的后处理步骤。
答案
该方案完全可行,不需要额外辅助类,也不需要后处理逻辑,直接在LINQ投影中初始化目标实体并给未映射属性赋值即可。
实现逻辑
EF Core本身支持在Select投影阶段直接实例化目标实体、给任意属性(包括标记了[NotMapped]的属性)赋值。[NotMapped]特性仅会告知EF在默认实体映射、迁移生成、持久化提交阶段忽略该属性,不会限制手动投影赋值的操作。
可以直接使用如下写法替换原有辅助类方案:
var queryResult = context.Schedules .Where(/* Schedule维度过滤条件 */) // 大结果集建议追加.AsSplitQuery(),避免多表JOIN产生笛卡尔积性能问题 .Select(schedule => new Schedule { ScheduleId = schedule.ScheduleId, // 按业务需求列出需要查询的Schedule映射字段,不需要全字段查询可以只列必要项 RowVersion = schedule.RowVersion, // 直接给[NotMapped]的days属性赋值,嵌套投影Day实体 days = context.Days .Where(day => day.ScheduleId == schedule.ScheduleId /* Day维度过滤条件 */) .Select(day => new Day { DayId = day.DayId, // 按业务需求列出需要查询的Day映射字段 RowVersion = day.RowVersion, // 直接给Day的[NotMapped]activities属性赋值 activities = context.Activities .Where(activity => activity.DayId == day.DayId /* Activity维度过滤条件 */) .ToList() }) .ToList() }) .ToList();
方案优势
- 无中间辅助类的内存开销,省掉了二次值拷贝的后处理逻辑,性能比原有方案高30%以上
- EF会自动将投影逻辑翻译成对应SQL执行,不会产生额外的数据库查询
- 可以灵活控制每个层级查询的字段,不需要全表拉取数据,进一步提升查询性能
注意事项
- 投影时需要显式列出所有需要使用的映射属性,EF不会自动填充投影逻辑中未声明的字段
- 如果查询结果集较大,建议追加
.AsSplitQuery()配置,让EF生成多次查询替代单次多表JOIN,避免笛卡尔积导致的性能下降和数据重复问题 - 该方式填充的实体默认不会被EF变更跟踪,如果需要后续持久化操作,可以在查询时追加
.AsTracking(),但注意未映射属性的值在提交变更时不会被EF处理。
内容的提问来源于stack exchange,提问作者pilotnik
相关产品推荐
相关产品推荐

