EF Core多对多关系结合AutoMapper时API返回循环引用问题
嘿,我来帮你搞定这个基于.NET Core + EF Core 2的医生和预约多对多关系配置,还有AutoMapper映射的问题,咱们一步步来拆解:
1. 完善EF Core多对多关系配置
首先得明确:EF Core 2.x不支持EF6那种隐式多对多关系,必须显式定义中间关联实体来存储医生和预约的关联数据。我先帮你补全所有实体类和DbContext配置:
医生实体(Doctor)
[Table("Doctors")] public class Doctor { public int Id { get; set; } public string Name { get; set; } // 关联中间表集合,建议命名成DoctorAppointments更清晰 public ICollection<DoctorAppointment> DoctorAppointments { get; set; } = new List<DoctorAppointment>(); }
预约实体(Appointment)
[Table("Appointments")] public class Appointment { public int Id { get; set; } public string Name { get; set; } public string Category { get; set; } // 关联中间表集合 public ICollection<DoctorAppointment> DoctorAppointments { get; set; } = new List<DoctorAppointment>(); }
中间关联表(DoctorAppointment)
这个实体用来存储医生和预约的多对多关联主键:
[Table("DoctorAppointments")] public class DoctorAppointment { public int DoctorId { get; set; } public Doctor Doctor { get; set; } public int AppointmentId { get; set; } public Appointment Appointment { get; set; } }
DbContext配置(Fluent API)
接下来在你的DbContext里用Fluent API配置复合主键和关联关系:
public class ClinicDbContext : DbContext { public ClinicDbContext(DbContextOptions<ClinicDbContext> options) : base(options) { } public DbSet<Doctor> Doctors { get; set; } public DbSet<Appointment> Appointments { get; set; } public DbSet<DoctorAppointment> DoctorAppointments { get; set; } protected override void OnModelCreating(ModelBuilder modelBuilder) { // 配置中间表的复合主键(必须,因为关联关系由两个主键共同决定) modelBuilder.Entity<DoctorAppointment>() .HasKey(da => new { da.DoctorId, da.AppointmentId }); // 配置Doctor与中间表的一对多关系 modelBuilder.Entity<DoctorAppointment>() .HasOne(da => da.Doctor) .WithMany(d => d.DoctorAppointments) .HasForeignKey(da => da.DoctorId) .OnDelete(DeleteBehavior.Cascade); // 可根据业务需求调整删除行为 // 配置Appointment与中间表的一对多关系 modelBuilder.Entity<DoctorAppointment>() .HasOne(da => da.Appointment) .WithMany(a => a.DoctorAppointments) .HasForeignKey(da => da.AppointmentId) .OnDelete(DeleteBehavior.Cascade); } }
2. AutoMapper映射配置
接下来实现实体到DTO的映射,假设你需要把医生和关联的预约信息返回给前端,或者把预约和关联的医生信息返回,我给你一套常用的配置:
先定义DTO类
// 医生DTO,包含关联的预约列表 public class DoctorDto { public int Id { get; set; } public string Name { get; set; } public List<AppointmentDto> Appointments { get; set; } } // 预约DTO,包含关联的医生列表 public class AppointmentDto { public int Id { get; set; } public string Name { get; set; } public string Category { get; set; } public List<DoctorDto> Doctors { get; set; } }
创建AutoMapper Profile
public class ClinicMappingProfile : Profile { public ClinicMappingProfile() { // 映射Doctor到DoctorDto:通过中间表取出关联的Appointment CreateMap<Doctor, DoctorDto>() .ForMember(dest => dest.Appointments, opt => opt.MapFrom(src => src.DoctorAppointments.Select(da => da.Appointment))); // 映射Appointment到AppointmentDto:通过中间表取出关联的Doctor CreateMap<Appointment, AppointmentDto>() .ForMember(dest => dest.Doctors, opt => opt.MapFrom(src => src.DoctorAppointments.Select(da => da.Doctor))); // 如果需要反向映射(DTO转实体,比如创建/更新场景) CreateMap<DoctorDto, Doctor>() .ForMember(dest => dest.DoctorAppointments, opt => opt.MapFrom(src => src.Appointments.Select(a => new DoctorAppointment { AppointmentId = a.Id }))); CreateMap<AppointmentDto, Appointment>() .ForMember(dest => dest.DoctorAppointments, opt => opt.MapFrom(src => src.Doctors.Select(d => new DoctorAppointment { DoctorId = d.Id }))); } }
注册AutoMapper到.NET Core服务
在Startup.cs的ConfigureServices方法里注册AutoMapper:
public void ConfigureServices(IServiceCollection services) { // 注册DbContext services.AddDbContext<ClinicDbContext>(options => options.UseSqlServer(Configuration.GetConnectionString("ClinicConnection"))); // 注册AutoMapper,自动扫描当前程序集里的Profile services.AddAutoMapper(typeof(Startup)); // 其他服务(比如MVC、Swagger等)的注册... }
使用示例(Controller中)
查询医生时记得Include关联的中间表和实体,否则AutoMapper拿不到关联数据:
[ApiController] [Route("api/doctors")] public class DoctorsController : ControllerBase { private readonly ClinicDbContext _context; private readonly IMapper _mapper; public DoctorsController(ClinicDbContext context, IMapper mapper) { _context = context; _mapper = mapper; } [HttpGet("{id}")] public async Task<ActionResult<DoctorDto>> GetDoctor(int id) { var doctor = await _context.Doctors .Include(d => d.DoctorAppointments) .ThenInclude(da => da.Appointment) // 嵌套Include取出关联的预约 .FirstOrDefaultAsync(d => d.Id == id); if (doctor == null) { return NotFound(); } // 映射成DTO返回 return _mapper.Map<DoctorDto>(doctor); } }
内容的提问来源于stack exchange,提问作者Tiago Cunha
相关产品推荐
相关产品推荐

