【问题标题】:How to delete row with 1:1 relation to the same table?如何删除与同一张表具有 1:1 关系的行?
【发布时间】:2018-08-03 00:57:50
【问题描述】:

我使用 Entity Framework Core,并且我有一个表:

public class BlogComment
{
    public int Id { get; set; }
    public BlogPost Post { get; set; }
    [StringLength(100)]
    public string AuthorName { get; set; }
    [StringLength(254)]
    public string AuthorEmail { get; set; }
    public bool SendMailOnReply { get; set; }
    [StringLength(2000)]
    public string Content { get; set; }
    public DateTime CreatedTime { get; set; }
    public int? ReplyToId { get; set; }
    public BlogComment ReplyTo { get; set; }
}

据此,EFC 生成下表:

CREATE TABLE [dbo].[BlogComment] (
    [Id]              INT             IDENTITY (1, 1) NOT NULL,
    [AuthorEmail]     NVARCHAR (254)  NULL,
    [AuthorName]      NVARCHAR (100)  NULL,
    [Content]         NVARCHAR (2000) NULL,
    [CreatedTime]     DATETIME2 (7)   NOT NULL,
    [PostId]          INT             NULL,
    [ReplyToId]       INT             NULL,
    [SendMailOnReply] BIT             NOT NULL,
    CONSTRAINT [PK_BlogComment] PRIMARY KEY CLUSTERED ([Id] ASC),
    CONSTRAINT [FK_BlogComment_BlogPost_PostId] FOREIGN KEY ([PostId]) REFERENCES [dbo].[BlogPost] ([Id]),
    CONSTRAINT [FK_BlogComment_BlogComment_ReplyToId] FOREIGN KEY ([ReplyToId]) REFERENCES [dbo].[BlogComment] ([Id])
);
GO
CREATE NONCLUSTERED INDEX [IX_BlogComment_PostId]
    ON [dbo].[BlogComment]([PostId] ASC);
GO
CREATE UNIQUE NONCLUSTERED INDEX [IX_BlogComment_ReplyToId]
    ON [dbo].[BlogComment]([ReplyToId] ASC) WHERE ([ReplyToId] IS NOT NULL);

一些 cmets 作为回复发送给另一个,但不是全部。删除原评论后,回复变为普通评论。所以,在this tutorial之后,配置看起来是这样的:

modelBuilder.Entity<BlogComment>()
      .HasOne(p => p.ReplyTo)
      .WithOne()
      .HasForeignKey<BlogComment>(c => c.ReplyToId)
      .IsRequired(false)
      .OnDelete(DeleteBehavior.SetNull);

删除方法很简单:

var comment = await context.BlogComment.Include(c => c.ReplyTo).SingleAsync(m => m.Id == id);
context.BlogComment.Remove(comment);
await context.SaveChangesAsync();

但是我不能运行它,我得到一个错误:

System.Data.SqlClient.SqlException:DELETE 语句与 SAME TABLE REFERENCE 约束“FK_BlogComment_BlogComment_ReplyToId”冲突。

我该如何解决这个问题?

【问题讨论】:

  • 从您的代码中删除 .Include(c =&gt; c.ReplyTo)
  • @viveknuna 起初我没有包含它,现在我删除了,但我得到了同样的错误。
  • 你能检查一下ReplyToId在数据库表中是否可以为空吗?
  • @viveknuna 是的。我将表格的代码添加到问题中。该表现在有几个 cmets 不是对另一个的回复,即 ReplyToId 为空。
  • 对不起,我不知道。也许您可以在保存更改之前尝试comment.ReplyTo = null;

标签: entity-framework entity-framework-core cascade one-to-one


【解决方案1】:

在 cmets 中结束对话:

首先,自引用是一个 1:n 关联:

modelBuilder.Entity<BlogComment>()
      .HasOne(p => p.ReplyTo)
      .WithMany(c => c.Replies)
      .HasForeignKey(c => c.ReplyToId)
      .IsRequired(false)
      .OnDelete(<we'll get to that>);

所以,为了方便起见,BlogComment 现在也有一个属性

public ICollection<BlogComment> Replies { get; set; }

但是,我无法使用

.OnDelete(DeleteBehavior.SetNull);

它给了我

在表“BlogComments”上引入 FOREIGN KEY 约束“FK_BlogComments_BlogComments_ReplyToId”可能会导致循环或多个级联路径。

这是一个我们只能接受的 Sql Server 限制,无法回避。获得所需级联行为的唯一方法是

.OnDelete(DeleteBehavior.ClientSetNull);

Which is:

对于由 DbContext 跟踪的实体,依赖实体中的外键属性的值设置为 null。这有助于在跟踪实体图时将它们保持在一致状态,以便可以将完全一致的图写入数据库。 (...) 这是可选关系的默认设置。

即:客户端执行 SQL 以使外键值无效。但是应该跟踪子记录。要删除 BlogComment 父级,删除操作应如下所示:

using (var db = new MyContext(connectionString))
{
    var c1 = db.BlogComments
        .Include(c => c.Replies) // Children should be included
        .SingleOrDefault(c => c.Id == 1);
    db.BlogComments.Remove(c1);
    db.SaveChanges();
}

如您所见,您不必设置 ReplyToId = null,这是 EF 负责的事情。

【讨论】:

  • 然而,这不是 1:1,您也可以针对原始问题描述这种情况。虽然它对我有用,但谢谢。
  • 1:0..1 的情况在 EF-core 中并没有什么不同。表结构可以相同(即单独的外键,而不是 EF6 规定的共享主键关系)。应用相同的级联限制,应跟踪父母和孩子以使 DeleteBehavior.ClientSetNull 工作。
  • 这确实是正确答案。与一对一的唯一区别是后一种情况下的唯一约束。但最重要的部分是ClientSetNull 和反向导航属性。在一对一的情况下,将Replies替换为public BlogComment Reply { get; set; }WithOne(c =&gt; c.Reply)Include(c -&gt; c.Reply)
【解决方案2】:

对我来说,当我删除一个实体时,我必须Include() 我需要“处理”的实体。 EF 无法管理它当前未跟踪的内容。

var breedToDelete = context.Breed
    .Include(x => x.Cats)
    .Single(x => x.Id == testBreedId);

context.Breed.Remove(breedToDelete);
context.SaveChanges();

【讨论】:

    【解决方案3】:

    我可以通过手动将ReplyTo 设置为 null 来使其工作。我仍在寻找更好的解决方案,或者解释为什么需要它。这不是OnDelete(DeleteBehavior.SetNull)应该做的吗?

    var comment = await context.BlogComment.Include(c => c.ReplyTo).SingleAsync(m => m.Id == id);
    var reply = await context.BlogComment.SingleOrDefaultAsync(m => m.ReplyToId == id);
    if (reply != null)
    {
        reply.ReplyTo = null;
        reply.ReplyToId = null;
        context.Entry(reply).State = EntityState.Modified;
    }
    context.BlogComment.Remove(comment);
    

    【讨论】:

      猜你喜欢
      • 2020-09-16
      • 1970-01-01
      • 1970-01-01
      • 2019-02-10
      • 2019-02-07
      • 2016-01-26
      • 1970-01-01
      • 2016-09-21
      • 2019-12-25
      相关资源
      最近更新 更多