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

添加新Review后Company的Reviews列表未更新且导航属性为null

问题排查:新增Review后Company的Reviews列表未更新且导航属性为null

实体类代码

Company类

public class Company
{
    [DatabaseGenerated(DatabaseGeneratedOption.Identity)]
    public int CompanyId { get; } // 注意:此处仅含getter,EF可能无法正确跟踪实体状态
    [MaxLength(100)]
    public string CompanyName { get; set; } = string.Empty;
    [MaxLength(500)]
    public string CompanyDescription { get; set; } = string.Empty;
    [MaxLength(100)]
    public string Location { get; set; } = string.Empty;
    [MaxLength(50)]
    public string City { get; set; } = string.Empty;
    [MaxLength(50)]
    public string Category { get; set; } = string.Empty;
    [MaxLength(400)]
    public string PhotoUrl { get; set; }
    public List<Review> Reviews { get; set; } = new List<Review>();
}

Review类

public class Review
{
    [DatabaseGenerated(DatabaseGeneratedOption.Identity)]
    public int ReviewId { get; set; }
    [Required,MaxLength(70)]
    public string JobTitle { get; set; } = string.Empty;
    [Required, MaxLength(1000)]
    public string Content { get; set; } = string.Empty;
    public DateTime TimeCreated { get; set; } = DateTime.UtcNow;
    public int TotalUpvotes { get; set; } = 0;
    public ApplicationUser User { get; set; }
    public string UserId { get; set; }
    public Company Company { get; set; }
    public int CompanyId { get; set; }
}

OnModelCreating配置

Company表配置

builder.HasKey(c => c.CompanyId);

builder.HasMany(c=>c.Reviews)
    .WithOne(c => c.Company)
    .HasForeignKey(c => c.CompanyId);

Review表配置

builder.HasKey(r => r.ReviewId);
builder.HasOne(r=>r.User)
    .WithMany(r=>r.Reviews)
    .HasForeignKey(r=>r.UserId)
    .OnDelete(DeleteBehavior.ClientCascade);
builder.HasOne(r => r.Company)
    .WithMany(r => r.Reviews)
    .HasForeignKey(r => r.CompanyId)
    .OnDelete(DeleteBehavior.ClientCascade);

新增Review的服务代码

public async Task<ServiceResponse<AddReviewResponseDto>> AddReview(AddReviewDto addReviewDto)
{
    ServiceResponse<AddReviewResponseDto> AddReviewResponse = new ServiceResponse<AddReviewResponseDto>();

    // 检查当前用户是否已注册
    var user = await _userManager.FindByIdAsync(GetUserId());

    if (user == null)
    {
        AddReviewResponse.Data = null;
        AddReviewResponse.Success = false;
        AddReviewResponse.Message = "未注册用户无法添加评论!";
        return AddReviewResponse;
    }

    // 拼写错误:CompnayId 应为 CompanyId
    var company = await _context.Companies.SingleOrDefaultAsync(c => c.CompanyId == addReviewDto.CompnayId);
    if (company == null)
    {
        AddReviewResponse.Data = null;
        AddReviewResponse.Success = false;
        AddReviewResponse.Message = "未找到对应ID的公司!";
        return AddReviewResponse;
    }

    var review = new Review
    {
        JobTitle = addReviewDto.JobTitle,
        Content = addReviewDto.Content,
        User = user,
        Company = company
    };

    await _context.AddAsync(review);

    company.Reviews.Add(review);

    await _context.SaveChangesAsync();
    var addReviewResponseDto = new AddReviewResponseDto
    {
        TimeCreated = review.TimeCreated,
        ReviewId = review.ReviewId
    };

    AddReviewResponse.Data = addReviewResponseDto;
    AddReviewResponse.Success = true;
    AddReviewResponse.Message = "评论添加成功";
    return AddReviewResponse;
}

接口返回结果

Review返回数据

"reviewId": 14,
"jobTitle": "software engineer",
"content": "I Love This Company",
"timeCreated": "2023-03-23T18:54:04.7119397",
"totalUpvotes": 0,
"user": null,
"userId": "userIdvalue",
"company": null,
"companyId": 6,
"upvotes": []

Company返回数据

{
    "companyId": 6,
    "companyName": "Globe Exports",
    "companyDescription": "We Globe exportsare exporters of canned products, pasta, flour, biscuits, jams, honey, tahina, halawa, foul, tomato puree, biscuits, wafer,spices&bean.",
    "location": "Cairo, Egypt",
    "city": "Cairo",
    "category": "Food Industries",
    "photoUrl": "photoUrl",
    "reviewsCount": 0,
    "averageRating": 0,
    "reviews": []
}

问题排查与解决方案

1. 实体类主键限制问题

Company类的CompanyId仅包含get方法,无set。EF Core需要实体主键具备可访问的setter(私有setter也可)才能正确跟踪实体状态,否则无法将数据库生成的ID赋值给实体,进而影响关联关系维护。

修复:给CompanyId添加私有setter:

[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int CompanyId { get; private set; }

2. 服务代码拼写错误

查询公司时使用了addReviewDto.CompnayId,正确应为addReviewDto.CompanyId。虽当前Review成功插入了CompanyId=6,但该错误会导致其他场景下无法匹配公司,必须修正。

3. 导航属性未显式加载

返回的Review中company为null、Company中reviews为空,原因是查询时未显式包含导航属性。EF Core默认不会自动加载关联数据(延迟加载在API序列化时易失效)。

修复:

  • 查询Company时加载关联的Reviews:
var company = await _context.Companies
    .Include(c => c.Reviews)
    .SingleOrDefaultAsync(c => c.CompanyId == addReviewDto.CompanyId);
  • 若需返回带Company的Review数据,查询Review时也需Include:
var review = await _context.Reviews
    .Include(r => r.Company)
    .FirstOrDefaultAsync(r => r.ReviewId == reviewId);

4. 关联关系维护简化

代码中同时执行_context.AddAsync(review)和company.Reviews.Add(review)虽可行,但可简化为直接设置外键,EF会自动维护反向关联:

var review = new Review
{
    JobTitle = addReviewDto.JobTitle,
    Content = addReviewDto.Content,
    UserId = user.Id,
    CompanyId = company.CompanyId
};
await _context.AddAsync(review);
await _context.SaveChangesAsync();

此方式无需手动添加到company.Reviews,EF会自动同步(上下文需处于跟踪状态)。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.27 11:57:56