You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

EF Core中如何映射SPA项目的预订实体关系?

EF Core 健康项目预订关系映射方案

问题描述

我正在用ASP.NET Core API开发一个SPA项目,用户可在网站上预订健康项目,无需付款。每个健康项目对应不同时长(即客人在水疗中心参与项目的时间),每个时长包含两种房间类型,搭配DurationType枚举(代表周/晚)。每个房间有独立的RoomType、价格和折扣,例如单人间、双人间。

核心需求:用EF Core在数据库中映射预订关系,预订需关联一个健康项目、一个时长及一个房间,实体层级为:健康项目包含多个时长,时长包含多个房间。

当前实体代码

HealthProgram

public class HealthProgram : BaseEntity
{
    public string Name { get; set; }
    public string Description { get; set; }
    public string EXPECT { get; set; }
    public string BENEFITS { get; set; }
    public string PACKAGE { get; set; }
    public string NameAR { get; set; }
    public string DescriptionAr { get; set; }
    public string EXPECTAr { get; set; }
    public string BENEFITSAR { get; set; }
    public string PACKAGEAr { get; set; }
    public virtual ICollection<Duration> duration { get; set; }
    public virtual ICollection<ProgramImages> ProgramImages { get; set; }
}

Duration

public class Duration : BaseEntity
{
    public DurationType DurationType { get; set; }
    public int DurationNumbers { get; set; }
    public int HealthProgramId { get; set; }
    public virtual HealthProgram program { get; set; }
    public virtual ICollection<Room> rooms { get; set; } 
}

Room

public class Room : BaseEntity
{
    public RoomType Type { get; set; }
    public decimal Price { get; set; }
    public decimal Discount { get; set; }
    public int durationId { get; set; }
    public virtual Duration duration { get; set; }
}

Gust(建议修正为Guest)

public class Gust : BaseEntity
{
    public string FristName { get; set; }
    public string LastName { get; set; }
    public int Age { get; set; }
    public string PhoneNumber { get; set; }
    public string Email { get; set; }
    public int Height { get; set; }
    public int Weight { get; set; }
    public bool cardiovascular_disease { get; set; }
    public bool taking_medications { get; set; }
    public bool practicing_sport { get; set; }
    public bool allergies { get; set; }
    public bool Any_illness { get; set; }
    public int BookedProgramId { get; set; }
    public virtual Booking Booking { get; set; }
}

BookedProgram

public class BookedProgram : BaseEntity
{
    public string ProgramName { get; set; }
    public string ProgramNameAr { get; set; }
    public string duration { get; set; }
    public RoomType Room { get; set; }
    public int RoomId { get; set; }
    [Column(TypeName = "decimal(18,4)")] 
    public decimal Price { get; set; }
    public virtual Booking booking { get; set; }
}

Booking

public class Booking : BaseEntity
{
    public int GistId { get; set; }
    public virtual Gust gust { get; set; }
    public int BookedProgramId { get; set; }
    public virtual BookedProgram BookedProgram { get; set; }
    public decimal TotalPrice { get; set; }
    public DateTime StartDate { get; set; }
    public DateTime EndDate { get; set; }
}

优化后的映射方案

一、实体模型调整

当前实体存在冗余字段(如BookedProgram中存储的ProgramName、duration),且部分关联关系不符合业务逻辑,调整后实体如下:

修正后的Guest实体

public class Guest : BaseEntity
{
    public string FirstName { get; set; }
    public string LastName { get; set; }
    public int Age { get; set; }
    public string PhoneNumber { get; set; }
    public string Email { get; set; }
    public int Height { get; set; }
    public int Weight { get; set; }
    public bool CardiovascularDisease { get; set; }
    public bool TakingMedications { get; set; }
    public bool PracticingSport { get; set; }
    public bool Allergies { get; set; }
    public bool AnyIllness { get; set; }
    // 一个客人可拥有多个预订
    public virtual ICollection<Booking> Bookings { get; set; } = new List<Booking>();
}

修正后的BookedProgram实体

public class BookedProgram : BaseEntity
{
    public int RoomId { get; set; }
    // 直接关联房间,通过房间导航至时长、健康项目
    public virtual Room Room { get; set; }
    [Column(TypeName = "decimal(18,4)")]
    // 存储预订时的实际结算价格(避免后续价格变动影响历史订单)
    public decimal ActualPrice { get; set; }
    public virtual Booking Booking { get; set; }
}

修正后的Booking实体

public class Booking : BaseEntity
{
    public int GuestId { get; set; }
    public virtual Guest Guest { get; set; }
    public int BookedProgramId { get; set; }
    public virtual BookedProgram BookedProgram { get; set; }
    public decimal TotalPrice { get; set; }
    public DateTime StartDate { get; set; }
    public DateTime EndDate { get; set; }
}

二、EF Core Fluent API 关系配置

在DbContext的OnModelCreating方法中,明确各实体间的关联规则:

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    // 健康项目 → 时长:一对多,级联删除
    modelBuilder.Entity<HealthProgram>()
        .HasMany(h => h.duration)
        .WithOne(d => d.program)
        .HasForeignKey(d => d.HealthProgramId)
        .OnDelete(DeleteBehavior.Cascade);

    // 时长 → 房间:一对多,级联删除
    modelBuilder.Entity<Duration>()
        .HasMany(d => d.rooms)
        .WithOne(r => r.duration)
        .HasForeignKey(r => r.durationId)
        .OnDelete(DeleteBehavior.Cascade);

    // 客人 → 预订:一对多,级联删除
    modelBuilder.Entity<Guest>()
        .HasMany(g => g.Bookings)
        .WithOne(b => b.Guest)
        .HasForeignKey(b => b.GuestId)
        .OnDelete(DeleteBehavior.Cascade);

    // 预订 → 已预订项目:一对一,级联删除
    modelBuilder.Entity<Booking>()
        .HasOne(b => b.BookedProgram)
        .WithOne(bp => bp.Booking)
        .HasForeignKey<Booking>(b => b.BookedProgramId)
        .OnDelete(DeleteBehavior.Cascade);

    // 已预订项目 → 房间:多对一(房间可被多次预订),限制删除(避免删除房间影响历史预订)
    modelBuilder.Entity<BookedProgram>()
        .HasOne(bp => bp.Room)
        .WithMany()
        .HasForeignKey(bp => bp.RoomId)
        .OnDelete(DeleteBehavior.Restrict);
}

三、预订创建示例

// 1. 获取目标房间(包含关联的时长、健康项目)
var targetRoom = await _context.Rooms
    .Include(r => r.duration)
    .Include(r => r.duration.program)
    .FirstOrDefaultAsync(r => r.Id == targetRoomId);

if (targetRoom == null)
{
    throw new Exception("指定房间不存在");
}

// 2. 计算实际结算价格
var actualPrice = targetRoom.Price - targetRoom.Discount;

// 3. 创建新客人(若为老客人则直接用已有ID)
var newGuest = new Guest
{
    FirstName = "张三",
    LastName = "王",
    Age = 35,
    PhoneNumber = "13900139000",
    Email = "zhangsan@test.com",
    // 其他健康信息字段赋值
};
_context.Guests.Add(newGuest);
await _context.SaveChangesAsync();

// 4. 创建已预订项目
var bookedProgram = new BookedProgram
{
    RoomId = targetRoom.Id,
    ActualPrice = actualPrice
};
_context.BookedPrograms.Add(bookedProgram);
await _context.SaveChangesAsync();

// 5. 创建预订记录
var booking = new Booking
{
    GuestId = newGuest.Id,
    BookedProgramId = bookedProgram.Id,
    TotalPrice = actualPrice,
    StartDate = DateTime.Now.AddDays(1),
    // 根据时长类型计算结束时间:如DurationType为周则乘以7
    EndDate = DateTime.Now.AddDays(1) + TimeSpan.FromDays(
        targetRoom.duration.DurationType == DurationType.Week 
            ? targetRoom.duration.DurationNumbers * 7 
            : targetRoom.duration.DurationNumbers
    )
};
_context.Bookings.Add(booking);
await _context.SaveChangesAsync();

四、预订详情查询示例

var bookingDetails = await _context.Bookings
    .Include(b => b.Guest)
    .Include(b => b.BookedProgram)
        .ThenInclude(bp => bp.Room)
            .ThenInclude(r => r.duration)
                .ThenInclude(d => d.program)
    .FirstOrDefaultAsync(b => b.Id == bookingId);

if (bookingDetails != null)
{
    var programName = bookingDetails.BookedProgram.Room.duration.program.Name;
    var durationInfo = $"{bookingDetails.BookedProgram.Room.duration.DurationNumbers} {bookingDetails.BookedProgram.Room.duration.DurationType}";
    var roomType = bookingDetails.BookedProgram.Room.Type;
    var actualPrice = bookingDetails.BookedProgram.ActualPrice;
    // 后续处理详情展示逻辑
}

内容的提问来源于stack exchange,提问作者mokhtar sayed

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.06 02:10:39