从单个路径删除时删除级联

时间:2019-02-20 10:46:40

标签: c# asp.net asp.net-mvc entity-framework entity-framework-6

关注我的数据模型。我剥离了所有不重要的注释,以使其简洁明了。

public class Bubble
{
    public int Id { get; set; }
    public string Name { get; set; }
    public ICollection<Level> Levels { get; set; }
}

public class Level
{
    public int Id { get; set; }
    public int BubbleId { get; set; }
    [ForeignKey("ParentLevel")]
    public int? LevelId { get; set; }

    public string Name { get; set; }

    public Level ParentLevel { get; set; }
    public Bubble Bubble { get; set; }

    public ICollection<Level> Levels { get; set; }
    public ICollection<Item> Items { get; set; }
}

public class Item
{
    public int Id { get; set; }
    public int LevelId { get; set; }

    public string Name { get; set; }

    public Level Level { get; set; }
}

如果我那样创建它,我将得到错误:

  

'在表'Item'上引入FOREIGN KEY约束'FK_dbo.Item_dbo.Level_LevelId'可能会导致循环或多个级联路径。指定ON DELETE NO ACTION或ON UPDATE NO ACTION,或修改其他FOREIGN KEY约束。   无法创建约束或索引。请参阅先前的错误。'

如果添加此代码,则数据库创建有效:

modelBuilder.Entity<Item>()
    .HasRequired(i => i.Level)
    .WithMany(l => l.Items)
    .HasForeignKey(i => i.LevelId)
    .WillCascadeOnDelete(false);

但是当我删除气泡时,我得到了这个错误:

  

SqlException:DELETE语句与REFERENCE约束“ FK_dbo.Item_dbo.Level_LevelId”冲突。数据库“ MvBubbles1”的表“ dbo.Item”的列“ LevelId”中发生了冲突。   该声明已终止。

所以我相信问题是Level是自引用的。因为我有很多一对多的关系,并且删除和层叠操作在除Level和Item之间的所有地方都可以使用,所以唯一的区别是在可行的情况下父级不是自引用的。我相信我只需要删除一个级联路径,但此刻我不知道如何执行此操作,而在不禁用删除功能的情况下,问题出在哪里,但我不想禁用它。

删除代码:

db.Bubbles.Remove(bubble);
db.SaveChanges();

1 个答案:

答案 0 :(得分:0)

在级联关闭时无法删除气泡的原因是,您仍然具有要删除气泡的外键的关卡。

此外,假设您要删除气泡2。气泡2的最高级别为20。
气泡3具有21级,它是20级的子级别。
气泡4的级别为22,是级别21的子级别。

如果您删除气泡2,是否应该删除气泡3和4的水平?

让我们假设您的软件没有这些特性:没有循环的关卡引用,并且所有关卡都来自同一Bubble。

您可以在删除“关卡”和“气泡”之前使要删除的气泡的所有关卡的所有LevelId无效:

// we want to remove Bubble 2
var levelsToRemove = dbContext.Levels.Where(level => level.BubbleId == 2).ToList();
// nullify all levelIds:
foreach (var levelToRemove in levelsToRemove)
{
    levelToRemove.LevelId = null;
}
// TODO: maybe we need an extra SaveChanges

// Remove the Levels and the Bubble:
dbContext.Levels.RemoveRange(levelsToRemove);

var bubbleToRemove = dbContext.Find(2); // TODO: exception if not found
dbContext.Bubbles.Remove(bubbleToRemove);
dbContext.SaveChanges();

适当的解决方案

您的代码必须防止循环引用和带有Levels的Bubbles属于其他Bubbles的Levels子级,这应该使您知道您的数据库未充分规范化。

考虑提供您的LevelCollection。这将是一对零或一对的关系,或者如果所有Bubble 2的所有关卡都有若干共同点,请考虑将这些东西放到LevelCollection中,并让您的Bubble拥有零个或多个LevelCollection(一对多)。 )。

每个LevelCollection都完全属于一个Bubble。 LevelCollection具有零个或多个级别。

现在可以保证,如果LevelCollection 42属于Bubble 2,则LevelCollection 42的所有级别都属于Bubble2。您可以从级别中删除外键BubbleId。

这不会阻止循环的关卡引用,但是会阻止一棵树中的关卡属于不同的气泡

为什么必须关闭级联

通常,如果您与一所学校有一对多关系,并且有许多学生,则在删除学校时,您还希望自动删除其所有学生。启用层叠时,实体框架会先删除所有带有外键的项目,然后再将其与您要移动的项目删除。

使用“气泡”和“水平”无法自动完成

让我们添加一些气泡和水平

                            Id | Name
Add Bubble with name A  =>  1  |  A

                                                  Id | BubbleId | LevelId
Add Level without Parent for Bubble 1         =>  10 |    1     |  null
Add sub Level of Level 10 Parent for Bubble 2 =>  11 |    1     |   10

Now give Level 10 a new LevelId               =>  10 |    1     |   11

好吧,级联已打开,让我们删除Bubble 1。

在实体框架可以执行此操作之前,它必须删除所有具有Bubble 1外键的内容。 因此,我们需要先删除第10级,而不能,因为我们必须先删除第11级。但是,无法删除11级,因为无法删除10级,等等。

我们已经制作了一个小圆圈,但是您可以想象一下,如果您拥有一个具有1000个色阶的圆圈,将会发生什么。

您的代码可能会阻止您创建一个圆,但是实体框架不能阻止您这样做。

另一个问题:假设气泡2的级别为20。级别20是级别21的子级别,级别21具有到气泡1的外键。如果删除气泡1,则气泡2的级别会发生什么情况?有人可能会说:它变成了泡泡2的最高等级,其他人可能会说:不,泡泡2失去了他的等级。实体框架无法检测到您想要的内容,因此您必须自己进行操作并关闭级联。