Введение ограничения FOREIGN KEY 'X' в таблице 'Y' может вызвать циклы или несколько каскадных путей - PullRequest
0 голосов
/ 02 августа 2020

Я знаю, что есть несколько вопросов относительно этого сообщения об ошибке, но я не нашел ничего, что могло бы мне помочь. У меня такая ситуация:

Модель пользователя

public class User : IdentityUser<int>
{
    ......
    public virtual ICollection<Like> Likers { get; set; }
    public virtual ICollection<Like> Likees { get; set; }
    public virtual ICollection<Message> MessagesSent { get; set; }
    public virtual ICollection<Message> MessagesReceived { get; set; }
}

Как модель

public class Like
{
    public int LikerId { get; set; }
    public int LikeeId { get; set; }
    public virtual User Liker { get; set; }
    public virtual User Likee { get; set; }
}

Модель сообщения

public class Message
{
    public int Id { get; set; }
    public int SenderId { get; set; }
    public virtual User Sender { get; set; }
    public int RecipientId { get; set; }
    public virtual User Recipient { get; set; }
    .....
}

В DataContext

.....
builder.Entity<Like>()
    .HasKey(k => new { k.LikerId, k.LikeeId });

builder.Entity<Like>()
    .HasOne(u => u.Likee)
    .WithMany(u => u.Likers)
    .HasForeignKey(u => u.LikeeId)
    .OnDelete(DeleteBehavior.Cascade);

builder.Entity<Like>()
    .HasOne(u => u.Liker)
    .WithMany(u => u.Likees)
    .HasForeignKey(u => u.LikerId)
    .OnDelete(DeleteBehavior.Cascade);

builder.Entity<Message>()
    .HasOne(u => u.Sender)
    .WithMany(u => u.MessagesSent)
    .OnDelete(DeleteBehavior.Cascade);


builder.Entity<Message>()
    .HasOne(u => u.Recipient)
    .WithMany(u => u.MessagesReceived)
    .OnDelete(DeleteBehavior.Cascade);
.....

Если у меня есть .OnDelete(DeleteBehavior.Cascade);, я получаю это сообщение об ошибке, когда пытаюсь применить миграцию: An error occured during migration Introducing FOREIGN KEY constraint 'FK_Likes_AspNetUsers_LikerId' on table 'Likes' may cause cycles or multiple cascade paths. Specify ON DELETE NO ACTION or ON UPDATE NO ACTION, or modify other FOREIGN KEY constraints. Одна и та же ошибка для таблиц лайков и сообщений .

Решением будет добавить .OnDelete(DeleteBehavior.Restrict);, но если я хочу добавить опцию в приложении к удалить пользователя, я не могу этого сделать, если пользователю кто-то понравился или он отправил сообщение. Сначала мне нужно будет вручную удалить все его лайки и его сообщения, а затем выполнить удаление пользователя, поэтому я хочу сделать это автоматически с помощью Cascade Delete.

Это сообщение об ошибке появляется только тогда, когда я использую Microsoft SQL Сервер, он работает с SQLite.

Изменить: В качестве обходного пути я выбрал то, что предлагал @lauxjpn, и изменил его с .OnDelete(DeleteBehavior.Delete); на .OnDelete(DeleteBehavior.Restrict); и создал БД Триггер для обработки удаления дочерних записей (лайков и сообщений) перед удалением родительской. Я протестировал этот подход, и он работает. Сначала я вошел в систему с новым пользователем, кому-то понравился и отправил сообщение, а затем удалил свою учетную запись. Об остальном позаботился триггер, удалил записи из понравившихся и сообщений, а затем пользователя из AspNetUsers.

Это триггер:

CREATE TRIGGER [DELETE_User]
   ON [dbo].[AspNetUsers]
   INSTEAD OF DELETE
AS 
BEGIN
 SET NOCOUNT ON;
 DELETE FROM [dbo].[Likes] WHERE LikerId IN (SELECT Id FROM DELETED)
 DELETE FROM [dbo].[Likes] WHERE LikeeId IN (SELECT Id FROM DELETED)
 DELETE FROM [dbo].[Messages] WHERE SenderId IN (SELECT Id FROM DELETED)
 DELETE FROM [dbo].[Messages] WHERE RecipientId IN (SELECT Id FROM DELETED)
 DELETE FROM [dbo].[AspNetUsers] WHERE Id IN (SELECT Id FROM DELETED)
END

Edit 2: (Может кому еще это понадобится) Триггер также можно добавить в миграцию:

public partial class AddedInstedOfTrigger : Migration
{
    protected override void Up(MigrationBuilder migrationBuilder)
    {
        migrationBuilder.Sql(@"
        CREATE OR ALTER TRIGGER [DELETE_User]
          ON [dbo].[AspNetUsers]
          INSTEAD OF DELETE
        AS
        BEGIN
          SET NOCOUNT ON;
          DELETE FROM [dbo].[Likes] WHERE LikerId IN (SELECT Id FROM DELETED)
          DELETE FROM [dbo].[Likes] WHERE LikeeId IN (SELECT Id FROM DELETED)
          DELETE FROM [dbo].[Messages] WHERE SenderId IN (SELECT Id FROM DELETED)
          DELETE FROM [dbo].[Messages] WHERE RecipientId IN (SELECT Id FROM DELETED)
          DELETE FROM [dbo].[AspNetUsers] WHERE Id IN (SELECT Id FROM DELETED)
        END");
    }

    protected override void Down(MigrationBuilder migrationBuilder)
    {
        migrationBuilder.Sql(@"DROP TRIGGER [DELETE_User]");
    }
}

1 Ответ

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

Недавно я дал ответ на тот же вопрос здесь, на SO:

Вам не нужно отбрасывать какие-либо внешние ключи. Просто используйте OnDelete(DeleteBehavior), чтобы явно указать, какой тип каскадного поведения вам нужен.

Например, следующее приведет к успешному созданию вашей модели, но для вашего реального приложения вам нужно решить самостоятельно, где и как к разорвать каскад :

builder.Entity<PhotoDevice>()
    .HasOne(bc => bc.Photo)
    .WithMany(b => b.PhotoDevices)
    .HasForeignKey(bc => bc.PhotoRef)
    .OnDelete(DeleteBehavior.Restrict); // <-- define cascading behavior

Для получения дополнительной информации см. Взаимосвязи: Каскадное удаление и Каскадное удаление .

Это не ядро ​​EF, а SQL ограничение сервера (поэтому исключение также выдается SQL сервером).

Далее ресурсы, которые имеют дело с этим ограничением и показывают, как обойти его, используя триггер INSTEAD OF, если разрыв каскада не является вариантом, с которым вы можете жить:

...