【问题标题】:EF Core - Return mapped Many-to-Many relationship from OData using AutomapperEF Core - 使用 Automapper 从 OData 返回映射的多对多关系
【发布时间】:2020-10-09 00:31:24
【问题描述】:
信息

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

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

要运行此 repo,您​​需要免费版本的 SQL Server 或更高版本

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

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)...

楷模

请参阅here - Entity Modelshere - Dto Models 了解类定义

自动映射器配置
    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
                    })));
        }
    }
启动
    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();
        }
    }
课程控制器
    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=Students

复制品

我已经为这个问题构建了演示 Blazor WASM 应用程序来重现

Repository

【问题讨论】:

  • 哪里抛出了异常? (或者更简单,只需将包括堆栈跟踪在内的完整异常添加到帖子中。)
  • 我添加了堆栈跟踪,并在控制器中指出了错误的确切位置

标签: c# entity-framework-core odata automapper


【解决方案1】:

常规设置

要使扩展工作,您需要允许使用 $expand query option。像这样显式配置它:

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:

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

设置该配置后,根据您的要求,至少有两种可能的解决方案:

A) 只扩展Types

如果您只想扩展 Types,则需要通过添加 .Where(z =&gt; z != null) 子句来更改 AutoMapper 映射,因为正如异常告诉您的那样,集合中不允许使用 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>();
    }
}

那么你可以使用下面的查询:

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

B) 也展开SubTypes

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

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

【讨论】:

  • $expand OData 命令在 Automapper GetAsync 扩展方法中动态处理包含语句
  • 我正在使用 Automapper 表达式映射:docs.automapper.org/en/stable/… 我也在使用 Automapper OData nuget:github.com/AutoMapper/AutoMapper.Extensions.OData
  • 那么这需要进一步调查。但是,我提供的解决方案确实有效(我知道您正在使用的软件包)。
  • 好的,我早上试试这个配置
  • 我又扩展了答案。
猜你喜欢
  • 1970-01-01
  • 2018-07-03
  • 2020-07-14
  • 1970-01-01
  • 2020-01-01
  • 1970-01-01
  • 2021-05-01
  • 1970-01-01
  • 2021-10-11
相关资源
最近更新 更多