Nota
O acesso a esta página requer autorização. Podes tentar iniciar sessão ou mudar de diretório.
O acesso a esta página requer autorização. Podes tentar mudar de diretório.
Os tipos de referência anuláveis (NRT) do C# permitem que os tipos de referência sejam anotados, indicando se é válido para eles conterem null ou não. Se você é novo nesse recurso, é recomendável que você se familiarize com ele lendo os documentos em C#. Os tipos de referência anuláveis são habilitados por padrão em novos modelos de projeto, mas permanecem desabilitados em projetos existentes, a menos que explicitamente aceites.
Esta página apresenta o suporte do EF Core para tipos de referência anuláveis e descreve as práticas recomendadas para trabalhar com eles.
Propriedades obrigatórias e opcionais
A documentação principal sobre propriedades obrigatórias e opcionais e sua interação com tipos de referência anuláveis é a página Propriedades obrigatórias e opcionais . Recomenda-se que comece por ler primeiro essa página.
Observação
Tenha cuidado ao habilitar tipos de referência anuláveis em um projeto existente: as propriedades de tipo de referência que antes eram configuradas como opcionais agora serão configuradas conforme necessário, a menos que sejam explicitamente anotadas para serem anuláveis. Ao gerenciar um esquema de banco de dados relacional, isso pode fazer com que migrações sejam geradas que alteram a anulabilidade da coluna de banco de dados.
Propriedades não anuláveis e inicialização
Quando os tipos de referência anuláveis estão ativados, o compilador C# emite avisos para qualquer propriedade não anulável não inicializada, pois esta conteria null. Como resultado, a seguinte maneira comum de escrever tipos de entidade não pode ser usada:
public class Customer
{
public int Id { get; set; }
// Generates CS8618, uninitialized non-nullable property:
public string Name { get; set; }
}
Se você estiver usando C# 11 ou superior, os membros necessários fornecem a solução perfeita para esse problema:
public required string Name { get; set; }
O compilador agora garante que, quando o seu código instancia um Cliente, ele sempre inicializa a sua propriedade Nome. E como a coluna do banco de dados mapeada para a propriedade não é anulável, todas as instâncias carregadas pelo EF sempre contêm um Nome não nulo também.
Se você estiver usando uma versão mais antiga do C#, a vinculação do construtor é uma técnica alternativa para garantir que suas propriedades não anuláveis sejam inicializadas:
public class CustomerWithConstructorBinding
{
public int Id { get; set; }
public string Name { get; set; }
public CustomerWithConstructorBinding(string name)
{
Name = name;
}
}
Infelizmente, em alguns cenários, a vinculação do construtor não é uma opção; as propriedades de navegação, por exemplo, não podem ser inicializadas dessa maneira. Nesses casos, você pode simplesmente inicializar a propriedade para null com a ajuda do operador null-forgiving (mas veja abaixo para obter mais detalhes):
public Product Product { get; set; } = null!;
Propriedades de navegação necessárias
As propriedades de navegação necessárias apresentam uma dificuldade adicional: embora um dependente sempre exista para um determinado principal, ele pode ou não ser carregado por uma consulta específica, dependendo das necessidades nesse ponto do programa (veja os diferentes padrões para carregar dados). Ao mesmo tempo, pode ser indesejável tornar essas propriedades nulas, uma vez que isso forçaria todos os acessos a elas a verificarem null, mesmo quando se sabe que a navegação está carregada e, portanto, não pode ser null.
Isso não é necessariamente um problema! Contanto que um dependente necessário seja carregado corretamente (por exemplo, via Include), o acesso à sua propriedade de navegação garante sempre retornar um valor não-nulo. Por outro lado, o aplicativo pode optar por verificar se a relação está carregada ou não, verificando se a navegação é null. Nesses casos, é razoável permitir que a navegação seja anulável. Isto significa que as navegações requeridas do dependente para o principal:
- Deve ser não anulável se for considerado um erro do programador aceder a uma navegação quando esta não está carregada.
- Deve ser anulável se for aceitável que o código da aplicação verifique a navegação para determinar se a relação está ou não carregada.
Se quiser uma abordagem mais rigorosa, pode ter uma propriedade não anulável com um campo de apoio anulável.
private Address? _shippingAddress;
public Address ShippingAddress
{
set => _shippingAddress = value;
get => _shippingAddress
?? throw new InvalidOperationException("Uninitialized property: " + nameof(ShippingAddress));
}
Desde que a navegação esteja devidamente carregada, 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, uma vez que o contrato de API foi usado incorretamente.
Observação
As navegações de coleção, que contêm referências a várias entidades relacionadas, devem ser sempre não anuláveis. Uma coleção vazia significa que não existem entidades relacionadas, mas a lista em si nunca deve ser null.
DbContext e DbSet
Com o EF, é prática comum ter propriedades DbSet não inicializadas em tipos de contexto:
public class MyContext : DbContext
{
public DbSet<Customer> Customers { get; set;}
}
Embora isso geralmente cause um aviso do compilador, o EF Core 7.0 e superior suprimem esse aviso, já que o EF inicializa automaticamente essas propriedades por meio de reflexão.
Na versão mais antiga do EF Core, você pode contornar esse problema da seguinte maneira:
public class MyContext : DbContext
{
public DbSet<Customer> Customers => Set<Customer>();
}
Outra estratégia é usar auto-propriedades não anuláveis, mas inicializá-las para null, usando o operador null-forgiving (!) para silenciar o aviso do compilador. O construtor base DbContext garante que todas as propriedades DbSet serão inicializadas e null nunca será observado nelas.
Navegando e incluindo relações anuláveis
Ao lidar com relações opcionais, é possível encontrar avisos do compilador onde uma exceção de referência real null 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çar. No entanto, o compilador não está ciente dessa garantia 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 null-forgiving (!) para informar ao compilador que um valor real null não é possível:
var order = await context.Orders
.Where(o => o.OptionalInfo!.SomeProperty == "foo")
.ToListAsync();
Um problema semelhante ocorre ao incluir vários níveis de relacionamentos em navegações opcionais:
var order = await context.Orders
.Include(o => o.OptionalInfo!)
.ThenInclude(op => op.ExtraAdditionalInfo)
.SingleAsync();
Se você estiver fazendo isso muito, e os tipos de entidade em questão forem predominantemente (ou exclusivamente) usados em consultas 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, mantendo a relação opcional; no entanto, se suas entidades forem percorridas fora do EF Core, você poderá observar null valores, mesmo que as propriedades sejam anotadas como não anuláveis.
Limitações em versões mais antigas
Antes do EF Core 6.0, as seguintes limitações se aplicavam:
- A superfície da API pública não estava anotada quanto à presença de valores nulos (a API pública ignorava nulidade), o que por vezes a torna difícil de utilizar quando o recurso NRT está ativado. Isso inclui notavelmente os operadores LINQ assíncronos expostos pelo EF Core, como FirstOrDefaultAsync. A API pública está totalmente equipada com anotações para anulabilidade a partir da versão EF Core 6.0.
- A engenharia reversa não suportava tipos de referência anuláveis (NRTs) C# 8: o EF Core sempre gerava código C# que presumia que o recurso estava desativado. Por exemplo, colunas de texto que aceitam valores nulos foram geradas como uma propriedade com o tipo
string, nãostring?, utilizando a API Fluent ou Anotações de Dados para definir se uma propriedade é obrigatória ou não. Se estiver usando uma versão mais antiga do EF Core, você ainda poderá editar o código do scaffolded e substituí-los por anotações de anulabilidade em C#.