引入FOREIGN KEY约束可能会导致循环或多个级联路径。在删除时指定无操作

时间:2014-12-23 02:05:04

标签: sql-server asp.net-mvc database entity-framework

用户,雇主,候选人和工作,雇主可以创造多个工作,每个工作只能有一个雇主,候选人可以申请许多工作,每个工作可以有多个申请成员。

所以关系是这样的:

enter image description here

我正在使用实体框架代码的第一种方法,目前如果我删除雇主,它将从数据库中删除所有相关的工作和用户,如果我删除候选人,它将删除用户:< / p>

modelBuilder.Entity<Employer>()
     .HasRequired(e => e.User)
    .WithOptional(e => e.Employer).WillCascadeOnDelete();

//member is candidate
modelBuilder.Entity<Member>()
    .HasRequired(e => e.User)
    .WithOptional(e => e.Member).WillCascadeOnDelete();

modelBuilder.Entity<Employer>()
    .HasMany(a => a.Jobs)
    .WithRequired(b => b.Employer)
    .WillCascadeOnDelete();

一切正常,除非我在候选人和工作之间指定多对多的关系并使用&#34; update-database&#34;更新数据库,它给了我这个错误:

介绍FOREIGN KEY约束&#39; FK_dbo.MemberJobMap_dbo.Jobs_JobId&#39;在桌面&#39; MemberJobMap&#39;可能会导致循环或多个级联路径。指定ON DELETE NO ACTION或ON UPDATE NO ACTION,或修改其他FOREIGN KEY约束。 无法创建约束。查看以前的错误。

以下是我如何指定多对多的关系:

modelBuilder.Entity<Member>()
   .HasMany(m => m.Jobs)
   .WithMany(j => j.Members)
   .Map(c =>
   {
      c.MapLeftKey("Id");
      c.MapRightKey("JobId");
      c.ToTable("MemberJobMap");
   });

当我添加迁移时:

CreateTable(
   "dbo.MemberJobMap",
   c => new
   {
      Id = c.String(nullable: false, maxLength: 128),
      JobId = c.Int(nullable: false),
   })
   .PrimaryKey(t => new { t.Id, t.JobId })
   .ForeignKey("dbo.Members", t => t.Id, cascadeDelete: true)
   .ForeignKey("dbo.Jobs", t => t.JobId, cascadeDelete: true)
   .Index(t => t.Id)
   .Index(t => t.JobId);   

我尝试将cascadeDelete更改为false,但是当我删除已应用作业的候选人或当我尝试删除具有应用候选人的作业时,这会给我带来错误。

如何解决此错误?那样:

  1. 删除作业后,将删除关联作业 candidatejobmap表行没有影响任何其他表
  2. 当候选人被移除时,它将删除相关联的 candidatejobmap表行和用户表行没有任何影响 其他表
  3. 同时保留所有其他指定的级联删除操作 相同的

3 个答案:

答案 0 :(得分:11)

我已经解决了这个问题

出现问题是因为我有两个到CandidateJobMap表的级联删除路径:

如果我删除了雇主,它将删除相关的雇主职位,这将删除CandidateJobMap表:

雇主 - &GT; Jobs-&GT; CandidateJobMap

如果我删除候选人,它将删除CandidateJobMap表:

成员 - &GT; CandidateJobMap

因此,要解决此问题,我必须禁用其中一个删除路径,在创建多对多关系时无法指定WillCascadeDelete(false),因此您必须按如下方式更改迁移:

CreateTable(
   "dbo.MemberJobMap",
   c => new
   {
      Id = c.String(nullable: false, maxLength: 128),
      JobId = c.Int(nullable: false),
   })
   .PrimaryKey(t => new { t.Id, t.JobId })
   .ForeignKey("dbo.Members", t => t.Id, cascadeDelete: false) <--------cascade delete to false
   .ForeignKey("dbo.Jobs", t => t.JobId, cascadeDelete: true)
   .Index(t => t.Id)
   .Index(t => t.JobId);   

现在因为你将cascade delete设置为false,当候选人被删除时,它不会删除相关的CandidateJobMap行,当你试图删除候选者时它会导致另一个错误,因为它也是一个相关的键。 CandidateJobMap,所以你必须在删除候选人之前手动删除CandidateJobMap中的相关行:

//remove all applied jobs from user, without doing this, you will receive an error
foreach (var appliedjob in user.Member.Jobs.ToList())
{
    user.Member.Jobs.Remove(appliedjob);
}

//before you can delete the user
await UserManager.DeleteAsync(user);

不确定这是否是最佳方式,但它对我有用。

答案 1 :(得分:4)

我意识到这已经很老了,但我遇到了同样的问题,并且有一些评论没有得到解决。

特别是“你已经隐藏了问题......”

来自多个路径的级联应该有效,因为删除祖先应该具有删除后代的效果是有效的。但这只是在理论上,实际上SQL Server只是不允许它,因此错误。这里有一个解决方案solving-the-sql-server-multiple-cascade-path-issue-with-a-trigger

他建议删除所有违规的级联操作,并用触发器替换它们以删除记录。我更喜欢在级联链的顶层工作。在他的例子中,我只是在'父母'记录中用 INSTEAD OF DELETE 为孩子们剪掉它,让级联来处理剩下的事情。

我这样做有两个原因

  1. 它应该在数据库中完成,因为它是坏数据的最后一道防线...有点像在衣服进入洗衣机之前清空口袋。处理那里的事情意味着您不必将代码复制到您将来可能构建这个DB的所有不同模型中,也不必依赖所有新手开发人员来处理它。

  2. 在最顶端的祖先进行此操作将允许所有其他关系保留,包括您将来可能添加的关系。

  3. 希望这有帮助, 麦克

答案 2 :(得分:2)

我会设计类似的东西..

User_Type    <-- Tow types of users Candidates and Employers     


USERS        <-- Common Fields for Candidates and Employers , along with one column to 
             -- identify if it is Candidate or an Employer referencing back to User_Type

Emp_Details  <-- Only columns that an employer will have referencing back to Users table

Can_Details  <-- Only columns that a Candidate will have referencing back to Users table

Jobs         <-- Jobs published by a user who is an employer referencing back to Users table

CandidateJobs <-- A composite key referencing back to Jobs(JobId) and Users who are
               --Candidates