.NET Core 3.1 OData中启用$levels的方法及报错排查
我来帮你分析这个问题并给出针对性的解决方案:
报错原因解析
你遇到的错误The $level option on navigation property 'PatientForms' is not allowed, because the related entity type 'mjzsoft.Models.PatientForm' could not be cast to source entity type 'mjzsoft.Models.Patient',核心原因是OData的$levels参数是专门为递归展开与当前实体同类型的导航属性设计的(比如自引用实体:例如Employee有Manager属性,类型也是Employee,可以用$levels递归展开多层上级)。
而你的场景中,PatientForms导航属性指向的是PatientForm类型,和源实体Patient完全不同,OData尝试将PatientForm强制转换为Patient时自然会失败。
解决方案
方法一:使用嵌套$expand(适合当前不同类型层级场景)
如果你的需求就是固定展开Patient -> PatientForms -> Form这两层,继续使用你当前的手动嵌套展开语法是最直接且符合OData标准的方案,完全不会出现类型转换错误:
https://localhost:44375/odata.svc/Patients?$expand=PatientForms($expand=Form)
方法二:启用$levels支持(针对同类型递归导航场景)
如果你确实需要用$levels来处理自引用类型的递归展开(比如你的Patient实体有ParentPatient这类同类型导航属性),需要先在OData模型构建时开启递归展开支持,步骤如下:
在模型构建中启用递归展开
在配置OData服务的代码(通常是Startup.cs或Program.cs)里,构建Edm模型时添加EnableRecursiveExpand()配置:using Microsoft.OData.ModelBuilder; public IEdmModel GetEdmModel() { var builder = new ODataConventionModelBuilder(); // 配置实体集 var patientSet = builder.EntitySet<Patient>("Patients"); var patientFormSet = builder.EntitySet<PatientForm>("PatientForms"); var formSet = builder.EntitySet<Form>("Forms"); // 配置实体主键(必须) patientSet.EntityType.HasKey(p => p.Id); patientFormSet.EntityType.HasKey(pf => pf.Id); formSet.EntityType.HasKey(f => f.Id); // 关键:开启递归展开,允许使用$levels参数 builder.EnableRecursiveExpand(); builder.EnableLowerCamelCase(); // 可选,根据你的URL风格调整 return builder.GetEdmModel(); }正确使用
$levels(仅限同类型导航)
比如如果Patient有自引用的ParentPatient属性,就可以这样调用:https://localhost:44375/odata.svc/Patients?$expand=ParentPatient($levels=2)这会递归展开
Patient -> ParentPatient -> ParentPatient的ParentPatient三层数据。
方法三:自定义扩展支持跨类型层级展开(进阶方案)
如果你的需求是通过$levels动态展开不同类型的多层级导航(而非同类型递归),这需要自定义OData查询扩展,因为默认的$levels不支持这种场景。大致思路是:
- 自定义查询选项处理器,解析
$levels参数并将其转换为对应的嵌套$expand语法 - 在
[EnableQuery]属性中注册自定义扩展 - 请求处理时自动替换
$levels为对应的展开逻辑
不过这种方案复杂度较高,会增加代码维护成本,除非有强烈的动态层级需求,否则不推荐使用。
额外检查点
确保你的EF Core多对多关系配置正确(.NET Core 3.1需要显式配置中间表),在XxxDbContext中添加如下配置:
protected override void OnModelCreating(ModelBuilder modelBuilder) { // 配置Patient与PatientForm的一对多关系 modelBuilder.Entity<Patient>() .HasMany(p => p.PatientForms) .WithOne(pf => pf.Patient) .HasForeignKey(pf => pf.PatientId); // 配置PatientForm与Form的多对一关系 modelBuilder.Entity<PatientForm>() .HasOne(pf => pf.Form) .WithMany(f => f.PatientForms) .HasForeignKey(pf => pf.FormId); }
内容的提问来源于stack exchange,提问作者MJBZA

