简体   繁体   English

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

[英]Entity Framework 5: Code-First Cyclical Relationship Issues

I understand why EF does not allow "cyclical references" in the PK/FK relationships. 我理解为什么EF不允许PK / FK关系中的“循环引用”。 I am looking for advice on how to alter my model to make the following scenario work. 我正在寻找有关如何更改模型以使以下方案有效的建议。

Scenario 脚本

Three entities: Employee , Agency , WorkRecord . 三个实体: EmployeeAgencyWorkRecord Their purpose is to log Employee time spent doing work. 他们的目的是记录员工上班时间。 Employee then contains reference to the Agency he/she is employed by, and his/her WorkRecord contain reference to the Agency the work was done for. 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; }
}

Like this, it bitches: FK_dbo.WorkRecords_dbo.Employees_EmployeeId causes a cyclical reference. 像这样,它很FK_dbo.WorkRecords_dbo.Employees_EmployeeIdFK_dbo.WorkRecords_dbo.Employees_EmployeeId导致循环引用。

Experiments 实验

My first thought was because of the bi-directional virtual properties, so I decided to designate one of the two a top-level entity with a 1-way relationship: 我的第一个想法是因为双向虚拟属性,所以我决定将这两个中的一个指定为具有单向关系的顶级实体:

First, I designated WorkRecord as a top-level entity and remove the virtual WorkRecords reference reference from the Employee entity... the same message is produced. 首先,我将WorkRecord指定为顶级实体,并从Employee实体中删除虚拟WorkRecords引用引用...生成相同的消息。

Second, I made Employee the top-level entity, leaving its virtual WorkRecords collection, and removing the virtual Employee reference property from the WorkRecord entity... works fine but does not achieve my goal. 其次,我将Employee作为顶级实体,保留其虚拟WorkRecords集合,并从WorkRecord实体中删除虚拟Employee引用属性......工作正常但未实现我的目标。

After more investigation, I find it is the Agency virtual reference property on both entities that causes the circular reference. 经过更多调查后,我发现两个实体上的代理虚拟引用属性导致循环引用。 If one entity removes this, the Employee / WorkRecord entity relationships work in all directions. 如果一个实体删除了它,那么Employee / WorkRecord实体关系将在所有方向上运行。

Question: 题:

So, clear as i can ask - how can I express this business model, using WorkRecord as my top-level entity, without making EF5 upset? 所以,我可以问清楚 - 如何使用WorkRecord作为我的顶级实体来表达这种商业模式,而不会让EF5感到不安?

It sounds like you just want to get EF off your back, but I think it's actually expressing a valid problem in the coupling of your data. 听起来你只是想让EF退出,但我认为它实际上是在表达数据耦合方面的一个有效问题。 If you bind AgencyId to both WorkRecord and Employee then updating the AgencyId on WorkRecord, for example, will cascade to Employee. 如果将AgencyId绑定到WorkRecord和Employee,则更新WorkRecord上的AgencyId将会级联到Employee。 Which will then cascade to WorkRecord etc. Hence "circular reference". 然后将级联到WorkRecord等。因此“循环引用”。 You really should designate which of those data objects will "own" the relationship to Agency. 您真的应该指定哪些数据对象将“拥有”与代理商的关系。

Personally, I suspect that the most natural binding is to reference the Agency from the WorkRecord. 就个人而言,我怀疑最自然的约束是从WorkRecord引用代理。 I can see a scenario where an Employee might move from one agency to another but it'd be much harder for a WorkRecord to move from one Agency to another. 我可以看到一个员工可能会从一个代理商转移到另一个代理商的情况,但WorkRecord从一个代理商转移到另一个代理商则要困难得多。 It's also the case that an Employee without a WorkRecord can't really be termed much of an Employee, really. 还有一种情况是,没有WorkRecord的员工真的不能真正被称为员工。 If you determine this to be the case, then I'd remove the Agency reference from Employee. 如果您确定是这种情况,那么我将从员工中删除代理商参考。 If you need to get to the Agency from the Employee then you probably should go through a WorkRecord anyway. 如果您需要从员工那里访问代理商,那么您可能应该通过WorkRecord。

All of that is merely conceptual, however. 然而,所有这些仅仅是概念性的。 I suspect that if you make it possible for AgencyId to be null on the Employee that EF won't complain any longer (and you might want it optional on both). 我怀疑,如果你使EmployId上的AgencyId成为空,那么EF不会再抱怨了(你可能希望两者都是可选的)。 That should make it valid for an Employee to be updated without requiring a circular update with WorkRecord. 这应该使得在不需要使用WorkRecord 进行循环更新的情况下更新Employee是有效的。 I'd have to test that to verify, but I suspect it'd hold true. 我必须测试一下来验证,但我怀疑它是否成立。

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; }
}

You probably get an exception from SQL Server, not Entity Framework, like: 您可能从SQL Server而不是实体框架中获得异常,例如:

Introducing FOREIGN KEY constraint 'ABC' on table 'XYZ' may cause cycles or multiple cascade paths. 在表'XYZ'上引入FOREIGN KEY约束'ABC'可能会导致循环或多个级联路径。 Specify ON DELETE NO ACTION or ON UPDATE NO ACTION, or modify other FOREIGN KEY constraints. 指定ON DELETE NO ACTION或ON UPDATE NO ACTION,或修改其他FOREIGN KEY约束。

This exception basically says what you need to do to fix the problem: "Specifying ON DELETE NO ACTION" means disabling cascading delete for at least one of the relationships. 此异常基本上说明了解决问题所需的操作:“指定ON DELETE NO ACTION”表示禁用至少一个关系的级联删除。 The problem is that all three relationships are required because your foreign key properties AgencyId and EmployeeId are non-nullable. 问题是所有三个关系都是必需的,因为您的外键属性AgencyIdEmployeeId是不可为空的。 In this case EF will create the relationships in the database with enabled delete. 在这种情况下,EF将在启用删除的情况下在数据库中创建关系。 The result is a multiple delete path when you would delete an Agency : It would delete the WorkRecords and the Employees, but the Employees will delete the Workrecords as well, so you have two multiple delete paths on WorkRecords. 删除Agency时会导致多个删除路径:它会删除WorkRecords和Employees,但Employees也会删除Workrecords,因此WorkRecords上有两个多个删除路径。

You can disable cascading delete only with Fluent API: 您只能使用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);

Deleting an Agency now causes the related employees to be deleted and the deleted employees will cause the related workrecords to be deleted. 现在删除Agency会导致相关员工被删除,被删除的员工将删除相关的工作记录。 But the agency won't directly delete the workrecords anymore, thus removing the second delete path. 但该机构不会再直接删除工作记录,从而删除了第二个删除路径。

You can alternatively make one of relationships optional which disables cascading delete automatically by convention (see Jacob Proffitt's answer). 您可以选择使其中一个关系可选 ,这会按惯例自动禁用级联删除(请参阅Jacob Proffitt的回答)。

BTW: You can't use an IEnumerable<T> for a navigation property, you must use ICollection<T> or a derived interface or implementation. 顺便说一句:您不能将IEnumerable<T>用于导航属性,您必须使用ICollection<T>或派生的接口或实现。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM