Chaves estrangeiras e principais em relações

Todas as relações de um para um e um para muitos são definidas por uma chave estrangeira no lado dependente que faz referência a uma chave primária ou alternativa no lado principal. Para conveniência, essa chave primária ou alternativa é conhecida como a "chave principal" da relação. Relações de muitos para muitos são compostas por duas relações de um para muitos, cada uma delas definida por uma chave estrangeira referenciando uma chave principal.

Dica

O código abaixo pode ser encontrado em ForeignAndPrincipalKeys.cs.

Chaves estrangeiras

A propriedade ou as propriedades que compõem a chave estrangeira geralmente são descobertas por convenção. As propriedades também podem ser configuradas explicitamente usando atributos de mapeamento ou com HasForeignKey na API de criação de modelos. HasForeignKey pode ser usado com uma expressão lambda. Por exemplo, para uma chave estrangeira composta por uma única propriedade:

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder.Entity<Blog>()
        .HasMany(e => e.Posts)
        .WithOne(e => e.Blog)
        .HasForeignKey(e => e.ContainingBlogId);
}

Ou, para uma chave estrangeira composta por mais de uma propriedade:

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder.Entity<Blog>()
        .HasMany(e => e.Posts)
        .WithOne(e => e.Blog)
        .HasForeignKey(e => new { e.ContainingBlogId1, e.ContainingBlogId2 });
}

Dica

O uso de expressões lambda na API de criação de modelos garante que o uso da propriedade esteja disponível para análise e refatoração de código e também forneça o tipo de propriedade à API para uso em outros métodos encadeados.

Também pode ser passado o nome da propriedade de chave estrangeira como uma cadeia de caracteres para HasForeignKey. Por exemplo, para uma única propriedade:

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder.Entity<Blog>()
        .HasMany(e => e.Posts)
        .WithOne(e => e.Blog)
        .HasForeignKey("ContainingBlogId");
}

Ou, para uma chave estrangeira composta:

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder.Entity<Blog>()
        .HasMany(e => e.Posts)
        .WithOne(e => e.Blog)
        .HasForeignKey("ContainingBlogId1", "ContainingBlogId2");
}

Usar uma cadeia de caracteres é útil quando:

  • A propriedade ou as propriedades são privadas.
  • A propriedade ou as propriedades não existem no tipo de entidade e devem ser criadas como propriedades de sombra.
  • O nome da propriedade é calculado ou construído com base em alguma entrada para o processo de criação do modelo.

Colunas de chave estrangeira não anuláveis

Conforme descrito em Relações opcionais e necessárias, a nulidade da propriedade de chave estrangeira determina se uma relação é opcional ou necessária. No entanto, uma propriedade de chave estrangeira anulável pode ser usada para uma relação necessária usando o atributo [Required], ou chamando IsRequired na API de criação de modelo. Por exemplo:

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder.Entity<Blog>()
        .HasMany(e => e.Posts)
        .WithOne(e => e.Blog)
        .HasForeignKey(e => e.BlogId)
        .IsRequired();
}

Ou, se a chave estrangeira for descoberta por convenção, IsRequired poderá ser usado sem uma chamada para HasForeignKey:

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder.Entity<Blog>()
        .HasMany(e => e.Posts)
        .WithOne(e => e.Blog)
        .IsRequired();
}

O resultado final disso é que a coluna de chave estrangeira no banco de dados é tornada não anulável mesmo que a propriedade de chave estrangeira seja anulável. A mesma coisa pode ser obtida configurando explicitamente a própria propriedade de chave estrangeira, conforme necessário. Por exemplo:

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder.Entity<Post>()
        .Property(e => e.BlogId)
        .IsRequired();
}

Chaves estrangeiras de sombra

As propriedades de chave estrangeira podem ser criadas como propriedades de sombra. Existe uma propriedade de sombra no modelo EF, mas não existe no tipo .NET. O EF controla internamente o valor e o estado da propriedade.

Chaves estrangeiras de sombra geralmente são usadas quando há um desejo de ocultar o conceito relacional de uma chave estrangeira do modelo de domínio usado pelo código do aplicativo/lógica de negócios. Este código do aplicativo manipula a relação inteiramente por meio de navegações.

Dica

Se as entidades forem serializadas, por exemplo, para enviar por meio de um fio, os valores de chave estrangeira poderão ser uma maneira útil de manter as informações de relação intactas quando as entidades não estiverem em um formulário de objeto/grafo. Portanto, muitas vezes é pragmático manter propriedades de chave estrangeira no tipo .NET para essa finalidade. As propriedades de chave estrangeira podem ser privadas, o que geralmente é um bom compromisso para evitar expor a chave estrangeira, permitindo que seu valor viaje com a entidade.

As propriedades de chave estrangeira de sombra geralmente são criadas por convenção. Uma chave estrangeira de sombra também será criada se o argumento para HasForeignKey não corresponder a nenhuma propriedade .NET. Por exemplo:

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder.Entity<Blog>()
        .HasMany(e => e.Posts)
        .WithOne(e => e.Blog)
        .HasForeignKey("MyBlogId");
}

Por convenção, uma chave estrangeira de sombra obtém seu tipo da chave principal na relação. Esse tipo é tornado anulável, a menos que a relação seja detectada como ou configurada conforme necessário.

A propriedade de chave estrangeira de sombra também pode ser criada explicitamente, o que é útil para configurar facetas da propriedade. Por exemplo, para tornar a propriedade não anulável:

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder.Entity<Post>()
        .Property<string>("MyBlogId")
        .IsRequired();

    modelBuilder.Entity<Blog>()
        .HasMany(e => e.Posts)
        .WithOne(e => e.Blog)
        .HasForeignKey("MyBlogId");
}

Dica

Por convenção, as propriedades de chave estrangeira herdam facetas como comprimento máximo e suporte unicode da chave principal na relação. Portanto, raramente é necessário configurar explicitamente facetas em uma propriedade de chave estrangeira.

A criação de uma propriedade de sombra se o nome fornecido não corresponder a nenhuma propriedade do tipo de entidade poderá ser desabilitada usando ConfigureWarnings. Por exemplo:

protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
    => optionsBuilder.ConfigureWarnings(b => b.Throw(CoreEventId.ShadowPropertyCreated));

Nomes de restrição de chave estrangeira

Por convenção, as restrições de chave estrangeira são nomeadas FK_<dependent type name>_<principal type name>_<foreign key property name>. Para chaves estrangeiras compostas, <foreign key property name> torna-se uma lista separada de sublinhados de nomes de propriedades de chave estrangeira.

Isso pode ser alterado na API de criação de modelo usando HasConstraintName. Por exemplo:

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder.Entity<Blog>()
        .HasMany(e => e.Posts)
        .WithOne(e => e.Blog)
        .HasForeignKey(e => e.BlogId)
        .HasConstraintName("My_BlogId_Constraint");
}

Dica

O nome da restrição não é usado pelo runtime do EF. Ele só é usado ao criar um esquema de banco de dados usando migrações do EF Core.

Índices de chaves estrangeiras

Por convenção, o EF cria um índice de banco de dados para a propriedade ou as propriedades de uma chave estrangeira. Confira Convenções de criação de modelo para obter mais informações sobre os tipos de índices criados por convenção.

Dica

As relações são definidas no modelo EF entre os tipos de entidade incluídos nesse modelo. Algumas relações podem precisar fazer referência a um tipo de entidade no modelo de um contexto diferente, por exemplo, ao usar o padrão BoundedContext. Nestas situações, as colunas de chave estrangeira devem ser mapeadas para propriedades normais e, em seguida, essas propriedades podem ser manipuladas manualmente para lidar com alterações na relação.

Chaves principais

Por convenção, as chaves estrangeiras são restritas à chave primária no lado principal da relação. No entanto, uma chave alternativa pode ser usada. Isso é feito usando HasPrincipalKey na API de criação de modelos. Por exemplo, para uma chave estrangeira de propriedade única:

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder.Entity<Blog>()
        .HasMany(e => e.Posts)
        .WithOne(e => e.Blog)
        .HasPrincipalKey(e => e.AlternateId);
}

Ou para uma chave estrangeira composta com várias propriedades:

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder.Entity<Blog>()
        .HasMany(e => e.Posts)
        .WithOne(e => e.Blog)
        .HasPrincipalKey(e => new { e.AlternateId1, e.AlternateId2 });
}

Também pode ser passado o nome da propriedade de chave alternativa como uma cadeia de caracteres para HasPrincipalKey. Por exemplo, para uma única chave de propriedade:

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder.Entity<Blog>()
        .HasMany(e => e.Posts)
        .WithOne(e => e.Blog)
        .HasPrincipalKey("AlternateId");
}

Ou, para uma chave composta:

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder.Entity<Blog>()
        .HasMany(e => e.Posts)
        .WithOne(e => e.Blog)
        .HasPrincipalKey("AlternateId1", "AlternateId2");
}

Observação

A ordem das propriedades nas chaves principal e estrangeira deve corresponder. Essa também é a ordem na qual a chave é definida no esquema de banco de dados. Ela não precisa ser a mesma que a ordem das propriedades no tipo de entidade ou as colunas na tabela.

Não é necessário chamar HasAlternateKey para definir a chave alternativa na entidade principal; isso é feito automaticamente quando HasPrincipalKey é usado com propriedades que não são as propriedades da chave primária. No entanto, HasAlternateKey pode ser usado para configurar ainda mais a chave alternativa, como definir seu nome de restrição de banco de dados. Confira Chaves para obter mais informações.

Relações com entidades sem chave

Cada relação deve ter uma chave estrangeira que faça referência a uma chave principal (primária ou alternativa). Isso significa que um tipo de entidade sem chave não pode atuar como lado principal de uma relação, pois não há chave principal a ser referenciada pelas chaves estrangeiras.

Dica

Um tipo de entidade não pode ter uma chave alternativa e nenhuma chave primária. Nesse caso, a chave alternativa (ou uma das chaves alternativas, se houver várias) deve ser promovida à chave primária.

No entanto, os tipos de entidade sem chave ainda podem ter chaves estrangeiras definidas e, portanto, podem atuar como o lado dependente de uma relação. Por exemplo, considere esses tipos, em que Tag não tem chave:

public class Tag
{
    public string Text { get; set; } = null!;
    public int PostId { get; set; }
    public Post Post { get; set; } = null!;
}

public class Post
{
    public int Id { get; set; }
}

Tag pode ser configurado no final dependente da relação:

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder.Entity<Tag>()
        .HasNoKey();

    modelBuilder.Entity<Post>()
        .HasMany<Tag>()
        .WithOne(e => e.Post);
}

Observação

O EF não dá suporte a navegações que apontam para tipos de entidade sem chave. Confira o Problema do GitHub nº 30331.

Chaves estrangeiras em relações de muitos para muitos

Em relações de muitos para muitos, as chaves estrangeiras são definidas no tipo de entidade de junção e mapeadas para restrições de chave estrangeira na tabela de junção. Tudo o que foi descrito acima também pode ser aplicado a essas chaves estrangeiras de entidade de junção. Por exemplo, definindo os nomes de restrição de banco de dados:

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder.Entity<Post>()
        .HasMany(e => e.Tags)
        .WithMany(e => e.Posts)
        .UsingEntity(
            l => l.HasOne(typeof(Tag)).WithMany().HasConstraintName("TagForeignKey_Constraint"),
            r => r.HasOne(typeof(Post)).WithMany().HasConstraintName("PostForeignKey_Constraint"));
}