简体   繁体   English

EF Core - 使用 Automapper 从 OData 返回映射的多对多关系

[英]EF Core - Return mapped Many-to-Many relationship from OData using Automapper

Info 信息

The app type is a Hosted Blazor web-assembly.应用类型是 Hosted Blazor web-assembly。 And below are the versions of the nuget packages I am using.以下是我正在使用的 nuget 软件包的版本。 There is an error that occurs when trying to expand a navigation property that is a many-to-many relationship.尝试扩展多对多关系的导航属性时发生错误。 The classes are mapped to DTO classes that flattens the middle relationship class.这些类映射到 DTO 类,使中间关系变平 class。

  • .Net core Version="3.1" .Net核心版本=“3.1”
  • AutoMapper Version="10.0.0" AutoMapper 版本="10.0.0"
  • AutoMapper.AspNetCore.OData.EFCore Version="2.0.1" AutoMapper.AspNetCore.OData.EFCore 版本="2.0.1"
  • AutoMapper.Extensions.ExpressionMapping Version="4.0.1" AutoMapper.Extensions.ExpressionMapping Version="4.0.1"
  • AutoMapper.Extensions.Microsoft.DependencyInjection Version="8.0.1" AutoMapper.Extensions.Microsoft.DependencyInjection Version="8.0.1"
  • Microsoft.AspNetCore.Components.WebAssembly.Server Version="3.2.1" Microsoft.AspNetCore.Components.WebAssembly.Server Version="3.2.1"
  • Microsoft.AspNetCore.OData Version="7.5.0" Microsoft.AspNetCore.OData 版本="7.5.0"

To run this repo, you will need the free version of SQL Server or better要运行此 repo,您将需要 SQL Server 或更好的免费版本

Set the EfCoreAutomapperOdata.Server project as the startup project and navigate to the Courses page (https://localhost:5001/courses) and click on either course.将 EfCoreAutomapperOdata.Server 项目设置为启动项目并导航到课程页面 (https://localhost:5001/courses) 并单击任一课程。 This will throw the following error:这将引发以下错误:

System.InvalidOperationException: No generic method 'Include' on type 'Microsoft.EntityFrameworkCore.EntityFrameworkQueryableExtensions' is compatible with the supplied type arguments and arguments. No type arguments should be provided if the method is non-generic. at System.Linq.Expressions.Expression.FindMethod(Type type, String methodName, Type[] typeArgs, Expression[] args, BindingFlags flags)...

Models 楷模

See here - Entity Models and here - Dto Models for class definition请参阅此处 - 实体模型此处 - class 定义的 Dto 模型

Automapper Config 自动映射器配置
 public class AutomapperConfig: Profile { public AutomapperConfig() { CreateMap<Instructor, InstructorDto>(); CreateMap<InstructorDto, Instructor>(); CreateMap<Course, CourseDto>().ForMember(dto => dto.Students, opt => { opt.MapFrom(_ => _.Students.Select(y => y.Student)); }); CreateMap<CourseDto, Course>().ForMember(ent => ent.Students, ex => ex.MapFrom(x => x.Students.Select(y => new CourseStudent { CourseId = x.Id, StudentId = y.Id }))); CreateMap<Student, StudentDto>().ForMember(dto => dto.Courses, opt => { opt.MapFrom(x => x.Courses.Select(y => y.Course)); }).ForMember(dto => dto.Friends, opt => { opt.MapFrom(x => x.Friends.Select(y => y.Friend)); }); CreateMap<StudentDto, Student>().ForMember(ent => ent.Courses, ex => ex.MapFrom(x => x.Courses.Select(y => new CourseStudent { StudentId = x.Id, CourseId = y.Id }))); } }
Startup 启动
 public class Startup { public Startup(IConfiguration configuration) { Configuration = configuration; } public IConfiguration Configuration { get; } public void ConfigureServices(IServiceCollection services) { // ------ Some code removed for brevity ------ services.AddOData(); services.AddAutoMapper(cfg => { cfg.AddExpressionMapping(); },typeof(AutomapperConfig)); // ------ Some code removed for brevity ------ } public void Configure(IApplicationBuilder app, IWebHostEnvironment env) { // ------ Some code removed for brevity ------ app.UseHttpsRedirection(); app.UseBlazorFrameworkFiles(); app.UseStaticFiles(); app.UseRouting(); app.UseEndpoints(endpoints => { endpoints.MapRazorPages(); endpoints.MapControllers(); endpoints.EnableDependencyInjection(); endpoints.Select().Filter().OrderBy().Count().Expand().MaxTop(1000); endpoints.MapODataRoute("odata", "odata", GetEdmModel()); endpoints.MapFallbackToFile("index.html"); }); } private IEdmModel GetEdmModel() { var builder = new ODataConventionModelBuilder(); builder.EntitySet<CourseDto>("Courses"); builder.EntitySet<InstructorDto>("Instructors"); builder.EntitySet<StudentDto>("Students"); return builder.GetEdmModel(); } }
Course Controller 课程 Controller
 public class CourseController: ODataController { protected readonly BlazorContext _context; protected readonly IMapper _mapper; public CourseController(BlazorContext context, IMapper mapper) { _context = context; _mapper = mapper; } [HttpGet] [ODataRoute("Courses")] public async Task<IActionResult> Get(ODataQueryOptions<CourseDto> options) { return Ok(await _context.Course.GetAsync(_mapper, options)); } [HttpGet] [ODataRoute("Courses({id})")] public async Task<IActionResult> Get([FromODataUri] int id, ODataQueryOptions<CourseDto> options) { return Ok((await _context.Course.GetAsync(_mapper, options)).Where(s => s.Id == id).ToList()); } }
Sample odata api query that fails 示例 odata api 查询失败

/odata/Courses?$expand=Students

Repro 复现

I have built demo Blazor WASM app for this issue to reproduce我已经为这个问题构建了演示 Blazor WASM 应用程序来重现

Repository资料库

General Setup一般设置

To make expansions work, you need to allow for the $expand query option to be used.要使扩展工作,您需要允许使用$expand 查询选项 Configure it explicitly like so:像这样显式配置它:

private IEdmModel GetEdmModel()
{
    var builder = new ODataConventionModelBuilder();

    builder.EntitySet<EstimateDto>(nameof(MyContext.Estimates))
        .EntityType
        .Expand(); // <-- allow expansion

    builder.EntitySet<TypeDto>(nameof(MyContext.Types))
        .EntityType
        .Expand(); // <-- allow expansion

    builder.EntitySet<SubTypeDto>(nameof(MyContext.SubTypes));
    
    return builder.GetEdmModel();
}

You will also need to update your AutoMapper map, to allow the queries to be successfully mapped to a DTOs:您还需要更新您的 AutoMapper map,以允许查询成功映射到 DTO:

public class AutoMapperConfig : Profile
{
    public AutoMapperConfig()
    {
        CreateMap<Estimate, EstimateDto>()
            .ForMember(
                dto => dto.Types,
                opt => opt.MapFrom(x => x.EstimateTypes.Select(y => y.Type)));

        // The following mapping is needed for expansion to work:
        CreateMap<EstimateTypeRel, TypeDto>()
            .ForMember(
                dto => dto.SubTypes,
                opt => opt.MapFrom(x => x.Type));

        CreateMap<Type, TypeDto>()
            .ForMember(
                dto => dto.SubTypes,
                opt => opt.MapFrom(x => x.SubTypes.Select(y => y.SubType)));

        CreateMap<SubTypeRel, SubTypeDto>();
    }
}

With that configuration being setup, there are at least two possible solutions to this issue, depending on your requirements:设置该配置后,根据您的要求,至少有两种可能的解决方案可以解决此问题:

A) Only expand Types A)只展开Types

If you only want to expand Types , you will need to change your AutoMapper mappings by adding a .Where(z => z != null) clause, because as the exception tells you, null values are not allowed in collections, but OData includes them for the non-expanded SubType entities:如果您只想扩展Types ,则需要通过添加.Where(z => z != null)子句来更改 AutoMapper 映射,因为异常告诉您, null中不允许使用 null 值,但 OData 包括它们用于非扩展的SubType实体:

public class AutoMapperConfig : Profile
{
    public AutoMapperConfig()
    {
        CreateMap<Estimate, EstimateDto>()
            .ForMember(
                dto => dto.Types,
                opt => opt.MapFrom(
                    x => x.EstimateTypes.Select(y => y.Type)
                        .Where(z => z != null))); // <-- filter out null values

        CreateMap<EstimateTypeRel, TypeDto>()
            .ForMember(
                dto => dto.SubTypes,
                opt => opt.MapFrom(x => x.Type));

        CreateMap<Type, TypeDto>()
            .ForMember(
                dto => dto.SubTypes,
                opt => opt.MapFrom(
                    x => x.SubTypes.Select(y => y.SubType)
                        .Where(z => z != null))); // <-- filter out null values

        CreateMap<SubTypeRel, SubTypeDto>();
    }
}

Then you can use the following query:然后您可以使用以下查询:

https://localhost:5001/odata/Estimates(1)?$expand=Types

B) Also expand SubTypes B) 同时扩展SubTypes

The alternative is to expand the SubTypes property as well, so the collection can be properly filled.另一种方法是也扩展SubTypes属性,以便可以正确填充集合。 To expand DTO mapped properties over multiple levels, use the $expand query option in your query string like so:要在多个级别上扩展 DTO 映射属性,请在查询字符串中使用$expand查询选项,如下所示:

https://localhost:5001/odata/Estimates(1)?$expand=Types($expand=SubTypes)

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM