首页 > 解决方案 > 在一对多关系中初始化子实体会导致重复

问题描述

我遇到了那个问题。我的问题很相似。

我有两个 EF 课程 Appointment 和 Job。它们之间具有一对多的关系。

 public class Appointment
{

    [DatabaseGenerated(DatabaseGeneratedOption.Identity)]
    [Key]
    public int AppointmentID { get; set; }
    public int AppointmentStatus { get; set; }
    public string Remarks { get; set; }
    public ICollection<Job> Job {get; set; }

}
 public class Job
{
    [DatabaseGenerated(DatabaseGeneratedOption.Identity)]
    public int JobId { get; set; }
    public string JobName { get; set; }
    public string JobDescription { get; set; }
    public short JobMode { get; set; }
    public virtual Appointment Appointments { get; set; }

}

当我尝试将作业添加到 Appointment 实例时,我收到 nullreferenceexception。因为 Appointment 的 Job 属性返回 null。到现在都正常。

为了处理该异常,我尝试在 Parent 的构造中添加一个块,以便在创建约会时初始化新的 Job 项。

       public Appointment()
    {
        if (this.Job == null)
        {
            this.Job = new Collection<Job>();
        }
    }

那时,我不再收到 nullreferenceexception,但该解决方案会导致重复的 Job 项。新约会成功创建但(!),不是我选择的工作。实际上是我选择的 Job 的一个实例。每次创建一个新的工作实例并与新创建的约会相关联。

示例工作

标签: sqlwpfentity-framework

解决方案


我建议坚持实体框架代码优先约定。它使您和将来阅读您的代码的人的生活更轻松。

在您的情况下,这意味着 everyAppointment有零个或多个Jobs,并且each使用外键Job完全属于一个。Appointment

class Appointment
{
    public int Id {get; set;}

    // every Appointment has zero or more Jobs:
    public virtual ICollection<Job> Jobs {get; set;}

    ...
}

class Job
{
    public int Id {get; set;}

    // every Job belongs to exactly one Appointment using foreign key:
    public int AppointmentId {get; set;}
    public virtual Appointment Appointment {get; set;}

    ...
}

因为我坚持约定,实体框架能够检测到一对多的关系。它检测主键和外键:不需要属性也不需要流畅的 API。

在 Entity Framework 中,表中的列是类中的非虚拟属性。表之间的关系(一对多,多对多,...)被标记为虚拟:它们不是您表中的真实项目

这只是一个建议,如果您有充分的理由偏离约定,您当然可以这样做。

但是,必须将表之间的关系定义为虚拟的。您还需要定义外键。

完成此操作后,您获取的约会将不会有空作业:

var fetchedAppointment = dbContext.Appointments
    .Where(appointment => appointment.Id = ...)
    .FirstOrDefault();
// fetchedAppointment.Jobs is not null!

// add a new Job:
fetchedAppointment.Jobs.Add(new Job()
{
     // no need to fill the Id, nor the foreign key. Entity Framework will do that for you
     JobName = ...,
     JobDescription = ...,
     ...
});
dbContext.SaveChanges();

但是,如果您想添加一个或多个职位的新约会,您必须自己添加它们。可以使用数组或者列表,没关系,只要实现ICollection<Job>

var addedAppointment = dbContext.Appointments.Add(new Appointment()
{
    // no need to fill the Id: entity framework will do that for you
    AppointmentStatus = ...
    Remarks = ...

    Jobs = new List<Job>()
    {
         // again: no need to fill the Id, nor the foreign key.
         // Entity Framework will do that for you
         new Job()
         {
             JobName = ...,
             JobDescription = ...,
         },
         new Job()
         {
             JobName = ...,
             JobDescription = ...,
         },
         ...
    },
});

// if you want, you can also add a Job here:
addedAppointment.Jobs.Add(new Job()
{
    JobName = ...,
    JobDescription = ...,
});

// All primary and foreign keys will be filled as soon as you call SaveChanges
dbContext.SaveChanges();

当然,您也可以将 Job 添加到具有它所属约会的上下文中:

var addedJob = dbContext.Jobs.Add(new Job()
{
    JobName = ...,
    JobDescription = ...,

    // this Job is a job of addedAppointment
    Appointment = addedAppointment,
}

或者如果您已经保存了约会,那么主键有一个值:

var addedJob = dbContext.Jobs.Add(new Job()
{
    JobName = ...,
    JobDescription = ...,

    // fill the foreign key instead
    AppointmentId = addedAppointment.Id
},

推荐阅读