同一实体类型的一对一和一对多关系

3
我将尝试设计以下实体关系:
  • 程序
  • 组件
一个程序可以拥有多个组件。其中一个组件将是主要组件。每个组件仅属于一个程序。
这些类的建模如下:
public class Program
{
    public int Id {get;set;}
    public virtual ProgramAssembly MainAssembly {get;set;}
    public virtual ICollection<ProgramAssembly> Assemblies {get;set;}
}

public class ProgramAssembly
{
   public int Id {get;set;}
   public virtual Program Program {get;set;}
   public int ProgramId {get;set;}
}

我没有使用FluentApi来指定任何内容(但我对此没有反感)。 使用上述代码,我得到了以下错误:
“在保存不公开其关系外键属性的实体时发生错误。EntityEntries属性将返回null,因为无法将单个实体标识为异常来源。通过在实体类型中公开外键属性,可以更轻松地处理保存时的异常。有关详细信息,请参见InnerException。
InnerException:无法确定依赖操作的有效排序方式。由于外键约束、模型要求或存储生成的值可能存在依赖项。”
我尝试将ProgramAssembly更改为以下内容。
public class ProgramAssembly
{
    [ForeignKkey("Program")]
    public int Id {get;set;}
    public virtual Program Program {get;set;}
}

然而,我遇到了以下错误:

ProgramAssembly_Program_Source: : 在关系“ProgramAssembly_Program”中,角色“ProgramAssembly_Program_Source”的重复性无效。因为从属角色引用了关键属性,所以从属角色的上限必须为“1”。

我还尝试了下面的流畅API方法。
modelBuilder.Entity<ProgramAssembly>()
   .HasRequired(a => a.Program)
   .WithMany(p => p.Assemblies);

public class ProgramAssembly
    {        
        public int Id { get; set; }
        public virtual Program Program { get; set; }
        [ForeignKey("Program")] //same error with or without this attribute
        public int ProgramId { get; set; }
   }

然后出现了以下错误:
“在保存不公开其关系外键属性的实体时发生错误。EntityEntries 属性将返回 null,因为无法将单个实体标识为异常来源。通过在实体类型中公开外键属性可以更轻松地处理保存时的异常。有关详细信息,请参见 InnerException。 UpdateException: 无法确定依赖操作的有效排序。由于外键约束、模型要求或存储生成的值,可能存在依赖项。”
如何在不改变向程序集添加“IsPrimary”属性的方法的情况下允许该关系?
我在 SO 上看到了一些类似的问题,但它们要么是关于 EF Core 的,比如 this one,要么建议进行重大逻辑更改,比如 this one,甚至 this one 连编译都无法通过。

1
你可以改变你的模型多少?我认为在Assembly中添加一个IsPrimary属性会比有两个关系更好看。 - Camilo Terevinto
1
通过这种方式指定关系,您将无法实现所需的功能。为了获得类似的结果,您需要像Camilo所说的那样,在ProgramAssembly实体中添加一个“IsMain”属性。如果您想在Program实体本身内获取它,则需要将MainAssembly属性更改为“public ProgramAssembly MainAssembly {get { return Assemblies.where(asm => asm.IsMain).FirstOrDefault(); }}”。 - iNovelletto
1
这只需要 program.Assemblies.Single(x => x.IsMain)(或者你甚至可以将其作为导航属性)即可,但你是对的,你需要在保存时验证它。对于 EF Core,答案应该使用 Fluent API 工作得很好。我相信你不能通过 DataAnnotations 来实现这一点。 - Camilo Terevinto
事实上,您需要指定主程序集的外键为ProgramAssembly.Id,而所有其他程序集的外键为ProgramAssembly.ProgramId。您对此是否满意?(对我来说,这听起来确实很糟糕) - Camilo Terevinto
有关原问题的任何解决方案吗? - Bjorn
显示剩余6条评论
2个回答

1
你需要告诉EF你的主键和外键如何工作。你需要添加属性来定义你的主键[Key],并定义外键,然后在导航属性上添加属性来定义该字段是外键。
public class Program
{
    [Key]
    public int Id {get;set;}
    public int MainAssemblyId {get;set;}
    [ForeignKey("MainAssemblyId ")]
    public virtual ProgramAssembly MainAssembly {get;set;}
    public virtual ICollection<ProgramAssembly> Assemblies {get;set;}
}

public class ProgramAssembly
{
   [Key]
   public int Id {get;set;}
   [ForeignKey("ProgramId")]
   public virtual Program Program {get;set;}
   public int ProgramId {get;set;}
}

哦,到目前为止那段代码给我抛出了这个异常: "在表 'ProgramAssemblies' 上引入外键约束 'FK_dbo.ProgramAssemblies_dbo.Programs_ProgramId' 可能会导致循环或多个级联路径。请指定 ON DELETE NO ACTION 或 ON UPDATE NO ACTION,或修改其他 FOREIGN KEY 约束。" - Bartosz

0

以下代码将满足您的需求。

public class Program
{
    [Key]
    public int Id {get;set;}
    public virtual ICollection<ProgramAssembly> Assemblies {get;set;}
}

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

   [ForeignKey("ProgramId")]
   public virtual Program Program {get;set;}
   public int ProgramId {get;set;}
   public bool IsMainProgramAssembly
}

如果需要,您可以使用[Required]来标记ProgramId,或者将ProgramId设置为可空。


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