Solucionado (ver solução)
Solucionado
(ver solução)
7
respostas

Adicionar cascade delete para todos os registros em um namespace

Boa Tarde, Seguinte, eu gostaria de configurar através do FluentAPI, um comportamento de apagar todas as entidades abaixo de uma classe primária, automaticamente, como eu posso fazer isso?

Por exemplo, um registro pai primordial tem vários filhos, e esses filhos, mais filhos, e vai descendo o nível.... Eu gostaria de que, quando eu apagasse o primeiro pai, todos os filhos fossem apagados recursivamente. É possível?

7 respostas

Oi, Bruno, bom dia. É possível sim usando o método OnDelete() disponível na configuração do relacionamento.

Exemplo:

modelBuilder.Entity<Book>()
        .HasOne(b => b.Author)
        .WithMany(a => a.Books)
        .IsRequired()
        .OnDelete(DeleteBehavior.Delete);

Uma observação importante é que todos os filhos devem estar sendo monitorados pelo Entity. Mais detalhes aqui e aqui.

Espero que tenha ajudado.

Abraços!

Olá Daniel, obrigado pela resposta, mas não funcionou. Vou tentar explicar melhor.

Digamos que eu tenha uma classe Biblioteca que possui uma lista "áreas", cada área possui uma lista de "corredores", e como pai a classe biblioteca, e cada corredor possui uma lista "fileira", e um pai "área", cada fileira, possui uma lista livros, e cada livro possui o pai "fileira". Entendeu, que o nível vai descendo?

Modelo Esquemático:

Biblioteca
-----> Area
----------> Corredor
---------------> Fileira
--------------------> Livro

Modelo com as classes:

class Biblioteca {
    public int Id {get;set;}
    public List<Area> Areas {get;set;}
}

class Area {
    public int Id {get;set;}
    public Biblioteca Biblioteca {get;set;}
    public List<Corredor> Corredores {get;set;}
}

class Corredor {
    public int Id {get;set;}
    public Area Area {get;set;}
    public List<Fileira> Fileiras {get;set;}
}

class Fileira {
    public int Id {get;set;}
    public Corredor Corredor {get;set;}
    public List<Livro> Livros {get;set;}
}

class Livro {
    public int Id {get;set;}
    public Fileira Fileira {get;set;}
    public string Nome {get;set;}
}

Eu gostaria de que quando apagasse Biblioteca, fosse apagando os filhos que fossem necessários dentro da cadeia, em cascata. Eu consegui fazer isso com uma "gambiarra", fiz que todas as minhas classes herdassem de uma classe em comum que chamei de "Registro", e que todas elas possuíssem uma propriedade em comum chamada "Biblioteca", e então configurei da seguinte forma:

modelBuilder.Model.GetEntityTypes()
    .Where(t => typeof(Registro).IsAssignableFrom(t.ClrType))
    .ToList()
    .ForEach(prop =>
    {
        modelBuilder
        .Entity(prop.Name)
        .HasOne(typeof(Biblioteca), "Biblioteca")
        .WithMany()
        .OnDelete(DeleteBehavior.Cascade);
    });

Eu testei com umas 20 classes e funcionou, na migration criada, todas as 20 entidades, possuem uma FK apontando para Blibioteca, chamada "BibliotecaId" e com a propriedade, "onDelete: ReferentialAction.Cascade", mas eu tenho uma estrutura com 650 classes, fica completamente inviável eu ficar herdando todas elas da classe Registro, creio que deva existir uma forma mais simples e eficiente, porém eu não sei como fazer...

Bruno, qdo vc diz que não funcionou significa que simplesmente não excluiu os filhos, certo?

Primeiro testa sem o Entity, criando as chaves estrangeiras em cada hierarquia com o cascade delete e em seguida deletando o registro-pai (a biblioteca). Se isso funcionar, é uma questão de configuração da migração do EF, que é nosso próximo passo.

Segundo, garanta que todas as classes descendentes são obrigatórias (usando o IsRequired()). Não testei aqui (ainda) mas acho que isso irá configurar automaticamente o ondelete para CascadeDelete. Gere a migração e em seguida verifique se de fato foram configuradas com o cascade correto. Outra maneira é configurando o OnDelete para cada descendente.

Terceiro (para esse passo é importante que os descendentes já estejam configurados com o CascadeDelete e aplicados no banco): quando recuperar um objeto do tipo biblioteca você precisa recuperar todos os objetos descendentes pelo EF Core usando os métodos Include() e ThenInclude(). Assim você vai garantir que toda a árvore está sendo monitorada pelo contexto.

Me dá um feedback se ajudou. Mais tarde vou simular essa situação com o seu código.

Abraços!

Daniel, sim, é possível configurar o cascade delete em cada nível, e funciona. O Problema é que eu tenho 650 niveis em um namespace e 990 em outro, fica inviável eu fica na mão, configurando tudo, eu queria tipo criar um loop que fosse configurando esse comportamento em todas as FK's das classes em um namespace. Eu consegui fazer isso, porem somente da forma que eu descrevi, colocando uma propriedade em todas as classes. mas ai eu caio no mesmo problema, a necessidade de ter que ir na mão abrindo todos os arquivos e adicionando a propriedade...

Eu testei o que falou do IsRequired(). Da seguinte forma:

modelBuilder.Entity<Corredor>().HasOne(c => c.Area).WithMany(c => c.Corredores).IsRequired();

Deu certo, mas ai eu caio no mesmo problema... Imagina configurar IsRequired() para todooooos as minhas classses....

O terceiro ponto que mencionou também é inviável, pois imagina que eu precise dar include, Then Include em 650 niveis... Ou existe uma forma que eu consiga dar em include em toda a cadeia?

Pensei aqui, não é o que eu queria mas ja ajuda, tem como configurar para que TODO E QUALQUER FK que por ventura seja criada na migration ja tenha o comportamento de Delete Cascade por default? porque o comportamento default é Restrict.

MUITO OBRIGADO PELO APOIO!!!

Bruno, agora entendi o problema! Achava que era porque o EF não estava fazendo o cascade delete corretamente. Então a questão é achar uma maneira mais eficiente de configurar isso em muitas classes. Seria legal se pudéssemos extrair essa configuração para uma classe com essa responsabilidade.

Daí pensei na interface IEntityTypeConfiguration<T>, conhece? Ela permite isolar a configuração em uma classe específica. E se você conseguisse aplicar a mesma configuração que estaria numa implementação dessa interface para todas as classes que quisesse aplicar o cascade delete?

Estou fazendo uns testes aqui, mas ainda estão inconclusivos. Mais tarde volto aqui com oq consegui extrair da experiência.

Daniel, eu não consegui entender como utilizar sua ideia de uma forma automática.

Eu encontrei uma forma na internet, funcionou, será que é a melhor maneira de fazer isso mesmo?

Veja:

 modelBuilder
    .Model
    .GetEntityTypes()
    .Where(p => p.ClrType.Namespace == "MEU_NAMESPACE")
    .ToList()
    .ForEach(prop => {
        prop.GetForeignKeys()
        .Where(fk => !fk.IsOwnership && fk.DeleteBehavior != DeleteBehavior.Cascade)
        .ToList()
        .ForEach(fk => fk.DeleteBehavior = DeleteBehavior.Cascade);
    });
solução!

Bruno, o código seria parecido com o que vc achou.

A única distinção seria onde colocá-lo. Em vez de ficar direto no OnModelCreating(), ele ficaria em uma classe que implementasse IEntityTypeConfiguration<T>. Mas daí teríamos que criar uma classe base para todas as entidades e caíria no problema de escrever código para as 650 classes novamente.

Essa maneira que você encontrou é a mais próxima do que você está buscando: um código único para configurar o cascade delete das entidades.

Parabéns pela determinação!

Abraços.