EF Core - Tratando tipos de referência anuláveis
 Hoje vamos mostrar como tratar os tipos de referência anuláveis no EF Core.


A partir do C# 8  foram introduzidos os tipos de referência anuláveis ou nullable reference types, permitindo que os tipos de referência sejam anotados, indicando se é válido para eles conterem nulo ou não.

 



Podemos agora usar este recuso para minimizar a probabilidade de que o código faça com que o runtime lançe uma exceptiondo tipo System.NullReferenceException.
 

Os tipos de referência anuláveis incluem três recursos que ajudam você a evitar essas exceções, incluindo a capacidade de marcar explicitamente um tipo de referência como anulável ou nullable :

EF Core  - Propriedades obrigatórias e opcionais

Uma propriedade é considerada opcional se for válida para ela conter null. Se null não for um valor válido a ser atribuído a uma propriedade, ela será considerada uma propriedade obrigatória ou requerida.

 

Ao mapear para um esquema de banco de dados relacional, as propriedades obrigatórias são criadas como colunas não anuláveis (non-nullable) ​​e as propriedades opcionais são criadas como colunas anuláveis.(nullable)

 

Por convenção, uma propriedade cujo tipo pode conter null será configurada como opcional, enquanto as propriedades cujo tipo não pode conter null serão configuradas como obrigatórias ou requeridas.

 

Por exemplo, todas as propriedades com tipos de valor(int, decimal, bool, etc.) são configuradas como requeridas ou obrigatórias e todas as propriedades com tipos de valor anuláveis ​​(int?, decimal?, bool?, etc.) são configuradas como opcionais.

 

Esse recurso é desabilitado por padrão e, se habilitado, modifica o comportamento do EF Core da seguinte maneira:

A seguir temos um exemplo  que  mostra um tipo de entidade com propriedades obrigatórias e opcionais, com o recurso de referência anulável desabilitado (o padrão).
 

public class Aluno_Sem_NullableReferenceTypes
{
    public int Id { get; set; }
    [Required]                                  // Data annotations necessário para configurar como requerida
    public string Nome { get; set; }
    [Required]
    public string Email { get; set; }        // Data annotations necessário para configurar como requerida
    public string Endereco { get; set; }   // Opcional por convenção
}

 

No exemplo abaixo temos um tipo de entidade com propriedades obrigatórias e opcionais, com o recurso de referência anulável habilitado:
 

public class Aluno
{
    public int Id { get; set; }
    public string Nome { get; set; }        // Requerida por convenção
    public string Email { get; set; }         // Requerida por convenção
    public string? Endereco { get; set; }  // Opcional por convenção

    public Aluno(string nome, string email, string? endereco = null)
    {
        Nome = nome;
        Email = email;
        Endereco = endereco;
    }
}


O uso de tipos de referência anuláveis é recomendado, pois ele flui a nulidade expressa no código C# para o modelo e o banco de dados do EF Core e remove o uso da API Fluent ou Data Annotations para expressar o mesmo conceito duas vezes.

 

Tratamento do DbContext e DbSet


Quando os tipos de referência anuláveis são habilitados, o compilador C# emite avisos para qualquer propriedade não anulável não inicializada, pois conteria null.

 

Como resultado, a prática comum de ter propriedades DbSet não inicializadas em um tipo de contexto agora gerará um aviso. Para corrigir isso, precisamos tornar suas propriedades DbSet somente leitura e inicializá-las da seguinte forma:

 

public class NullableReferenceTypesContext : DbContext
{
    public DbSet<Aluno> Alunos => Set<Aluno>();
    public DbSet<Curso> Cursos => Set<Curso>();
}


Propriedades e inicialização não anuláveis

 

Os avisos do compilador para tipos de referência não anuláveis não inicializados também são um problema para propriedades regulares em seus tipos de entidade.

 

Evitamos esses avisos usando a vinculação de construtor, um recurso que funciona perfeitamente com propriedades não anuláveis, garantindo que sejam sempre inicializadas.

 

No entanto, em alguns cenários, a associação de construtor não é uma opção, por exemplo, as propriedades de navegação não podem ser inicializadas dessa maneira.

 

As propriedades de navegação necessárias apresentam uma dificuldade adicional, embora sempre exista um dependente para um determinado principal, ele pode ou não ser carregado por uma consulta específica, dependendo das necessidades naquele ponto do programa.

 

Ao mesmo tempo, é indesejável tornar essas propriedades anuláveis, pois isso forçaria todos os acessos a elas a verificar se são nulas, mesmo que sejam necessárias.

 

Uma maneira de lidar com esses cenários é ter uma propriedade não anulável com um campo de apoio anulável.

 

...

private Endereco? _endereco;

public Endereco Endereco
{
    set => _endereco = value;
    get => _endereco ?? throw new InvalidOperationException("Propriedade não inicializada: " + nameof(Endereco));
}


A propriedade de navegação não é anulável, a navegação obrigatória está configurada e desde que a navegação seja carregada corretamente, o dependente estará acessível através da propriedade.

 

Se, no entanto, a propriedade for acessada sem primeiro carregar corretamente a entidade relacionada, um InvalidOperationException será lançado.

 

O EF deve ser configurado para sempre acessar o campo de apoio e não a propriedade, pois depende de poder ler o valor mesmo quando não definido.

 

Como alternativa mais conscisa, é possível simplesmente inicializar a propriedade como null com a ajuda do operador null-forgiving (!):

 

public Endereco Endereco { get; set; } = null!;

 

O operador ! é o operador que perdoa nulos ou supressor de nulos. Em um contexto de anotação anulável habilitado, você usa este operador para declarar que a expressão x de um tipo de referência não é nula:  x!.
 

No entanto este operador não tem efeito em tempo de execução. Ele afeta apenas a análise de fluxo estático do compilador alterando o estado nulo da expressão. Em tempo de execução, a expressão x! avalia o resultado da expressão subjacente x.

 

A navegação e inclusão de relacionamentos anuláveis

 

Ao lidar com relacionamentos opcionais, é possível encontrar avisos do compilador onde uma exceção de referência nula real seria impossível.

 

Ao traduzir e executar suas consultas LINQ, o EF Core garante que, se uma entidade relacionada opcional não existir, qualquer navegação para ela será simplesmente ignorada, em vez de lançada.

 

No entanto, o compilador não tem conhecimento dessa garantia do EF Core e produz avisos como se a consulta LINQ fosse executada na memória, com LINQ to Objects.

 

Como resultado, é necessário usar o operador de tolerância a nulos (!) para informar ao compilador que um valor nulo real não é possível.

 

Console.WriteLine(aluno.InfoOpcional1!.InfoOpcional2!.InfoOpcional3);

 

Um problema semelhante ocorre ao incluir vários níveis de relacionamentos em navegações opcionais.

 

var aluno  = context.Alunos
       .Include(o => o.
InfoOpcional1
!)
           .ThenInclude(op => op.
InfoOpcional2).FirstOrDefault();

 

Se você estiver fazendo muito isso e os tipos de entidade em questão forem predominantemente (ou exclusivamente) usados ​​em consultas do EF Core, considere tornar as propriedades de navegação não anuláveis ​​e configurá-las como opcionais por meio da API Fluent ou Anotações de Dados.
 

Isso removerá todos os avisos do compilador enquanto mantém o relacionamento opcional; no entanto, se suas entidades forem percorridas fora do EF Core, você poderá observar valores nulos, embora as propriedades sejam anotadas como não anuláveis.


Assim temos o mesmo resultado usando a view que incluímos no banco de dados usando o EF Core.

 

Estamos conversados....

 

"Sei estar abatido, e sei também ter abundância; em toda a maneira, e em todas as coisas estou instruído, tanto a ter fartura, como a ter fome; tanto a ter abundância, como a padecer necessidade.
Posso todas as coisas em Cristo que me fortalece."

Filipenses 4:12,13

 

Porque um menino nos nasceu, um filho se nos deu, e o principado está sobre os seus ombros, e se chamará o seu nome: Maravilhoso, Conselheiro, Deus Forte, Pai da Eternidade, Príncipe da Paz.

Isaías 9:6
Porque um menino nos nasceu, um filho se nos deu, e o principado está sobre os seus ombros, e se chamará o seu nome: Maravilhoso, Conselheiro, Deus Forte, Pai da Eternidade, Príncipe da Paz.

Isaías 9:6

Referências:


José Carlos Macoratti