【问题标题】:Fluent API, one-to-one relationship and cascading deletesFluent API、一对一关系和级联删除
【发布时间】:2018-01-11 19:07:07
【问题描述】:

所以我有这门课:

public class Question
{
    public int QuestionId { get; set; }
    public string QuestionText { get; set; }
    public bool IsInactive { get; set; }

    public virtual ICollection<Answer> Answers { get; set; }
}

还有这个:

public class Answer
{
    public Guid AnswerId { get; set; }
    public string AnswerText { get; set; }

    [Required]
    [ForeignKey(nameof(Question))]
    public int QuestionId { get; set; }
    [Required]
    public virtual Question Question { get; set; }
}

使用代码优先的实体框架,一切似乎都按我的预期设置好了。我在QuestionAnswer 之间有一对多的关系,删除Question 会导致关联的Answer 消失(我认为)。

现在我想将它添加到Question 类中:

public virtual Answer RightAnswer { get; set; }

我想在QuestionAnswer 之间建立一对一的关系(除了来自AnswersQuestion 属性的一对多关系),这就是问题所在开始。问题是由于级联删除而发生的。数据库现在看到它的方式,删除问题将删除答案(包括RightAnswer),这将导致它再次尝试删除Question(或者它可能正在删除Answer删除Question,这将然后删除Answer,无论哪种方式)。为了解决这个问题,我需要告诉它 not 级联删除该一对一关系。而且,不幸的是,要做到这一点,我必须深入研究流体 API,在这里我不确定正确的方法。

我有这个:

modelBuilder.Entity<Question>()
    .HasRequired(p => p.RightAnswer)
    .WithRequiredDependent()
    .WillCascadeOnDelete(false);

但我真的不确定这应该是WithRequiredDependant 还是WithRequiredPrinciple。或者如果在这种情况下它甚至很重要。

(是的,我知道另一种解决方案是将IsRight bool 添加到Answer,但现在我想知道是否可以这样做)

【问题讨论】:

  • 在一对一的关系中,什么是主体,什么是依赖并不重要。在您的方法中,逻辑会建议要回答的主体是正确答案的问题,因此 .WithRequiredPrinciple() 在您的映射中。但是,我不太确定这在这种情况下是否可行,因为一对一的关系是通过 PK=FK 完成的,并且由于您的实体具有不同的 PK 类型,我不确定 EF 是否能够没有额外的 FK 列的映射。

标签: c# entity-framework


【解决方案1】:

这是可能的,虽然应该尽可能避免关系数据库表之间的循环关系,所以我会认真考虑“其他方法”。

通过 EF 术语,关系的 principal 是被引用的表,而 dependent 是通过 FK 引用主 PK 的表。

对于 RightAnswer 定义关系,Question 将通过 FK 引用 Answer,因此 Answer 是主体,Question 是依赖关系。

所以从这个角度来看,您的流畅配置是正确的(WithRequiredDependent 中的词 Dependent 适用于 正在配置的实体,在您的情况下为 Question - 来自 Entity&lt;Question&gt;)。

但是,如果结合其他一对多关系,则不是。为什么?因为循环依赖。 Answer 需要QuestionId FK,所以必须先创建Question 才能创建它。通过使RightAnswer 成为必需,如果不首先创建Question,就无法创建Question。这造成了无法解决的先有鸡还是先有蛋的问题。

因此RightQuestion 应该是可选的。这导致了另一个问题。如果我们将Answer 设为必需,EF 按照惯例会将其视为主体,这不是我们想要的。

因此,这种模型唯一可行的解​​决方案是使两端可选

modelBuilder.Entity<Question>()
    .HasOptional(p => p.RightAnswer)
    .WithOptionalDependent()
    .Map(m => m.MapKey("RightAnswerId"))
    .WillCascadeOnDelete(false);

请注意,最后两个 fluent API 调用是可选的,可以跳过。 Map 用于指定与本例中的常规 RightAnswer_AnswerId 不同的 FK 列名称。并且WillCascadeOnDelete(false) 可以跳过,因为按照惯例,级联删除对于可选 关系是关闭的。但明确不会受到伤害,尤其是在播放/修改模型时。

【讨论】:

  • 谢谢。我意识到它必须是可选的,因为它变成了先有鸡还是先有蛋的问题。另一种解决方案的问题是只有一个答案应该是正确的,并且拥有IsRight 字段可以让多个答案正确。另一方面,拥有RightAnswer 属性并不强制要求RightAnswer 实际上属于Question!这可能是一个更大的问题。我想其中一些东西只需要在我端而不是在数据库端强制执行。
  • 可选的RightAnswer 弹出的另一个问题是,在创建新问题(带有答案)时,您必须先创建所有这些问题,然后将其保存到数据库中,然后再设置RightAnswer 属性并再次保存。否则它无法确定先保存谁。
  • 确实如此。关系数据库无法通过其标准约束强制执行第一条规则。两次保存是第二次的唯一解决方案。而不是谈论删除。要删除Question,您需要先将其更新为将RightQuestionId 设置为null,然后将其删除:)
【解决方案2】:

对于One-to-zero/one Relationship

modelBuilder.Entity<Question>()
    .HasOptional(q => q.RightAnswer)
    .WithRequired(q => q.Question)
    .WillCascadeOnDelete(false);

对于One-to-one Relationship

modelBuilder.Entity<Question>()
    .HasRequired(q => q.RightAnswer)
    .WithRequiredPrincipal(q => q.Question)
    .WillCascadeOnDelete(false);

【讨论】:

  • 这并不能真正回答问题。添加q =&gt; q.Question 会导致EF 在Answers 中创建另一个不需要的列,这是不可取的。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2018-04-26
  • 2011-01-15
  • 2016-05-03
  • 1970-01-01
  • 2011-03-01
  • 2019-01-04
相关资源
最近更新 更多