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

Entity Framework新增购物车时误插入Project问题排查

问题描述

购物车表通过外键关联Project表,新增购物车记录时,Entity Framework默认尝试插入新的Project实体,但实际需求仅为更新已有Project数据,无需新增。执行保存操作时,因Project的必填字段Description为空,抛出空值错误。

相关代码

Get请求方法

public async Task<IActionResult> MakePayment(int? projectId)
{
    ShoppingCart cart = new ShoppingCart()
    {
        ProjectId = projectId,
        Project = await _dbContext.Projects.Where(p => p.Id == projectId).Select(p => new Project()
        {
            AmountPaid = p.AmountPaid,
            RemainingBalance = p.RemainingBalance,
            OriginalPrice = p.OriginalPrice
        }).FirstOrDefaultAsync()
    };
    //默认将支付金额设为剩余余额
    cart.PaymentAmount = cart.Project.RemainingBalance;

    return View(cart);
}

Post请求方法

[HttpPost]
[ValidateAntiForgeryToken]
[Authorize]
public async Task<IActionResult> MakePayment(ShoppingCart shoppingCart)
{
    //获取登录用户ID
    var claimsIdentity = (ClaimsIdentity)User.Identity;
    var claim = claimsIdentity.FindFirst(ClaimTypes.NameIdentifier);
    //如果claim为空,说明未登录
    if(claim != null)//因使用了Authorize特性,claim不应为空
    {
        shoppingCart.AppUserId = claim.Value;

        //获取关联的Project以更新支付数据
        var dbProject = await _dbContext.Projects.Where(p => p.Id == shoppingCart.ProjectId).FirstOrDefaultAsync();
        if(dbProject != null)
        {
            dbProject.AmountPaid += shoppingCart.PaymentAmount;
            dbProject.RemainingBalance = dbProject.OriginalPrice - dbProject.AmountPaid;
        }
        await _dbContext.SaveChangesAsync();
    }

    await _dbContext.ShoppingCarts.AddAsync(shoppingCart);
    await _dbContext.SaveChangesAsync();

    return RedirectToAction(nameof(Index));
}

实体模型

ShoppingCart

namespace FordWare.Models
{
    public class ShoppingCart
    {
#nullable disable
        [Key]
        [Required]
        public int Id { get; set; }
#nullable enable
        public string? AppUserId { get; set; }
#nullable enable
        [ValidateNever]
        public AppUser? AppUser { get; set; }
#nullable enable
        public int? ProjectId { get; set; }
#nullable enable
        [ValidateNever]
        public Project? Project { get; set; }
#nullable enable
        public double? PaymentAmount { get; set; }
    }
}

Project

namespace FordWare.Models
{
    public class Project
    {
#nullable disable
        [Required]
        [Key]
        public int Id { get; set; }
#nullable disable
        [Required]
        public string Description { get; set; }
#nullable enable
        [ValidateNever]
        [DisplayName("Image")]
        public string? ImageURL { get; set; }
#nullable enable
        public string? LinkToProject { get; set; }
#nullable enable
        public double? OriginalPrice { get; set; }
#nullable enable
        public double? AmountPaid { get; set; }
#nullable enable
        public double? RemainingBalance { get; set; }
#nullable disable
        [Required]
        public string ServiceCSV { get; set; }
#nullable enable
        [DisplayName("Company")]
        public int? CompanyId { get; set; }
#nullable enable
        [ForeignKey("CompanyId")]
        [ValidateNever]
        public Company? Company { get; set; }
#nullable disable
        [Required]
        public double EstimatedHours { get; set; }
#nullable disable
        [Required]
        public double EstimatedPrice { get; set; }
#nullable disable
        [Required]
        public DateTime TargetStartDate { get; set; }
#nullable disable
        [Required]
        public DateTime TargetCompletionDate { get; set; }
    }
}

报错截图

报错截图


解决方案

问题根源

  1. Get方法中创建了未被EF上下文跟踪的新Project对象,并赋值给ShoppingCart的导航属性。Post提交后,EF会将这个不完整的Project识别为需要插入的新实体。
  2. Post方法新增ShoppingCart时,EF会级联处理其关联的Project导航属性,尝试插入缺少必填字段的Project,触发空值错误。

具体修改

1. 调整Get方法,避免创建新Project对象

只获取Project的必要计算字段用于页面展示,无需赋值ShoppingCart的Project导航属性,仅保留外键ProjectId即可关联已有Project:

public async Task<IActionResult> MakePayment(int? projectId)
{
    var projectInfo = await _dbContext.Projects
        .Where(p => p.Id == projectId)
        .Select(p => new 
        {
            p.AmountPaid,
            p.RemainingBalance,
            p.OriginalPrice
        })
        .FirstOrDefaultAsync();

    ShoppingCart cart = new ShoppingCart()
    {
        ProjectId = projectId,
        PaymentAmount = projectInfo?.RemainingBalance
    };

    return View(cart);
}

2. 修改Post方法,清空无效导航属性

Post提交的ShoppingCart对象中,Project导航属性是不完整的(页面不会回传完整Project数据),需清空该属性,让EF仅通过外键关联已有Project:

[HttpPost]
[ValidateAntiForgeryToken]
[Authorize]
public async Task<IActionResult> MakePayment(ShoppingCart shoppingCart)
{
    var claimsIdentity = (ClaimsIdentity)User.Identity;
    var claim = claimsIdentity.FindFirst(ClaimTypes.NameIdentifier);
    
    if(claim != null)
    {
        shoppingCart.AppUserId = claim.Value;

        // 更新已有Project数据
        var dbProject = await _dbContext.Projects.FindAsync(shoppingCart.ProjectId);
        if(dbProject != null)
        {
            // 处理空值,避免运算错误
            dbProject.AmountPaid += shoppingCart.PaymentAmount ?? 0;
            dbProject.RemainingBalance = dbProject.OriginalPrice - dbProject.AmountPaid;
            await _dbContext.SaveChangesAsync();
        }

        // 关键:清空Project导航属性,仅保留外键关联
        shoppingCart.Project = null;
        await _dbContext.ShoppingCarts.AddAsync(shoppingCart);
        await _dbContext.SaveChangesAsync();
    }

    return RedirectToAction(nameof(Index));
}

额外说明

  • EF实体跟踪规则:未被上下文跟踪的新对象,关联到新增实体时会被EF判定为需要插入的新实体。
  • 关联已有实体的正确方式:优先使用外键字段(如ProjectId)关联;若必须使用导航属性,需先通过_dbContext.Projects.Attach(project)将实体附加到上下文,标记为已存在状态。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.09 18:10:49