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:
Se os tipos de
referência anuláveis estiverem desabilitados (o padrão), todas as
propriedades com tipos de referência serão configuradas como opcionais
por convenção (por exemplo, string).
Se os tipos de referência anuláveis estiverem habilitados, as propriedades serão configuradas com base na nulidade do C# de seu tipo: string? será configurado como opcional, mas a string será configurada como requerida;
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 |
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; |
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
Referências: