EF Core - Return 使用 Automapper 从 OData 映射多对多关系
EF Core - Return mapped Many-to-Many relationship from OData using Automapper
信息
应用程序类型是托管 Blazor Web 程序集。下面是我正在使用的 nuget 包的版本。尝试扩展多对多关系的导航 属性 时发生错误。 classes 映射到 DTO classes,使中间关系变平 class.
- .Net核心版本="3.1"
- AutoMapper 版本="10.0.0"
- AutoMapper.AspNetCore.OData.EFCore 版本="2.0.1"
- AutoMapper.Extensions.ExpressionMapping 版本="4.0.1"
- AutoMapper.Extensions.Microsoft.DependencyInjection 版本="8.0.1"
- Microsoft.AspNetCore.Components.WebAssembly.Server 版本="3.2.1"
- Microsoft.AspNetCore.OData 版本="7.5.0"
要 运行 这个 repo,你需要 SQL 服务器的免费版本或更好的
将 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 Models and here - Dto Models 了解 class 定义
自动映射器配置
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 应用程序
常规设置
要使扩展工作,您需要允许使用 $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 => z != null)
子句来更改您的 AutoMapper 映射,因为正如异常告诉您的那样,null
值不是集合中允许,但 OData 将它们包含在 non-expanded 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)
应用程序类型是托管 Blazor Web 程序集。下面是我正在使用的 nuget 包的版本。尝试扩展多对多关系的导航 属性 时发生错误。 classes 映射到 DTO classes,使中间关系变平 class.
- .Net核心版本="3.1"
- AutoMapper 版本="10.0.0"
- AutoMapper.AspNetCore.OData.EFCore 版本="2.0.1"
- AutoMapper.Extensions.ExpressionMapping 版本="4.0.1"
- AutoMapper.Extensions.Microsoft.DependencyInjection 版本="8.0.1"
- Microsoft.AspNetCore.Components.WebAssembly.Server 版本="3.2.1"
- Microsoft.AspNetCore.OData 版本="7.5.0"
要 运行 这个 repo,你需要 SQL 服务器的免费版本或更好的
将 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 Models and here - Dto Models 了解 class 定义
自动映射器配置 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 应用程序
常规设置
要使扩展工作,您需要允许使用 $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 => z != null)
子句来更改您的 AutoMapper 映射,因为正如异常告诉您的那样,null
值不是集合中允许,但 OData 将它们包含在 non-expanded 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)