Partilhar via


Relações um-para-um

As relações um para um são usadas quando uma entidade está associada no máximo a uma outra entidade. Por exemplo, um Blog tem um BlogHeader e esse BlogHeader pertence a um único Blog.

Esse documento é estruturado com base em muitos exemplos. Os exemplos começam com casos comuns que também introduzem conceitos. Os exemplos posteriores abrangem tipos menos comuns de configuração. Uma abordagem recomendada é entender os primeiros exemplos e conceitos para, em seguida, conferir os exemplos posteriores de acordo com suas necessidades específicas. Com base nessa abordagem, começaremos com relações um para um simples "obrigatórias" e "opcionais".

Dica

O código para todos os exemplos abaixo pode ser encontrado em OneToOne.cs.

Relações um para um

// Principal (parent)
public class Blog
{
    public int Id { get; set; }
    public BlogHeader? Header { get; set; } // Reference navigation to dependent
}

// Dependent (child)
public class BlogHeader
{
    public int Id { get; set; }
    public int BlogId { get; set; } // Required foreign key property
    public Blog Blog { get; set; } = null!; // Required reference navigation to principal
}

Uma relação um para um consiste em:

Dica

Nem sempre é óbvio qual lado de uma relação um para um deve ser a principal e qual lado deve ser a dependente. Algumas considerações são:

  • Se as tabelas de banco de dados para os dois tipos já existirem, a tabela com as colunas de chave estrangeira deverá ser mapeada para o tipo dependente.
  • Um tipo geralmente é o tipo dependente se não puder existir logicamente sem o outro tipo. Por exemplo, não faz sentido ter um cabeçalho de um blog que não existe, portanto, o BlogHeader é naturalmente o tipo dependente.
  • Se houver uma relação pai/filho natural, o filho geralmente será o tipo dependente.

Portanto, para a relação nesse exemplo:

  • A propriedade de chave estrangeira BlogHeader.BlogId não é anulável. Isso torna a relação "obrigatória" porque cada (BlogHeader) dependente deve estar relacionado a algum(Blog) principal, uma vez que sua propriedade de chave estrangeira deve ser definida como algum valor.
  • A navegação de ambas as entidades aponta para a entidade relacionada do outro lado da relação.

Observação

Uma relação obrigatória garante que todas as entidades dependentes sejam associadas a alguma entidade principal. No entanto, uma entidade principal sempre pode existir sem uma entidade dependente. Ou seja, uma relação obrigatória não indica que sempre haverá uma entidade dependente. Não há nenhuma maneira no modelo EF e também nenhuma maneira padrão em um banco de dados relacional de garantir que uma entidade principal esteja associada a uma dependente. Se isso for necessário, deve ser implementado na lógica do aplicativo (de negócios). Para saber mais, confira Navegações obrigatórias.

Dica

A relação com duas navegações, uma da entidade dependente para a principal e uma inversa, da entidade principal para a dependente, é conhecida como uma relação bidirecional.

Essa relação é descoberta por convenção. Ou seja:

  • Blog é descoberta como a entidade principal na relação e BlogHeader é descoberta como a dependente.
  • BlogHeader.BlogId é descoberta como uma chave estrangeira da entidade dependente que faz referência à chave primária Blog.Id da entidade principal. A relação é descoberta como obrigatória porque a BlogHeader.BlogId não é anulável.
  • O Blog.BlogHeader é descoberto como a navegação de referência.
  • O BlogHeader.Blog é descoberto como a navegação da referência.

Importante

Ao usar tipos de referência anuláveis em C#, a navegação da entidade dependente para a principal deverá ser anulável se a propriedade de chave estrangeira for anulável. Se a propriedade de chave estrangeira não for anulável, a navegação poderá ser anulável ou não. Nesse caso, BlogHeader.BlogId não é anulável e BlogHeader.Blog também não é anulável. O constructo = null!; é usado para marcar isso como intencional para o compilador C#, uma vez que o EF normalmente define a instância Blog e não pode ser nula em uma relação totalmente carregada. Para saber mais, confira Como trabalhar com tipos de referência anuláveis.

Nos casos em que as navegações, a chave estrangeira ou a natureza obrigatória/opcional da relação não são descobertas por convenção, é possível configurá-las explicitamente. Por exemplo:

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

No exemplo acima, a configuração das relações começa no tipo de entidade (Blog) principal. Assim como acontece com todas as relações, é equivalente começar com o tipo de entidade dependente (BlogHeader) em vez disso. Por exemplo:

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

Nenhuma dessas opções é melhor do que a outra, e ambas resultam exatamente na mesma configuração.

Dica

Nunca é necessário configurar uma relação duas vezes. Ao começar na entidade principal, não será preciso fazer o mesmo ao começar novamente na dependente. Além disso, a tentativa de configurar a entidade principal e as metades dependentes de uma relação separadamente geralmente não funciona. Escolha configurar cada relação de uma extremidade ou de outra e, em seguida, escreva o código de configuração apenas uma vez.

Relação um para um opcional

// Principal (parent)
public class Blog
{
    public int Id { get; set; }
    public BlogHeader? Header { get; set; } // Reference navigation to dependent
}

// Dependent (child)
public class BlogHeader
{
    public int Id { get; set; }
    public int? BlogId { get; set; } // Optional foreign key property
    public Blog? Blog { get; set; } // Optional reference navigation to principal
}

Isso é o mesmo que o exemplo anterior, exceto que a propriedade de chave estrangeira e a navegação para a entidade principal agora são anuláveis. Isso torna a relação "opcional" porque um dependente (BlogHeader) não pode estar relacionado a nenhuma (Blog) entidade de segurança definindo sua propriedade de chave estrangeira e navegação como null.

Importante

Ao usar tipos de referência anuláveis em C#, a propriedade de navegação da entidade dependente para a principal deverá ser anulável se a propriedade de chave estrangeira for anulável. Nesse caso, BlogHeader.BlogId é anulável, portanto, BlogHeader.Blog deve ser anulável também. Para saber mais, confira Como trabalhar com tipos de referência anuláveis.

Como no exemplo anterior, esta relação é descoberta por convenção. Nos casos em que as navegações, a chave estrangeira ou a natureza obrigatória/opcional da relação não são descobertas por convenção, é possível configurá-las explicitamente. Por exemplo:

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder.Entity<Blog>()
        .HasOne(e => e.Header)
        .WithOne(e => e.Blog)
        .HasForeignKey<BlogHeader>(e => e.BlogId)
        .IsRequired(false);
}

A relação um para um obrigatória com a chave primária para a relação de chave primária

// Principal (parent)
public class Blog
{
    public int Id { get; set; }
    public BlogHeader? Header { get; set; } // Reference navigation to dependent
}

// Dependent (child)
public class BlogHeader
{
    public int Id { get; set; }
    public Blog Blog { get; set; } = null!; // Required reference navigation to principal
}

Ao contrário das relações um para muitos, a extremidade dependente de uma relação um para um pode usar sua propriedade de chave primária ou propriedades como a propriedade ou as propriedades de chave estrangeira. Isso geralmente é chamado de relação PK para PK. Isso só é possível quando os tipos principal e dependente têm os mesmos tipos de chave primária e a relação resultante é sempre obrigatória, pois a chave primária do dependente não pode ser anulável.

Qualquer relação um para um em que a chave estrangeira não seja descoberta por convenção deve ser configurada para indicar as extremidades principal e dependente da relação. Normalmente isso é feito com uma chamada para HasForeignKey. Por exemplo:

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder.Entity<Blog>()
        .HasOne(e => e.Header)
        .WithOne(e => e.Blog)
        .HasForeignKey<BlogHeader>();
}

Dica

HasPrincipalKey também pode ser usado para essa finalidade, mas fazer isso é menos comum.

Quando nenhuma propriedade é especificada na chamada para HasForeignKey e a chave primária é adequada, ela é usada como a chave estrangeira. Para casos em que as navegações, a chave estrangeira ou a natureza obrigatória/opcional da relação não são descobertas por convenção, elas podem ser configuradas explicitamente. Por exemplo:

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder.Entity<Blog>()
        .HasOne(e => e.Header)
        .WithOne(e => e.Blog)
        .HasForeignKey<BlogHeader>(e => e.Id)
        .IsRequired();
}

Relação um para um obrigatória com chave estrangeira de sombra

// Principal (parent)
public class Blog
{
    public int Id { get; set; }
    public BlogHeader? Header { get; set; } // Reference navigation to dependent
}

// Dependent (child)
public class BlogHeader
{
    public int Id { get; set; }
    public Blog Blog { get; set; } = null!; // Required reference navigation to principal
}

Em alguns casos, é possível que você não queira uma propriedade de chave estrangeira no modelo, já que elas são um detalhe sobre como a relação é representada no banco de dados, o que não é necessário ao usar a relação de maneira puramente orientada a objetos. No entanto, se as entidades forem serializadas, por exemplo, para envio por uma rede com fio, os valores de chave estrangeira poderão ser uma maneira útil de manter as informações da relação intactas quando as entidades não estiverem em um formato de objeto. Portanto, é frequentemente recomendado manter as 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.

Seguindo o exemplo anterior, esse exemplo remove a propriedade de chave estrangeira do tipo de entidade dependente. No entanto, em vez de usar a chave primária, o EF é instruído a criar uma propriedade de chave estrangeira de sombra chamada BlogId do tipo int.

Um ponto importante a ser observado aqui é que os tipos de referência anuláveis em C# estão sendo usados, portanto, a nulidade da navegação de dependente para principal é usada para determinar se a propriedade de chave estrangeira é ou não anulável e, portanto, se a relação é opcional ou obrigatória. Se os tipos de referência anuláveis não estiverem sendo usados, a propriedade de chave estrangeira de sombra será anulável por padrão tornando a relação opcional por padrão. Nesse caso, use IsRequired para forçar a propriedade de chave estrangeira de sombra a ser não anulável e tornar a relação obrigatória.

Essa relação precisa novamente de uma configuração para indicar as extremidades principal e dependentes:

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder.Entity<Blog>()
        .HasOne(e => e.Header)
        .WithOne(e => e.Blog)
        .HasForeignKey<BlogHeader>("BlogId");
}

Para casos em que as navegações, a chave estrangeira ou a natureza obrigatória/opcional da relação não são descobertas por convenção, elas podem ser configuradas explicitamente. Por exemplo:

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder.Entity<Blog>()
        .HasOne(e => e.Header)
        .WithOne(e => e.Blog)
        .HasForeignKey<BlogHeader>("BlogId")
        .IsRequired();
}

Relação um para um opcional com chave estrangeira de sombra

// Principal (parent)
public class Blog
{
    public int Id { get; set; }
    public BlogHeader? Header { get; set; } // Reference navigation to dependent
}

// Dependent (child)
public class BlogHeader
{
    public int Id { get; set; }
    public Blog? Blog { get; set; } // Optional reference navigation to principal
}

Como o exemplo anterior, a propriedade da chave estrangeira foi removida do tipo de entidade dependente. No entanto, ao contrário do exemplo anterior, dessa vez a propriedade de chave estrangeira é criada como anulável porque os tipos de referência anuláveis em C# estão sendo usados e a navegação no tipo de entidade dependente é anulável. Isso torna a relação opcional.

Quando os tipos de referência anuláveis em C# não estiverem sendo usados, a propriedade de chave estrangeira será criada como anulável por padrão. Isso significa que as relações com propriedades de sombra criadas automaticamente são opcionais por padrão.

Como anteriormente, essa relação precisa de uma configuração para indicar as extremidades principal e dependente:

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder.Entity<Blog>()
        .HasOne(e => e.Header)
        .WithOne(e => e.Blog)
        .HasForeignKey<BlogHeader>("BlogId");
}

Para casos em que as navegações, a chave estrangeira ou a natureza obrigatória/opcional da relação não são descobertas por convenção, elas podem ser configuradas explicitamente. Por exemplo:

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder.Entity<Blog>()
        .HasOne(e => e.Header)
        .WithOne(e => e.Blog)
        .HasForeignKey<BlogHeader>("BlogId")
        .IsRequired(false);
}

Relação um para um sem navegação para a entidade principal

// Principal (parent)
public class Blog
{
    public int Id { get; set; }
    public BlogHeader? Header { get; set; } // Reference navigation to dependent
}

// Dependent (child)
public class BlogHeader
{
    public int Id { get; set; }
    public int BlogId { get; set; } // Required foreign key property
}

Nesse exemplo, a propriedade de chave estrangeira foi novamente introduzida, mas a navegação no dependente foi removida.

Dica

A relação com apenas uma navegação, uma da entidade dependente para a principal ou uma da entidade principal para a dependente, mas não ambas, é conhecida como uma relação unidirecional.

Essa relação é descoberta pela convenção, uma vez que a chave estrangeira é descoberta, indicando assim o lado dependente. Para casos em que as navegações, a chave estrangeira ou a natureza obrigatória/opcional da relação não são descobertas por convenção, elas podem ser configuradas explicitamente. Por exemplo:

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

Observe que a chamada para WithOne não tem argumentos. Esta é a maneira de informar ao EF que não há navegação de BlogHeader para Blog.

Se a configuração começar na entidade sem navegação, o tipo da entidade na outra extremidade da relação deverá ser especificado explicitamente usando a chamada HasOne<>() genérica. Por exemplo:

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

A relação um para um sem navegação para entidade principal e com chave estrangeira de sombra

// Principal (parent)
public class Blog
{
    public int Id { get; set; }
    public BlogHeader? Header { get; set; } // Reference navigation to dependent
}

// Dependent (child)
public class BlogHeader
{
    public int Id { get; set; }
}

Esse exemplo combina dois dos exemplos anteriores removendo a propriedade de chave estrangeira e a navegação do dependente.

Como anteriormente, essa relação precisa de uma configuração para indicar as extremidades principal e dependente:

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder.Entity<Blog>()
        .HasOne(e => e.Header)
        .WithOne()
        .HasForeignKey<BlogHeader>("BlogId")
        .IsRequired();
}

Uma configuração mais completa pode ser usada para configurar explicitamente o nome da navegação e da chave estrangeira, com uma chamada apropriada para IsRequired() ou IsRequired(false), conforme necessário. Por exemplo:

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder.Entity<Blog>()
        .HasOne(e => e.Header)
        .WithOne()
        .HasForeignKey<BlogHeader>("BlogId")
        .IsRequired();
}

Relação um para um sem navegação para a entidade dependente

// Principal (parent)
public class Blog
{
    public int Id { get; set; }
}

// Dependent (child)
public class BlogHeader
{
    public int Id { get; set; }
    public int BlogId { get; set; } // Required foreign key property
    public Blog Blog { get; set; } = null!; // Required reference navigation to principal
}

Os dois exemplos anteriores tinham navegações da entidade principal para as dependentes, mas nenhuma navegação da dependente para a principal. Nos próximos exemplos, a navegação no dependente é reintroduzida, enquanto a navegação na principal é removida.

Por convenção, o EF tratará isso como uma relação um para muitos. Algumas configurações mínimas são necessárias para torná-la um para um:

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder.Entity<BlogHeader>()
        .HasOne(e => e.Blog)
        .WithOne();
}

Observe novamente que WithOne() é chamado sem argumentos para indicar que não há navegação nessa direção.

Para casos em que as navegações, a chave estrangeira ou a natureza obrigatória/opcional da relação não são descobertas por convenção, elas podem ser configuradas explicitamente. Por exemplo:

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

Se a configuração começar da entidade sem navegação, o tipo da entidade na outra extremidade da relação deverá ser especificado explicitamente usando a chamada HasOne<>() genérica. Por exemplo:

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

Relação um para um sem navegação

Ocasionalmente, pode ser útil configurar uma relação sem navegação. Essa relação só pode ser manipulada alterando diretamente o valor da chave estrangeira.

// Principal (parent)
public class Blog
{
    public int Id { get; set; }
}

// Dependent (child)
public class BlogHeader
{
    public int Id { get; set; }
    public int BlogId { get; set; } // Required foreign key property
}

Ela não é descoberta por convenção, pois não há navegação indicando que os dois tipos estão relacionados. É possível configurá-la explicitamente em OnModelCreating. Por exemplo:

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder.Entity<Blog>()
        .HasOne<BlogHeader>()
        .WithOne();
}

Com essa configuração, a propriedade BlogHeader.BlogId ainda é detectada como a chave estrangeira por convenção e a relação é "obrigatória" porque a propriedade de chave estrangeira não é anulável. A relação pode se tornar "opcional" tornando a propriedade de chave estrangeira anulável.

Uma configuração explícita mais completa dessa relação é:

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

Relação um para um com chave alternativa

Em todos os exemplos até agora, a propriedade de chave estrangeira na dependente é restrita à propriedade de chave primária na principal. Em vez disso, a chave estrangeira pode ser restrita a uma propriedade diferente, que se torna uma chave alternativa para o tipo de entidade principal. Por exemplo:

// Principal (parent)
public class Blog
{
    public int Id { get; set; }
    public int AlternateId { get; set; } // Alternate key as target of the BlogHeader.BlogId foreign key
    public BlogHeader? Header { get; set; } // Reference navigation to dependent
}

// Dependent (child)
public class BlogHeader
{
    public int Id { get; set; }
    public int BlogId { get; set; } // Required foreign key property
    public Blog Blog { get; set; } = null!; // Required reference navigation to principal
}

Essa relação não é descoberta por convenção, pois o EF sempre criará, por convenção, uma relação com a chave primária. Ela pode ser configurada explicitamente em OnModelCreating usando uma chamada para HasPrincipalKey. Por exemplo:

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

HasPrincipalKey pode ser combinada com outras chamadas para configurar explicitamente as navegações, as propriedades de chave estrangeira e a natureza obrigatória/opcional. Por exemplo:

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder.Entity<Blog>()
        .HasOne(e => e.Header)
        .WithOne(e => e.Blog)
        .HasPrincipalKey<Blog>(e => e.AlternateId)
        .HasForeignKey<BlogHeader>(e => e.BlogId)
        .IsRequired();
}

Relação um para um com chave estrangeira composta

Em todos os exemplos até agora, a propriedade de chave primária ou alternativa da entidade principal consistia em uma única propriedade. As chaves primárias ou alternativas também podem ser formadas por mais de uma propriedade, sendo conhecidas como "chaves compostas". Quando a entidade principal de uma relação tem uma chave composta, a chave estrangeira da dependente também deve ser uma chave composta com o mesmo número de propriedades. Por exemplo:

// Principal (parent)
public class Blog
{
    public int Id1 { get; set; } // Composite key part 1
    public int Id2 { get; set; } // Composite key part 2
    public BlogHeader? Header { get; set; } // Reference navigation to dependent
}

// Dependent (child)
public class BlogHeader
{
    public int Id { get; set; }
    public int BlogId1 { get; set; } // Required foreign key property part 1
    public int BlogId2 { get; set; } // Required foreign key property part 2
    public Blog Blog { get; set; } = null!; // Required reference navigation to principal
}

Essa relação é descoberta por convenção. No entanto, ela só será descoberta se a chave composta tiver sido configurada explicitamente, já que as chaves compostas não são descobertas automaticamente. Por exemplo:

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder.Entity<Blog>()
        .HasKey(e => new { e.Id1, e.Id2 });
}

Importante

Um valor de chave estrangeira composta é considerado null quando qualquer um dos valores de propriedade é nulo. Uma chave estrangeira composta com uma propriedade nula e outra não nula não será considerada uma correspondência para uma chave primária ou alternativa com os mesmos valores. Ambas serão consideradas null.

Tanto HasForeignKey quanto HasPrincipalKey podem ser usadas para especificar explicitamente chaves com muitas propriedades. Por exemplo:

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder.Entity<Blog>(
        nestedBuilder =>
        {
            nestedBuilder.HasKey(e => new { e.Id1, e.Id2 });

            nestedBuilder.HasOne(e => e.Header)
                .WithOne(e => e.Blog)
                .HasPrincipalKey<Blog>(e => new { e.Id1, e.Id2 })
                .HasForeignKey<BlogHeader>(e => new { e.BlogId1, e.BlogId2 })
                .IsRequired();
        });
}

Dica

No código acima, as chamadas para HasKey e HasOne foram agrupadas em um construtor aninhado. Os construtores aninhados removem a necessidade de chamar Entity<>() várias vezes para o mesmo tipo de entidade, mas são funcionalmente equivalentes a chamar Entity<>() várias vezes.

Relação um para um obrigatória sem exclusão em cascata

// Principal (parent)
public class Blog
{
    public int Id { get; set; }
    public BlogHeader? Header { get; set; } // Reference navigation to dependent
}

// Dependent (child)
public class BlogHeader
{
    public int Id { get; set; }
    public int BlogId { get; set; } // Required foreign key property
    public Blog Blog { get; set; } = null!; // Required reference navigation to principal
}

Por convenção, as relações obrigatórias são configuradas para exclusão em cascata. Isso ocorre porque a dependente não pode existir no banco de dados depois que a principal tiver sido excluída. O banco de dados pode ser configurado para gerar um erro, normalmente causando falha no aplicativo, em vez de excluir automaticamente linhas dependentes que não podem mais existir. Isso requer uma configuração:

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder.Entity<Blog>()
        .HasOne(e => e.Header)
        .WithOne(e => e.Blog)
        .OnDelete(DeleteBehavior.Restrict);
}

Auto-referenciando relação um-para-um

Em todos os exemplos anteriores, o tipo de entidade principal era diferente do tipo de entidade dependente. Esse não precisa ser o caso. Por exemplo, nos tipos abaixo, cada Person está opcionalmente relacionado a outro Person.

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

    public int? HusbandId { get; set; } // Optional foreign key property
    public Person? Husband { get; set; } // Optional reference navigation to principal
    public Person? Wife { get; set; } // Reference navigation to dependent
}

Essa relação é descoberta por convenção. Nos casos em que as navegações, a chave estrangeira ou a natureza obrigatória/opcional da relação não são descobertas por convenção, é possível configurá-las explicitamente. Por exemplo:

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder.Entity<Person>()
        .HasOne(e => e.Husband)
        .WithOne(e => e.Wife)
        .HasForeignKey<Person>(e => e.HusbandId)
        .IsRequired(false);
}

Observação

Para relações um para um de auto-referência, uma vez que os tipos de entidade principal e dependente são os mesmos, especificar qual tipo contém a chave estrangeira não esclarece a extremidade dependente. Nesse caso, a navegação especificada em pontos HasOne de dependente para principal e a navegação especificada em pontos WithOne de principal para dependente.