Как удалить строку с отношением 1: 1 к той же таблице? - PullRequest
0 голосов
/ 13 мая 2018

Я использую 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);

Некоторые комментарии отправляются в ответ надругое, но не все.Когда исходный комментарий удаляется, ответ становится обычным комментарием.Итак, следуя этому руководству , конфигурация выглядит так:

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".

Как исправитьэто?

Ответы [ 3 ]

0 голосов
/ 13 мая 2018

Чтобы завершить разговор в комментариях:

Во-первых, ссылка на себя - это связь 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);

Это дает мне

Введение ограничения FOREIGN KEY 'FK_BlogComments_BlogComments_ReplyToId' в таблицу 'BlogComments' может привести к возникновению циклов или нескольких каскадных путей.

Это ограничение сервера Sql, которое мы просто должны принять, и никак не обойти его. Единственный способ получить желаемое каскадное поведение -

.OnDelete(DeleteBehavior.ClientSetNull);

Что :

Для сущностей, отслеживаемых DbContext, значения свойств внешнего ключа в зависимых сущностях устанавливаются в нуль. Это помогает поддерживать граф сущностей в согласованном состоянии, пока они отслеживаются, так что полностью согласованный граф можно затем записать в базу данных. (...) Это значение по умолчанию для необязательных отношений.

Т.е.: клиент выполняет 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.

0 голосов
/ 02 августа 2018

Для меня я должен был Include() сущности, с которыми мне нужно было иметь дело, когда я удалял сущность.EF не может управлять вещами, которые в данный момент не отслеживаются.

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

context.Breed.Remove(breedToDelete);
context.SaveChanges();
0 голосов
/ 13 мая 2018

Я мог бы заставить его работать, вручную установив ReplyTo в ноль. Я все еще ищу лучшее решение или объяснение, зачем оно нужно. Разве это не то, что 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);
...