EF Core-使用Automapper从OData返回映射的多对多关系 [英] EF Core - Return mapped Many-to-Many relationship from OData using Automapper

查看:491
本文介绍了EF Core-使用Automapper从OData返回映射的多对多关系的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

该应用程序类型是Hosted Blazor Web程序集。以下是我正在使用的nuget软件包的版本。尝试展开多对多关系的导航属性时,发生错误。这些类映射到展平中间关系类的DTO类。

The app type is a Hosted Blazor web-assembly. And below are the versions of the nuget packages I am using. 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.


  • .Net core Version = 3.1

  • AutoMapper版本= 10.0.0

  • AutoMapper.AspNetCore.OData.EFCore Version = 2.0.1

  • AutoMapper .Extensions.ExpressionMapping Version = 4.0.1

  • AutoMapper.Extensions.Microsoft.DependencyInjection Version = 8.0.1

  • Microsoft .AspNetCore.Components.WebAssembly.Server Version = 3.2.1

  • Microsoft.AspNetCore.OData Version = 7.5.0&

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

要运行此存储库,您将需要SQL Server的免费版本或更高版本

To run this repo, you will need the free version of SQL Server or better

将EfCoreAutomapperOdata.Server项目设置为启动项目,然后导航到课程页面(https:// localhost:5001 / courses),然后单击任一课程。这将引发以下错误:

Set the EfCoreAutomapperOdata.Server project as the startup project and navigate to the Courses page (https://localhost:5001/courses) and click on either course. This will throw the following error:

System.InvalidOperationException:类型'Microsoft.EntityFrameworkCore.EntityFrameworkQueryableExtensions'上的通用方法'Include'与提供的类型参数兼容和争论。如果方法是非泛型的,则不应该提供任何类型参数。在System.Linq.Expressions.Expression.FindMethod(Type type,String methodName,Type [] typeArgs,Expression [] args,BindingFlags标志)...

请参见此处-实体模型此处-Dto模型用于类定义

    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

    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());
        }
    }


失败的示例odata api查询

/ odata / Courses?$ expand =学生

我已经为这个问题制作了演示Blazor WASM应用程序

I have built demo Blazor WASM app for this issue to reproduce

存储库

推荐答案

常规设置


要使扩展正常工作,您需要允许 $ expand 查询选项。像这样明确配置:

General Setup

To make expansions work, you need to allow for the $expand query option to be used. 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();
}

您还需要更新AutoMapper映射,以使查询成功映射到DTO:

You will also need to update your AutoMapper map, to allow the queries to be successfully mapped to a DTOs:

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:

如果仅要扩展 Types ,您需要通过添加来更改AutoMapper映射。where(z => z!= null)子句,因为异常告诉您,集合中不允许 null 值,但是OData包含未扩展的 SubType的值实体:

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:

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)同时扩展 SubTypes


另一种方法是扩展 SubTypes 属性,因此可以正确填充集合。要将DTO映射的属性扩展到多个级别,请在查询字符串中使用 $ expand 查询选项,如下所示:

B) Also expand SubTypes

The alternative is to expand the SubTypes property as well, so the collection can be properly filled. To expand DTO mapped properties over multiple levels, use the $expand query option in your query string like so:

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

这篇关于EF Core-使用Automapper从OData返回映射的多对多关系的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆