一对多关系返回空数组

4

我试图连接一个一对多的关系,Workplace可以容纳多个人,而一个人只能有一个工作场所。

当我执行这段代码并检查结果时,p1已将w1正确分配为p1.Workplace,但是w1.employees列表总是为空。

即使在将w1分配给p1.Workplace后,我是否仍然需要手动将p1添加到w1.Employees中?

SeedData.cs(代码片段)

var w1 = new Workplace
{
   EntryCode = "1111"
   //[...] other data I cut out for brievity
};
context.Workplaces.Add(w1);

Person p1 = new Person
{
    Name = "Spencer",
    Workplace = w1
   //[...] other data I cut out for brievity
};
context.Person.Add(p1)
context.SaveChanges();

Workplace.cs

using System.Collections.Generic;
using System.ComponentModel.DataAnnotations.Schema;

namespace Counsel.Models
{
    [Table("Workplace")]
    public class Workplace
    {
        [Column("WorkplaceId")]
        public int WorkplaceId {get;set;}

        [Column("EntryCode")]
        public string EntryCode {get;set;}

        [Column("ConfirmationCode")]
        public string ConfirmationCode {get;set;}

        [Column("Employees")]
        public ICollection<Person> Employees {get;set;}
    }
}

Person.cs

using System.Collections.Generic;
using System.ComponentModel.DataAnnotations.Schema;

namespace Counsel.Models
{
    public class Person
    {
        [Column("PersonId")]
        public int PersonId {get;set;}

        [Column("Image")]
        public string Image {get;set;}

        [Column("FName")]
        public string FName { get; set; }

        [Column("LName")]
        public string LName {get;set;}

        [Column("Role")]
        public string Role {get;set;}

        [Column("Email")]
        public string Email {get;set;}

        [Column("Password")]
        public string Password {get;set;}

        public Workplace Workplace {get;set;} = new Workplace();
        public ICollection<ChatPerson> Chats {get;set;}
    }
}

DataContext.cs

using Counsel.Models;
using Microsoft.EntityFrameworkCore;

namespace Counsel.Models {
    public class DataContext : DbContext
    {
        public DataContext(DbContextOptions<DataContext> opts): base(opts){}

        public DbSet<Person> People {get;set;}
        public DbSet<Workplace> Workplaces {get;set;}


        protected override void OnModelCreating(ModelBuilder modelBuilder)
        {

            modelBuilder.Entity<Person>((item)=>{
                item.HasKey(p => p.PersonId);
                item.HasOne<Workplace>(p => p.Workplace).WithMany(w => w.Employees).OnDelete(DeleteBehavior.SetNull);
            });


            modelBuilder.Entity<Workplace>((item)=>{
                item.HasKey(p => p.WorkplaceId);
                item.HasMany<Person>(w => w.Employees).WithOne(p => p.Workplace).OnDelete(DeleteBehavior.Cascade);
            });
        }
    }
}

WorkplaceController.cs(代码片段)

[HttpGet("{id}")]
        public Workplace GetWorkplace(int id)
        {
           return context.Workplaces
                .Where(c => c.WorkplaceId == id)
                .Include(c => c.Employees).ThenInclude(c => c.Workplace)
                .FirstOrDefault();

        }

GetWorplace()输出

[
  {
    "workplaceId": 1,
    "entryCode": "1111",
    "confirmationCode": "1111",
    "employees": [

    ]
  }
]

正如您所看到的,"employees"数组是空的,尽管p1应该在其中。

我绝不是专家(可能会有错误),但通常遇到这个错误时,我会检查在将实体添加到其他内容之前是否进行了保存(在将它们链接到其他内容之前)。而且我会在事务中执行此操作。 - mishan
1
一切看起来都很好。您能否检查种子数据是否成功保存? - Krishna Varma
3个回答

7
问题是由引用导航属性初始化程序引起的:
public Workplace Workplace { get; set; } = new Workplace(); // <--

切勿这样做-它会混淆EF导航属性修复并导致意外的运行时行为。

请注意,初始化集合导航属性是可以的,尽管不是必需的。这是因为引用导航属性中的null具有特殊含义,并真正提供了一个链接到主实体的链接,该主实体可能包含或不包含相关实体的集合。

简而言之,删除初始值设定项。

public Workplace Workplace { get; set; }

问题将会得到解决。


这应该可以解决问题,但我建议将 WorkplaceId 添加到 Person 对象中,并为其添加 [ForeignKey] 属性,以链接到 Workplace 对象。 - Chris Dixon
@ChrisDixon 显式 FK 属性并非必需,因此推荐使用它可能出于其他原因(例如,在不加载相关实体的情况下应用断开的实体更改,尽管在这种场景下 EF Core 允许设置阴影属性),但总的来说,它是可选的(不需要的),我看不到任何理由在无关问题中推荐使用它。此外(我已经测试过了),即使使用显式 FK 属性,OP 问题仍会发生,因此添加它并不能解决该问题,仍然需要删除初始化程序。 - Ivan Stoev
@IvanStoev 当然,这并不是100%必需的,但我相信它可以提高对象的可读性——你可以更好地了解关键关系。这更多是为了代码可维护性而提出的建议,而不是针对这个具体问题的修复(我同意你指出了这个问题)。无论哪种方式,听起来问题已经解决了! - Chris Dixon

1
如果您正在使用数据的延迟加载,您需要枚举这些数据以便它们被拉入EF上下文中。子元素不会默认检索。另一件事情是在构建查询时明确地包含子元素。您可以在此处查看包含操作的工作方式:https://entityframework.net/include-multiple-levels
您还可以在此处查看相关问题:Include children in EF

2
OP正在使用Include()和ThenInclude()来获取子元素。 - Tony Abrams

0

Workplace有一个关系属性Employees,但它被映射为[Column("Employees")],我猜这会导致EF停止将其视为关系以查找其他实体,并开始将其视为存在于Workplace表中的列。移除列属性,并在必要时向EF提供更多信息,以便它可以正确地映射Person * ↔ 1 Workplace关系。


那并没有直接导致问题,但我感谢你指出来,因为这确实帮助我更好地理解EF。 - kzmijak

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接