实体框架5:代码优先的周期性关系问题

时间:2012-11-06 22:16:28

标签: c# database entity-framework ef-code-first foreign-keys

我理解为什么EF不允许PK / FK关系中的“循环引用”。我正在寻找有关如何更改模型的建议,以使以下方案有效。

方案

三个实体:EmployeeAgencyWorkRecord。他们的目的是记录员工上班时间。 Employee然后包含对他/她所雇用的Agency的引用,并且他/她WorkRecord包含对Agency所做工作的引用。

public class Employee
{
    [Key]
    public int Id { get; set; }

    public string Name { get; set; }

    public int AgencyId { get; set; }
    public virtual Agency Agency { get; set; }

    public virtual IEnumerable<WorkRecord> WorkRecords { get; set; }
}

public class Agency
{
    [Key]
    public int Id { get; set; } 
}

public class WorkRecord
{
    [Key]
    public int Id { get; set; } 

    public int Hours { get; set; } 

    public int AgencyId { get; set; } 
    public virtual Agency Agency { get; set; } 

    public int EmployeeId { get; set; }
    public virtual Employee { get; set; }
}

像这样,它是婊子:FK_dbo.WorkRecords_dbo.Employees_EmployeeId会引起周期性参考。

实验

我的第一个想法是因为双向虚拟属性,所以我决定将这两个中的一个指定为具有单向关系的顶级实体:

首先,我将WorkRecord指定为顶级实体,并从WorkRecords实体中移除虚拟Employee引用引用...生成相同的消息。

其次,我将Employee作为顶级实体,保留其虚拟WorkRecords集合,并从Employee实体中删除虚拟WorkRecord引用属性...工作正常,但没有实现我的目标。

经过更多调查后,我发现它是导致循环引用的两个实体上的代理虚拟引用属性。如果一个实体将其删除,则Employee / WorkRecord实体关系可以在所有方向上运行。

问题:

所以,我可以问清楚 - 如何使用WorkRecord作为我的顶级实体来表达这种商业模式,而不会让EF5感到不安?

2 个答案:

答案 0 :(得分:2)

听起来你只是想让EF退缩,但我认为它实际上是在表达数据耦合方面的一个有效问题。如果将AgencyId绑定到WorkRecord和Employee,则更新WorkRecord上的AgencyId将会级联到Employee。然后将级联到WorkRecord等。因此“循环引用”。你真的应该指定哪些数据对象将“拥有”与代理商的关系。

就我个人而言,我怀疑最自然的约束是从WorkRecord引用代理。我可以看到一个员工可能会从一个代理商转移到另一个代理商的情况,但WorkRecord从一个代理商转移到另一个代理商则要困难得多。还有一种情况是,没有WorkRecord的员工真的不能真正被称为员工。如果您确定是这种情况,那么我将从员工中删除代理商参考。如果您需要从员工那里访问代理商,那么您可能通过WorkRecord。

然而,所有这些仅仅是概念性的。我怀疑如果你使EmployId在Employee上成为空,那么EF不会再抱怨了(你可能希望它两者都是可选的)。这应该使得在没有要求使用WorkRecord进行循环更新的情况下更新Employee是有效的。我必须对此进行测试以验证,但我怀疑它是否成立。

public class Employee
{
    [Key]
    public int Id { get; set; }

    public string Name { get; set; }

    public int? AgencyId { get; set; }
    public virtual Agency Agency { get; set; }

    public virtual IEnumerable<WorkRecord> WorkRecords { get; set; }
}

答案 1 :(得分:1)

您可能从SQL Server获得异常,而不是实体框架,例如:

  

在表'XYZ'上引入FOREIGN KEY约束'ABC'可能会导致   循环或多个级联路径。指定ON DELETE NO ACTION或ON   更新NO ACTION,或修改其他FOREIGN KEY约束。

此异常基本上说明了解决问题所需的操作:“指定ON DELETE NO ACTION”表示至少禁用其中一个关系的级联删除。问题是所有三个关系都是 required ,因为您的外键属性AgencyIdEmployeeId不可为空。在这种情况下,EF将在启用删除的情况下在数据库中创建关系。删除Agency时,结果是多个删除路径:它会删除WorkRecords和Employees,但Employees也会删除Workrecords,因此WorkRecords上有两个多个删除路径。

您只能使用Fluent API禁用级联删除:

modelBuilder.Entity<Employee>()
    .HasRequired(e => e.Agency)
    .WithMany()
    .HasForeignKey(e => e.AgencyId);

modelBuilder.Entity<WorkRecord>()
    .HasRequired(w => w.Agency)
    .WithMany()
    .HasForeignKey(w => w.AgencyId)
    .WillCascadeOnDelete(false); // or for one or more of the other relationships

modelBuilder.Entity<WorkRecord>()
    .HasRequired(w => w.Employee)
    .WithMany(e => e.WorkRecords)
    .HasForeignKey(w => w.EmployeeId);

现在删除Agency会导致相关员工被删除,被删除的员工将删除相关的工作记录。但该机构不再直接删除工作记录,因此删除了第二个删除路径。

您也可以选择可选之一,按惯例自动禁用级联删除(参见Jacob Proffitt的回答)。

BTW:您不能使用IEnumerable<T>作为导航属性,必须使用ICollection<T>或派生的接口或实现。