EF Core 2.1中通过Include/ThenInclude查询SettingInformation完整关联数据
我来帮你搞定这个复杂模型的关联数据加载问题——EF Core 2.1里要获取嵌套的关联实体,核心是用好显式加载的能力,下面是几个针对性的解决方案:
1. 用Include + ThenInclude一次性加载所有关联
这是EF Core加载关联数据的标准方式,完美适配你的场景:Include用来加载直接关联的集合(比如SettingInformation的Settings属性),ThenInclude用来深入加载集合内实体的关联(比如Setting关联的Client、User、Workspace)。
针对你的模型,代码示例如下:
var targetSettingInfo = _context.SettingInformation // 先加载SettingInformation关联的Settings集合 .Include(si => si.Settings) // 加载每个Setting关联的Client实体 .ThenInclude(s => s.Client) // 再次Include Settings,然后加载关联的User .Include(si => si.Settings) .ThenInclude(s => s.User) // 第三次Include Settings,加载关联的Workspace .Include(si => si.Settings) .ThenInclude(s => s.Workspace) // 如果Client/User/Workspace还有更深的关联,继续用ThenInclude扩展 .FirstOrDefault(si => si.Id == yourTargetId);
注意:EF Core 2.1中,针对同一个集合的不同关联(比如Settings的Client和User),需要单独调用
Include后再用ThenInclude,不能链式多次ThenInclude跨不同的关联实体。
2. 处理循环引用问题
如果你的模型存在双向关联(比如Setting引用Client,Client又引用ICollection<Setting>),直接序列化实体时会出现循环引用错误。可以通过两种方式解决:
方式一:配置Json序列化忽略循环引用
在Startup的服务配置中添加:
services.AddMvc() .AddJsonOptions(options => { options.SerializerSettings.ReferenceLoopHandling = Newtonsoft.Json.ReferenceLoopHandling.Ignore; });
方式二:使用DTO(数据传输对象)
定义只包含业务需要字段的DTO,避免序列化不必要的关联,同时彻底解决循环引用问题。比如:
// 定义DTO类 public class SettingInformationDto { public int Id { get; set; } // 其他SettingInformation需要的字段 public List<SettingDto> Settings { get; set; } } public class SettingDto { public int Id { get; set; } // 其他Setting需要的字段 public ClientDto Client { get; set; } public UserDto User { get; set; } public WorkspaceDto Workspace { get; set; } } // 省略ClientDto、UserDto、WorkspaceDto的定义
然后通过Linq投影查询:
var settingInfoDto = _context.SettingInformation .Where(si => si.Id == yourTargetId) .Select(si => new SettingInformationDto { Id = si.Id, Settings = si.Settings.Select(s => new SettingDto { Id = s.Id, Client = new ClientDto { Id = s.Client.Id, Name = s.Client.Name }, User = new UserDto { Id = s.User.Id, Username = s.User.Username }, Workspace = new WorkspaceDto { Id = s.Workspace.Id, Title = s.Workspace.Title } }).ToList() }) .FirstOrDefault();
这种方式还能减少数据库传输的数据量,提升查询性能,非常适合API返回数据的场景。
3. 显式加载(按需加载关联)
如果因为上下文生命周期问题(比如延迟加载时上下文已释放),无法用Include一次性加载,可以用显式加载:
var settingInfo = _context.SettingInformation.FirstOrDefault(si => si.Id == yourTargetId); if (settingInfo != null) { // 先加载Settings集合 _context.Entry(settingInfo).Collection(si => si.Settings).Load(); // 遍历每个Setting,加载其关联的Client、User、Workspace foreach (var setting in settingInfo.Settings) { _context.Entry(setting).Reference(s => s.Client).Load(); _context.Entry(setting).Reference(s => s.User).Load(); _context.Entry(setting).Reference(s => s.Workspace).Load(); } }
注意:这种方式会产生多次数据库查询(1次查SettingInformation,1次查Settings,N次查每个Setting的关联实体),性能不如
Include,只适合小数据量的按需加载场景。
4. 启用延迟加载(谨慎使用)
如果你之前没启用延迟加载,可以通过以下方式开启:
- 在模型配置中给导航属性添加
virtual关键字(比如public virtual ICollection<Setting> Settings { get; set; }) - 在上下文配置中启用延迟加载代理:
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) { optionsBuilder.UseSqlServer("yourConnectionString") .UseLazyLoadingProxies(); }
不过延迟加载容易导致N+1查询问题,尤其是复杂模型场景,所以更推荐前面的Include或投影DTO的方式。
内容的提问来源于stack exchange,提问作者Sascha Patschka

