ASP .NET Core 3.1 - Usando o Identity de cabo a rabo - XXII


Hoje vamos continuar a série de artigos mostrando como usar o ASP .NET Core Identiy na versão 3.1 da ASP .NET .Core e do EF Core.

Continuando a vigésima primeira parte do artigo vamos entender como funciona a exclusão em cascata e como impor a integridade referencial de restrição de chave estrangeira ON DELETE NO ACTION no EF Core.

Como funciona a restrição de integridade referencial em cascata

Já apresentamos o relacionamento entre as tabelas AspNetUsers, AspNetRoles e AspNetUserRoles :

Onde vimos que a tabela AspNetUserRoles possui 2 chaves estrangeiras (foreign Keys) : UserId e RoleId.

A restrição de integridade referencial em cascata permite definir as ações que o Microsoft SQL Server deve executar quando um usuário tenta excluir ou atualizar uma chave para a qual uma chave estrangeira existente aponta.

No Entity Framework Core, por padrão, as chaves estrangeiras na tabela AspNetUserRoles têm o comportamento Cascade DELETE.

Isso significa que, se um registro na tabela pai (AspNetRoles) for excluído, os registros correspondentes na tabela filha (AspNetUserRoles) serão automaticamente excluídos. Assim se eu excluir uma role da tabela AspNetRoles os registros relacionados a esta role serão removidos da tabela AspNetUserRoles.

Esse comportamento é definido por padrão quando da criação do relacionamento, e, podemos especificar algumas opções adicionais nos eventos ON UPDATE e ON DELETE que estão associados a alteração e exclusão de registros.

As opções são:

  1. CASCADE - Atualiza ou exclui os registros da tabela filha automaticamente, ao atualizar ou excluir um registro da tabela pai;
  2. SET NULL - Define como null o valor do campo na tabela filha, ao atualizar ou excluir o registro da tabela pai;
  3. NO ACTION - Nenhuma ação é realizada na tabela filha quando os dados da tabela pai são excluídos ou atualizados;
  4. SET DEFAULT - Os dados da tabela filha são definidos com seus valores padrão quando os dados da tabela pai são excluídos ou atualizados;

Podemos confirmar isso no SQL Server Management Studio e abrindo a tabela no modo Design verificando o relacionamento das chaves estrangeiras da tabela AspNetUserRoles:

Como podemos alterar esse comportamento ?

Vamos alterar esse comportamento para não permitir que uma role seja excluída se existirem linhas na tabela filha - AspNetUserRoles - que apontam para a role na tabela pai - AspNetRoles; ou seja, se uma role possuir usuários relacionados a ela não vamos poder excluir a role.

Podemos alterar esse comportamento via código modificando o comportamento de exclusão.

Os comportamentos de exclusão são definidos no tipo enumerador DeleteBehavior e pode ser passado para a API fluente OnDelete para controlar se a exclusão de uma entidade de pai ou o corte da relação com entidades dependentes/filho deve ter um efeito colateral nas entidades dependentes/filho.

Existem três ações que o EF Core pode executar quando uma entidade principal é excluída ou o relacionamento com o filho for rompido:

1- O filho/dependente pode ser excluído;
2- Os valores da chave estrangeira filha podem ser definidos como nulos;
3- O filho permanece inalterada;

Então podemos modificar o comportamento definindo nas chaves estrangeiras DeleteBehavior com o valor Restrict, e, isso pode ser feito no método OnModelCreating da classe de contexto do projeto, ou seja, a classe AppDbContext.

Abaixo temos o código alterado da classe AppDbContext onde no método OnModelCreating alteramos o comportamento definindo o valor Restrict.

 public class AppDbContext : IdentityDbContext<ApplicationUser>
 {
        public AppDbContext(DbContextOptions<AppDbContext> options)
      : base(options)
        { }
        public DbSet<Funcionario> Funcionarios { get; set; }
        protected override void OnModelCreating(ModelBuilder modelBuilder)
        {
            base.OnModelCreating(modelBuilder);
            foreach (var foreignKey in modelBuilder.Model.GetEntityTypes().SelectMany(e => e.GetForeignKeys()))
            {
                foreignKey.DeleteBehavior = DeleteBehavior.Restrict;
            }
        }
 }

Após isso podemos dar um build na solution e aplicar uma nova Migration e atualizar o banco de dados.

Add-Migration OnDeleteNoAction

Este comando vai gerar um script que vai aplicar a opção ReferentialAction.Restrict na ação onDelete.

Update-Database

Com essa alteração, se você visualizar as propriedades da chave estrangeira, verá que ON DELETE agora está definido como NO ACTION (Sem Ação):

Nesse momento, se você tentar excluir uma role da tabela AspNetRoles, para a qual existem linhas filhas na tabela AspNetUserRoles, será gerado um erro e a ação DELETE será revertida.

Você precisa excluir as linhas CHILD antes de excluir a linha pai, ou seja, você tem que excluir primeiro os usuários para depois remover a role.

Veja o erro gerado ao tentar excluir uma role agora:

No próximo artigo veremos como criar uma página de erros customizada.

Tu, pois, meu filho, fortifica-te na graça que há em Cristo Jesus.
2 Timóteo 2:1

Referências:


José Carlos Macoratti