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

如何在ASP.NET Core Razor Page中创建支持多详情记录的主详情页?

实现主详情结构的Razor页面方案

以下是创建包含主表展示、明细录入与保存功能的Razor页面的具体步骤:

1. 定义实体与数据库上下文

首先创建主表和明细表的实体类,配置好主外键关联:

// 主表实体:员工主信息
public class EmployeeHeader
{
    [Required]
    public int EmpId { get; set; }
    [Required]
    [StringLength(50)]
    public string EmployeeType { get; set; }
    // 导航属性:关联当前主记录的所有明细
    public List<EmployeeDetail> Details { get; set; } = new List<EmployeeDetail>();
}

// 明细表实体:员工明细信息
public class EmployeeDetail
{
    public int Id { get; set; } // 明细主键
    public int EmpId { get; set; } // 外键,关联主表EmpId
    [Required]
    [StringLength(50)]
    public string FName { get; set; }
    [Required]
    [StringLength(50)]
    public string LName { get; set; }
    public string Address { get; set; }
    public string City { get; set; }
    public string State { get; set; }
    public string Zip { get; set; }
    // 导航属性:关联所属的主记录
    public EmployeeHeader Header { get; set; }
}

接着配置数据库上下文,确保主外键关系生效:

public class AppDbContext : DbContext
{
    public AppDbContext(DbContextOptions<AppDbContext> options) : base(options) { }

    public DbSet<EmployeeHeader> EmployeeHeaders { get; set; }
    public DbSet<EmployeeDetail> EmployeeDetails { get; set; }

    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        // 配置主表与明细表的一对多关联
        modelBuilder.Entity<EmployeeDetail>()
            .HasOne(d => d.Header)
            .WithMany(h => h.Details)
            .HasForeignKey(d => d.EmpId);
    }
}

2. 创建主详情Razor页面

创建名为EmployeeForm.cshtml的页面,分为主信息区域和明细录入区域,支持动态添加/删除明细行:

@page
@model EmployeeFormModel

<h1>员工信息录入</h1>

<!-- 主信息区域 -->
<div class="card mb-4">
    <div class="card-header">主信息</div>
    <div class="card-body">
        <div class="row g-3">
            <div class="col-md-6">
                <label asp-for="Header.EmpId" class="form-label">员工ID</label>
                <input asp-for="Header.EmpId" class="form-control" />
                <span asp-validation-for="Header.EmpId" class="text-danger"></span>
            </div>
            <div class="col-md-6">
                <label asp-for="Header.EmployeeType" class="form-label">员工类型</label>
                <input asp-for="Header.EmployeeType" class="form-control" />
                <span asp-validation-for="Header.EmployeeType" class="text-danger"></span>
            </div>
        </div>
    </div>
</div>

<!-- 明细信息区域 -->
<div class="card">
    <div class="card-header d-flex justify-content-between align-items-center">
        <span>员工明细</span>
        <button type="button" class="btn btn-sm btn-primary" onclick="addDetailRow()">添加新明细</button>
    </div>
    <div class="card-body">
        <form method="post">
            <table class="table table-bordered" id="detailsTable">
                <thead>
                    <tr>
                        <th>名</th>
                        <th>姓</th>
                        <th>地址</th>
                        <th>城市</th>
                        <th>州</th>
                        <th>邮编</th>
                        <th>操作</th>
                    </tr>
                </thead>
                <tbody>
                    <!-- 渲染已有的明细行(初始会有一条空行) -->
                    @for (int i = 0; i < Model.Header.Details.Count; i++)
                    {
                        <tr>
                            <td>
                                <input asp-for="Header.Details[i].FName" class="form-control" />
                                <span asp-validation-for="Header.Details[i].FName" class="text-danger"></span>
                            </td>
                            <td>
                                <input asp-for="Header.Details[i].LName" class="form-control" />
                                <span asp-validation-for="Header.Details[i].LName" class="text-danger"></span>
                            </td>
                            <td>
                                <input asp-for="Header.Details[i].Address" class="form-control" />
                            </td>
                            <td>
                                <input asp-for="Header.Details[i].City" class="form-control" />
                            </td>
                            <td>
                                <input asp-for="Header.Details[i].State" class="form-control" />
                            </td>
                            <td>
                                <input asp-for="Header.Details[i].Zip" class="form-control" />
                            </td>
                            <td>
                                <button type="button" class="btn btn-sm btn-danger" onclick="removeRow(this)">删除</button>
                            </td>
                        </tr>
                    }
                </tbody>
            </table>

            <button type="submit" class="btn btn-primary mt-3">保存所有信息</button>
        </form>
    </div>
</div>

<script>
    // 添加新的明细行
    function addDetailRow() {
        const tableBody = document.getElementById('detailsTable').querySelector('tbody');
        const rowCount = tableBody.children.length;
        // 动态生成符合模型绑定格式的输入框
        const newRow = `
            <tr>
                <td>
                    <input name="Header.Details[${rowCount}].FName" class="form-control" required />
                </td>
                <td>
                    <input name="Header.Details[${rowCount}].LName" class="form-control" required />
                </td>
                <td>
                    <input name="Header.Details[${rowCount}].Address" class="form-control" />
                </td>
                <td>
                    <input name="Header.Details[${rowCount}].City" class="form-control" />
                </td>
                <td>
                    <input name="Header.Details[${rowCount}].State" class="form-control" />
                </td>
                <td>
                    <input name="Header.Details[${rowCount}].Zip" class="form-control" />
                </td>
                <td>
                    <button type="button" class="btn btn-sm btn-danger" onclick="removeRow(this)">删除</button>
                </td>
            </tr>
        `;
        tableBody.insertAdjacentHTML('beforeend', newRow);
    }

    // 删除明细行并重新索引,确保模型绑定正常
    function removeRow(btn) {
        btn.closest('tr').remove();
        const rows = document.getElementById('detailsTable').querySelector('tbody').children;
        for (let i = 0; i < rows.length; i++) {
            const inputs = rows[i].querySelectorAll('input');
            inputs.forEach(input => {
                input.name = input.name.replace(/\[(\d+)\]/, `[${i}]`);
            });
        }
    }
</script>

3. 编写页面模型逻辑

创建EmployeeForm.cshtml.cs文件,处理页面初始化、表单提交与数据保存:

public class EmployeeFormModel : PageModel
{
    private readonly AppDbContext _dbContext;

    public EmployeeFormModel(AppDbContext dbContext)
    {
        _dbContext = dbContext;
    }

    // 绑定主记录及其明细,自动处理模型绑定
    [BindProperty]
    public EmployeeHeader Header { get; set; } = new EmployeeHeader();

    public void OnGet()
    {
        // 初始化一条空明细,方便用户直接录入
        Header.Details.Add(new EmployeeDetail());
    }

    public async Task<IActionResult> OnPostAsync()
    {
        // 验证模型有效性
        if (!ModelState.IsValid)
        {
            return Page();
        }

        // 将主记录与明细批量添加到数据库
        _dbContext.EmployeeHeaders.Add(Header);
        await _dbContext.SaveChangesAsync();

        // 保存成功后跳转到员工列表页(可根据需求调整)
        return RedirectToPage("/Employees/Index");
    }
}

4. 收尾与验证

  • 执行数据库迁移:在包管理器控制台依次运行Add-Migration InitialEmployeeSchema和Update-Database,生成对应的数据库表。
  • 测试功能:启动项目后访问该页面,录入主信息,添加多条明细,提交后查看数据库是否正确保存了主记录和对应的明细。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.16 03:03:14