实体框架代码优先关系 - 如何定义两个对象之间的关系:两个实体之间的可选一对一

Posted

技术标签:

【中文标题】实体框架代码优先关系 - 如何定义两个对象之间的关系:两个实体之间的可选一对一【英文标题】:Entity Framework Code First Relationship - How to define relationship between 2 objects: Optional One to One From Both Entities to each other 【发布时间】:2021-09-20 09:27:07 【问题描述】:

我有一个要求,即我需要链接数据库中的 2 个实体(使用 Code First 生成),但我有点不确定如何定义外键/关系,以便我可以从另一个访问每个项目。

基本上,作业可以存在而不需要任何票证。工单也可以没有作业而存在,但最终将为现有工单创建作业。

我正在使用实体框架 6

public class Job

    [Key]
    [DatabaseGenerated(DatabaseGeneratedOption.Identity)]
    public Guid JobId  get; set; 

    //Other Properties

    public virtual Ticket Ticket  get; set; 


public class Ticket

    [Key]
    [DatabaseGenerated(DatabaseGeneratedOption.Identity)]
    public Guid TicketId  get; set; 

    //Other Properties

    public virtual Job Job  get; set; 

然后,基本上我想要做的是选择一个带有它的票的工作(如果它有一个)和一个带有它的工作的票(如果它有一个)。

var tickets = context.Tickets
              .Include(ticket => ticket.Job);

var job = context.Jobs
              .Include(job => job.Ticket);

我是否在两个表上都放置了一个可为空的外键?

public class Job

    [Key]
    [DatabaseGenerated(DatabaseGeneratedOption.Identity)]
    public Guid JobId  get; set; 

    //Other Properties

    public Guid? TicketId  get; set; )

    [ForeignKey("TicketId")]
    public virtual Ticket Ticket  get; set; 


public class Ticket

    [Key]
    [DatabaseGenerated(DatabaseGeneratedOption.Identity)]
    public Guid TicketId  get; set; 

    //Other Properties

    public Guid? JobId  get; set; )

    [ForeignKey("JobId ")]    
    public virtual Job Job  get; set; 

...或者有更好的方法来做到这一点(最好使用数据注释)?

提前感谢您提供的任何指导。

编辑:以下似乎有效,但当然不能保证 TicketId 的 Job 和 JobId 的 TicketId 上的唯一键:- 更正:这不适用于检索外键属性 - 见下文

public class Job

    [Key]
    [DatabaseGenerated(DatabaseGeneratedOption.Identity)]
    public Guid JobId  get; set; 

    //Other Properties

    [InverseProperty("Jobs")]
    public Guid? TicketId  get; set; )

    [ForeignKey("TicketId")]
    public virtual ICollection<Ticket> Tickets  get; set; 


public class Ticket

    [Key]
    [DatabaseGenerated(DatabaseGeneratedOption.Identity)]
    public Guid TicketId  get; set; 

    //Other Properties

    [InverseProperty("Tickets")]
    public Guid? JobId  get; set; )
  
    [ForeignKey("JobId ")]
    public virtual ICollection<Job> Jobs  get; set; 

编辑:请参阅 .NET Core 的 Serge 答案。我已经尝试了上述方法,但并未检索每个实体的子属性。 IE。检索作业时没有工单,检索工单时没有作业。

如果有人有任何想法,仍然卡住!

【问题讨论】:

我相信你是对的。这就是你所做的。一个工作可以有票也可以没有。也就是说 - 作业的 ticketId 可以为 null 或不为 null,反之亦然。 您好,实际上我只是尝试过并收到以下错误:“无法确定类型 'Data.Models.Ticket' 和 'Data.Models.Job' 之间关联的主体端” . 必须使用关系流式 API 或数据注释显式配置此关联的主体端。" 哪个 EF 版本?在 EF6 中,这并不容易:***.com/a/22237880/861716 嗨格特,感谢您的回复。是的,在 EF6 中并不容易。我会好好阅读您的出色回答。 【参考方案1】:

你必须修理你的桌子。如果您希望键自动递增,请将 Guid 更改为 int。还要注意 Nullable TicketId

public class Job

      [Key]
     public int Id  get; set; 
      public string Name  get; set; 
    
     public int? TicketId get; set;

    [ForeignKey(nameof(TicketId ))]
    [InverseProperty("Job")]
    public virtual Ticket Ticket  get; set; 


public class Ticket

      [Key]
     public int Id  get; set; 
      public string Name  get; set; 
    //Other Properties

 
    [InverseProperty("Ticket")]
    public virtual Job Job  get; set; 

此模型已使用此代码在 Vs 2019 和 MS SQL Server 中成功测试

            var ticket = new Ticket  Name = "Ticket1" ;
            _context.Tickets.Add(ticket);
            _context.SaveChanges();

            var job = new Job  Name = "Job1", TicketId=1;
            _context.Jobs.Add(job);
            _context.SaveChanges();

            var job2 = new Job  Name = "Job2" ;
            _context.Jobs.Add(job2);
            _context.SaveChanges();

            var jobs = _context.Jobs.Include(i => i.Ticket).ToList();
            var tickets = _context.Tickets.Include(i => i.Job).ToList();

【讨论】:

感谢您的回复。我使用 Guids 作为主键,但这工作正常。我已经尝试过您的建议,但收到错误消息:“无法确定类型 'Data.Models.Job' 和 'Data.Models.Ticket' 之间关联的主体端。此关联的主体端必须明确使用关系流式 API 或数据注释进行配置。”添加迁移时。有什么想法吗? @ledragon 再试一次。很难配置,因为您不知道第一个是什么,第二个是什么。我不能在表中保存两个外键,外键应该只有一个 感谢您的修改。不幸的是还没有快乐。当我插入新票证时,我收到以下 SQL 错误:“INSERT 语句与 FOREIGN KEY 约束“FK_dbo.Ticket_dbo.Job_TicketId”冲突。冲突发生在数据库“xxx”、表“dbo.Job”、列“JobId”中’。”感谢您一直以来的帮助! @ledragon 请发布您如何插入新灌木丛的代码 只是以标准的 EF 方式。我创建了一个 Ticket,添加到上下文并调用 SaveChanges。票证的创建方式没有什么特别之处。到目前为止唯一有效的是在每个实体上创建一个外键,并将关系设为一对多或零。即在 Ticket 上有一个属性 ICollection Jobs 和在 Job ICollection Tickets 上,并且知道 Jobs 中只有唯一的 TicketId,Ticket 中只有唯一的 JobId。也许这是要走的路?【参考方案2】:
public class Job

    [Key]
    [DatabaseGenerated(DatabaseGeneratedOption.Identity)]
    public Guid JobId  get; set; 

    //Other Properties

    

    [ForeignKey("Ticket")]
    public int TicketId  get; set; 

    public virtual Ticket Ticket  get; set; 
    
    public virtual List<Job> Jobs  get; set; 



public class Ticket

    [Key]
    [DatabaseGenerated(DatabaseGeneratedOption.Identity)]
    public Guid TicketId  get; set; 

    //Other Properties

  

    [ForeignKey("Job")]   
    public int JobId  get; set; 


    public virtual Job Job  get; set; 
    
    public virtual List<Ticket> Tickets  get; set;


【讨论】:

以上是关于实体框架代码优先关系 - 如何定义两个对象之间的关系:两个实体之间的可选一对一的主要内容,如果未能解决你的问题,请参考以下文章

两个表之间的许多关系 - 代码优先

实体框架代码优先 - 来自同一个表的两个外键

实体框架 CTP5,代码优先。多对多级联删除

如何使用实体框架关联来自多个上下文的对象

使用实体框架 4.1 代码优先方法将一对一的表关系映射到单个实体

两个实体之间的代码优先外键