Relacionamento um para muitos
Relações um para muitos são usadas quando uma única entidade é associada a qualquer número de outras entidades. Por exemplo, Blog
pode ter muitas Posts
associadas, mas cada Post
está associada a apenas uma Blog
.
Este documento foi elaborado 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 com base em suas necessidades específicas. Com base nessa abordagem, começaremos com relações um para muitos simples, denominadas "obrigatórias" e "opcionais".
Dica
O código para todos os exemplos abaixo pode ser encontrado em OneToMany.cs.
Relações um para muitos obrigatórias
// Principal (parent)
public class Blog
{
public int Id { get; set; }
public ICollection<Post> Posts { get; } = new List<Post>(); // Collection navigation containing dependents
}
// Dependent (child)
public class Post
{
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 muitos consiste no seguinte:
- Uma ou mais propriedades de chave primária ou alternativa na entidade principal, representando o “um” nessa relação. Por exemplo,
Blog.Id
. - Uma ou mais propriedades de chave estrangeira na entidade dependente, representando a palavra "muitos" nessa relação. Por exemplo,
Post.BlogId
. - Opcionalmente, uma navegação de coleção na entidade principal que faz referência às entidades dependentes. Por exemplo,
Blog.Posts
. - Opcionalmente, uma navegação de referência na entidade principal que faz referência às entidades dependentes. Por exemplo,
Post.Blog
.
Portanto, para a relação neste exemplo:
- A propriedade de chave estrangeira
Post.BlogId
não é anulável. Isso torna a relação "necessária" porque cada entidade dependente (Post
) deve estar relacionada a alguma entidade principal (Blog
), uma vez que a propriedade de chave estrangeira deve ser definida como algum valor. - A navegação de ambas as entidades aponta para as entidades relacionadas do outro lado da relação.
Observação
Uma relação necessária garante que todas as entidades dependentes sejam associadas a alguma entidade principal. No entanto, uma entidade principal sempre pode existir sem entidades dependentes. Isso significa que uma relação necessária não indica que sempre haverá pelo menos uma entidade dependente. Não há nenhuma maneira no modelo do EF e também nenhuma maneira padrão em um banco de dados relacional de garantir que uma entidade principal esteja associada a um determinado número de dependentes. Se isso for necessário, deverá ser implementado na lógica do aplicativo (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 as dependentes, é conhecida como uma relação bidirecional.
Essa relação é descoberta por convenção. Ou seja:
Blog
é descoberta como a entidade principal na relação ePost
é descoberta como a dependente.Post.BlogId
é descoberta como uma chave estrangeira da entidade dependente que faz referência à chave primáriaBlog.Id
da entidade principal. A relação é descoberta conforme necessário porquePost.BlogId
não é anulável.Blog.Posts
é descoberta como a navegação da coleção.Post.Blog
é descoberta como a navegação da referência.
Importante
Ao usar tipos de referência anuláveis em C#, a navegação de referência deverá ser anulável se a propriedade de chave estrangeira também for. Se a propriedade de chave estrangeira não for anulável, a navegação de referência poderá ser anulável ou não. Nesse caso, Post.BlogId
não é anulável e Post.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 ela não pode ser nula para 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>()
.HasMany(e => e.Posts)
.WithOne(e => e.Blog)
.HasForeignKey(e => e.BlogId)
.IsRequired();
}
No exemplo acima, a configuração das relações começa com HasMany
no tipo de entidade principal (Blog
) e, em seguida, com WithOne
. Assim como acontece com todas as relações, isso é exatamente equivalente a começar com o tipo de entidade dependente (Post
) e usar HasOne
seguido por WithMany
. Por exemplo:
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Post>()
.HasOne(e => e.Blog)
.WithMany(e => e.Posts)
.HasForeignKey(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 por meio de uma ou de outra extremidade e, em seguida, escreva o código de configuração apenas uma vez.
Relações um para muitos opcionais
// Principal (parent)
public class Blog
{
public int Id { get; set; }
public ICollection<Post> Posts { get; } = new List<Post>(); // Collection navigation containing dependents
}
// Dependent (child)
public class Post
{
public int Id { get; set; }
public int? BlogId { get; set; } // Optional foreign key property
public Blog? Blog { get; set; } // Optional reference navigation to principal
}
Este exemplo é igual ao anterior, mas 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 uma entidade dependente (Post
) pode existir sem estar relacionada a uma entidade principal (Blog
).
Importante
Ao usar tipos de referência anuláveis em C#, a navegação de referência deverá ser anulável se a propriedade de chave estrangeira também for. Nesse caso, como Post.BlogId
é anulável, Post.Blog
também deve ser. 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>()
.HasMany(e => e.Posts)
.WithOne(e => e.Blog)
.HasForeignKey(e => e.BlogId)
.IsRequired(false);
}
Relações um para muitos obrigatórias com chave estrangeira de sombra
// Principal (parent)
public class Blog
{
public int Id { get; set; }
public ICollection<Post> Posts { get; } = new List<Post>(); // Collection navigation containing dependents
}
// Dependent (child)
public class Post
{
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 da chave estrangeira podem ser particulares, o que geralmente é adequado para evitar a exposição dela, mas permitir que seu valor seja enviado com a entidade.
Dando sequência aos exemplos anteriores, aqui, a propriedade da chave estrangeira é removida do tipo de entidade dependente. Portanto, o EF cria uma propriedade de chave estrangeira de sombra chamada BlogId
com o tipo int
.
É importante notar que, como os tipos de referência anuláveis em C# estão sendo usados, a nulidade da navegação de referência é usada para determinar se a propriedade da 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, o que tornará 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.
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>()
.HasMany(e => e.Posts)
.WithOne(e => e.Blog)
.HasForeignKey("BlogId")
.IsRequired();
}
Relações um para muitos opcionais com chave estrangeira de sombra
// Principal (parent)
public class Blog
{
public int Id { get; set; }
public ICollection<Post> Posts { get; } = new List<Post>(); // Collection navigation containing dependents
}
// Dependent (child)
public class Post
{
public int Id { get; set; }
public Blog? Blog { get; set; } // Optional reference navigation to principal
}
Como no exemplo anterior, a propriedade da chave estrangeira foi removida do tipo de entidade dependente. Portanto, o EF cria uma propriedade de chave estrangeira de sombra chamada BlogId
com o tipo int?
. No entanto, diferentemente do exemplo anterior, desta 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 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>()
.HasMany(e => e.Posts)
.WithOne(e => e.Blog)
.HasForeignKey("BlogId")
.IsRequired(false);
}
Relações um para muitos sem navegação para a entidade principal
// Principal (parent)
public class Blog
{
public int Id { get; set; }
public ICollection<Post> Posts { get; } = new List<Post>(); // Collection navigation containing dependents
}
// Dependent (child)
public class Post
{
public int Id { get; set; }
public int BlogId { get; set; } // Required foreign key property
}
Neste exemplo, a propriedade de chave estrangeira foi novamente introduzida, mas a navegação na entidade dependente foi removida.
Dica
Uma relação que tem somente uma navegação, da entidade dependente para a principal ou da principal para as dependentes, mas não ambas, é conhecida como uma relação unidirecional.
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>()
.HasMany(e => e.Posts)
.WithOne()
.HasForeignKey(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 Post
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<Post>()
.HasOne<Blog>()
.WithMany(e => e.Posts)
.HasForeignKey(e => e.BlogId)
.IsRequired();
}
Relações um para muitos sem navegação para a entidade principal e com chave estrangeira de sombra
// Principal (parent)
public class Blog
{
public int Id { get; set; }
public ICollection<Post> Posts { get; } = new List<Post>(); // Collection navigation containing dependents
}
// Dependent (child)
public class Post
{
public int Id { get; set; }
}
Este exemplo combina dois dos exemplos anteriores removendo a propriedade de chave estrangeira e a navegação da entidade dependente.
Essa relação é descoberta por convenção como opcional. Como não há nada no código que possa ser usado para indicar que a relação é obrigatória, é preciso realizar algumas configurações mínimas usando IsRequired
para criar essa relação. Por exemplo:
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Blog>()
.HasMany(e => e.Posts)
.WithOne()
.IsRequired();
}
Uma configuração mais completa pode ser usada para configurar explicitamente o nome da chave estrangeira e a navegação, com uma chamada apropriada para IsRequired()
ou IsRequired(false)
, conforme necessário. Por exemplo:
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Blog>()
.HasMany(e => e.Posts)
.WithOne()
.HasForeignKey("BlogId")
.IsRequired();
}
Relações um para muitos sem navegação para as entidades dependentes
// Principal (parent)
public class Blog
{
public int Id { get; set; }
}
// Dependent (child)
public class Post
{
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 das dependentes para a principal. Nos próximos exemplos, a navegação nas entidades dependentes é introduzida novamente e a navegação na entidade principal é removida.
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<Post>()
.HasOne(e => e.Blog)
.WithMany()
.HasForeignKey(e => e.BlogId)
.IsRequired();
}
Observe novamente que WithMany()
é chamado sem argumentos para indicar que não há navegação nessa direção.
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 HasMany<>()
genérica. Por exemplo:
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Blog>()
.HasMany<Post>()
.WithOne(e => e.Blog)
.HasForeignKey(e => e.BlogId)
.IsRequired();
}
Relações um para muitos 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 Post
{
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>()
.HasMany<Post>()
.WithOne();
}
Com essa configuração, a propriedade Post.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 ser transformada em "opcional" ao tornar a propriedade de chave estrangeira anulável.
Veja a seguinte configuração explícita mais completa para essa relação:
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Blog>()
.HasMany<Post>()
.WithOne()
.HasForeignKey(e => e.BlogId)
.IsRequired();
}
Relações um para muitos com chaves alternativas
Em todos os exemplos anteriores, a propriedade de chave estrangeira na entidade 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 Post.BlogId foreign key
public ICollection<Post> Posts { get; } = new List<Post>(); // Collection navigation containing dependents
}
// Dependent (child)
public class Post
{
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>()
.HasMany(e => e.Posts)
.WithOne(e => e.Blog)
.HasPrincipalKey(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>()
.HasMany(e => e.Posts)
.WithOne(e => e.Blog)
.HasPrincipalKey(e => e.AlternateId)
.HasForeignKey(e => e.BlogId)
.IsRequired();
}
Relações um para muitos com chave estrangeira composta
Em todos os exemplos anteriores, a propriedade de chave primária ou alternativa da entidade principal consistia em uma única propriedade. Chaves primárias ou alternativas também podem ser formadas a partir de mais de uma propriedade. Elas são 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 ICollection<Post> Posts { get; } = new List<Post>(); // Collection navigation containing dependents
}
// Dependent (child)
public class Post
{
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, a própria chave composta precisa ser configurada explicitamente:
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.HasMany(e => e.Posts)
.WithOne(e => e.Blog)
.HasPrincipalKey(e => new { e.Id1, e.Id2 })
.HasForeignKey(e => new { e.BlogId1, e.BlogId2 })
.IsRequired();
});
}
Dica
No código acima, as chamadas para HasKey
e HasMany
foram agrupadas em um construtor aninhado. Os construtores aninhados removem a necessidade de chamar Entity<>()
diversas vezes para o mesmo tipo de entidade, mas são funcionalmente equivalentes a chamar Entity<>()
repetidamente.
Relações um para muitos obrigatórias sem exclusão em cascata
// Principal (parent)
public class Blog
{
public int Id { get; set; }
public ICollection<Post> Posts { get; } = new List<Post>(); // Collection navigation containing dependents
}
// Dependent (child)
public class Post
{
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 significa que, quando a entidade principal é excluída, todos as dependentes também são, pois elas não podem existir no banco de dados sem a principal. É possível configurar o EF para gerar uma exceção em vez de excluir automaticamente as linhas dependentes que não podem mais existir:
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Blog>()
.HasMany(e => e.Posts)
.WithOne(e => e.Blog)
.OnDelete(DeleteBehavior.Restrict);
}
Relações um para muitos com referência própria
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 Employee
está opcionalmente relacionada a outra Employees
.
public class Employee
{
public int Id { get; set; }
public int? ManagerId { get; set; } // Optional foreign key property
public Employee? Manager { get; set; } // Optional reference navigation to principal
public ICollection<Employee> Reports { get; } = new List<Employee>(); // Collection navigation containing dependents
}
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<Employee>()
.HasOne(e => e.Manager)
.WithMany(e => e.Reports)
.HasForeignKey(e => e.ManagerId)
.IsRequired(false);
}