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

EF Core一对多关系异常:仅能获取最后插入的实体

问题描述

我有一个采用MVC模式和Entity Framework Core的Web项目,通过EF DBFirst方式生成了Company(公司)和Plant(工厂)两个模型,二者为一对多关系(一个公司对应多个工厂)。希望在视图中向Plant表提交新工厂数据,并展示该公司的所有工厂数据。目前数据能成功存入数据库,但控制器存在异常:每次提交新工厂后,Company的Plants字段仅保留最后插入的工厂,而非全部。

错误原因分析
  • 控制器生命周期限制:ASP.NET Core控制器是瞬态对象,每次HTTP请求都会创建新实例。控制器中声明的_companyDeclared、_holdPlants字段,以及构造函数注入的List<Plant>和Company,每次请求都会被重新初始化,无法跨请求保存状态。
  • EF导航属性未加载:_context.Companys.Find(companyId)仅查询Company的基础数据,默认不会自动加载关联的Plants集合,导致_companyDeclared.Plants初始为空列表,添加新Plant后仅包含当前一条数据。
  • 状态保存逻辑错误:试图用_holdPlants跨请求保存工厂列表,但每次请求都会重置该列表,导致视图仅能显示最新添加的工厂。
正确实现方案

调整控制器代码

移除控制器级别的状态字段,每次请求直接从数据库加载目标公司的完整数据:

using System.Diagnostics;
using Data.Models;
using Microsoft.AspNetCore.Mvc;
using UI.Models;
using Microsoft.EntityFrameworkCore;
using Newtonsoft.Json;

namespace UI.Controllers;

public class HomeController : Controller
{
    private readonly ILogger<HomeController> _logger;
    private readonly ProjectContext _context;

    // 仅保留必要的依赖注入服务
    public HomeController(ILogger<HomeController> logger, ProjectContext context)
    {
        _logger = logger;
        _context = context;
    }

    public IActionResult Index()
    {
        return View();
    }
    
    // 完善Company信息
    [HttpPost]
    public ActionResult CompanyDetail(Company company)
    {
        if (ModelState.IsValid)
        {
            short companyId = JsonConvert.DeserializeObject<short>(TempData["CompanyId"].ToString());
            TempData.Keep("CompanyId");

            var companyToUpdate = _context.Companys.Find(companyId);
            if (companyToUpdate != null)
            {
                companyToUpdate.CompanyName = company.CompanyName;
                _context.SaveChanges();
            }
            return RedirectToAction("PlantsDetail");
        }

        return View();
    }

    // GET请求加载工厂列表
    public IActionResult PlantsDetail()
    {
        short companyId = JsonConvert.DeserializeObject<short>(TempData["CompanyId"].ToString());
        TempData.Keep("CompanyId");

        // 显式加载公司及其关联的所有工厂
        var companyWithPlants = _context.Companys
            .Include(c => c.Plants)
            .FirstOrDefault(c => c.CompanyId == companyId);

        ViewBag.myCompany = companyWithPlants;
        ViewBag.plantsList = companyWithPlants?.Plants ?? new List<Plant>();
        return View();
    }

    // 提交新工厂
    [HttpPost]
    public ActionResult PlantsDetail(Plant plant)
    {
        if (ModelState.IsValid)
        {
            short companyId = JsonConvert.DeserializeObject<short>(TempData["CompanyId"].ToString());
            TempData.Keep("CompanyId");

            // 创建新工厂并关联公司ID
            var newPlant = new Plant 
            { 
                CompanyId = companyId, 
                PlantNo = plant.PlantNo, 
                PlantName = plant.PlantName
            };

            _context.Plants.Add(newPlant);
            _context.SaveChanges();

            // 保存后重定向到GET方法,加载最新工厂列表
            return RedirectToAction(nameof(PlantsDetail));
        }

        // 验证失败时,重新加载当前公司的工厂列表
        short companyId = JsonConvert.DeserializeObject<short>(TempData["CompanyId"].ToString());
        TempData.Keep("CompanyId");
        var companyWithPlants = _context.Companys
            .Include(c => c.Plants)
            .FirstOrDefault(c => c.CompanyId == companyId);
        ViewBag.myCompany = companyWithPlants;
        ViewBag.plantsList = companyWithPlants?.Plants ?? new List<Plant>();
        return View(plant);
    }
}

调整视图代码

修复HTML结构错误,规范表格渲染:

@if (ViewBag.myCompany != null)
{
    <table class="table">
        <thead>
            <tr>
                <th>工厂编号</th>
                <th>工厂名称</th>
                <th>操作</th>
            </tr>
        </thead>
        <tbody>
            @foreach (var item in ViewBag.plantsList)
            {
                <tr>
                    <td>@item.PlantNo</td>
                    <td>@item.PlantName</td>
                    <td>
                        <button type="submit" class="btn btn-success">更新</button>
                        <button type="submit" class="btn btn-danger">删除</button>
                    </td>
                </tr>
            }
        </tbody>
    </table>
}
关键改进点
  • 移除跨请求状态字段:不再依赖控制器实例保存状态,每次请求直接从数据库获取最新数据,保证数据一致性。
  • 显式加载导航属性:使用Include(c => c.Plants)加载Company关联的所有Plants集合,确保获取完整数据。
  • POST后重定向GET:提交新工厂后重定向到PlantsDetail的GET方法,避免重复提交问题,同时保证视图加载最新的工厂列表。
  • 规范视图结构:修复HTML表格的标签嵌套错误,确保页面渲染正确。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.21 05:07:23