Relações

Uma relação define como duas entidades se relacionam entre si. Em um banco de dados relacional, isso é representado por uma restrição FOREIGN KEY.

Observação

A maioria dos exemplos neste artigo usa uma relação um-para-muitos para demonstrar conceitos. Para obter exemplos de relações um-para-um e muitos para muitos, consulte a seção outros padrões de relação no final do artigo.

Definição de termos

Há vários termos usados para descrever as relações

  • Entidade dependente: Essa é a entidade que contém as propriedades de chave estrangeira. Às vezes, chamado de ' filho ' da relação.

  • Entidade principal: Esta é a entidade que contém as propriedades de chave primária/alternativa. Às vezes, chamado de ' pai ' da relação.

  • Chave principal: As propriedades que identificam exclusivamente a entidade principal. Essa pode ser a chave primária ou uma chave alternativa.

  • Chave estrangeira: As propriedades na entidade dependente que são usadas para armazenar os valores de chave de entidade de segurança para a entidade relacionada.

  • Propriedade de navegação: Uma propriedade definida na entidade principal e/ou dependente que faz referência à entidade relacionada.

    • Propriedade de navegação da coleção: Uma propriedade de navegação que contém referências a muitas entidades relacionadas.

    • Propriedade de navegação de referência: Uma propriedade de navegação que mantém uma referência a uma única entidade relacionada.

    • Propriedade de navegação inversa: Ao discutir uma propriedade de navegação específica, esse termo refere-se à propriedade de navegação na outra extremidade da relação.

  • Relação de auto-referência: Uma relação na qual os tipos de entidade dependente e principal são os mesmos.

O código a seguir mostra uma relação um-para-muitos entre Blog e Post

public class Blog
{
    public int BlogId { get; set; }
    public string Url { get; set; }

    public List<Post> Posts { get; set; }
}

public class Post
{
    public int PostId { get; set; }
    public string Title { get; set; }
    public string Content { get; set; }

    public int BlogId { get; set; }
    public Blog Blog { get; set; }
}
  • Post é a entidade dependente

  • Blog é a entidade principal

  • Blog.BlogId é a chave principal (nesse caso, é uma chave primária em vez de uma chave alternativa)

  • Post.BlogId é a chave estrangeira

  • Post.Blog é uma propriedade de navegação de referência

  • Blog.Posts é uma propriedade de navegação de coleção

  • Post.Blog é a propriedade de navegação inversa de Blog.Posts (e vice-versa)

Convenções

Por padrão, uma relação será criada quando houver uma propriedade de navegação descoberta em um tipo. Uma propriedade é considerada uma propriedade de navegação se o tipo que ele aponta não puder ser mapeado como um tipo escalar pelo provedor de banco de dados atual.

Observação

As relações descobertas por convenção serão sempre direcionadas à chave primária da entidade principal. Para direcionar uma chave alternativa, a configuração adicional deve ser executada usando a API Fluent.

Relações totalmente definidas

O padrão mais comum para relações é ter propriedades de navegação definidas em ambas as extremidades da relação e uma propriedade de chave estrangeira definida na classe de entidade dependente.

  • Se um par de propriedades de navegação for encontrado entre dois tipos, eles serão configurados como propriedades de navegação inversas da mesma relação.

  • Se a entidade dependente contiver uma propriedade com um nome correspondente a um desses padrões, ela será configurada como a chave estrangeira:

    • <navigation property name><principal key property name>
    • <navigation property name>Id
    • <principal entity name><principal key property name>
    • <principal entity name>Id
public class Blog
{
    public int BlogId { get; set; }
    public string Url { get; set; }

    public List<Post> Posts { get; set; }
}

public class Post
{
    public int PostId { get; set; }
    public string Title { get; set; }
    public string Content { get; set; }

    public int BlogId { get; set; }
    public Blog Blog { get; set; }
}

Neste exemplo, as propriedades realçadas serão usadas para configurar a relação.

Observação

Se a propriedade for a chave primária ou for de um tipo não compatível com a chave principal, ela não será configurada como a chave estrangeira.

Observação

Antes de EF Core 3,0, a propriedade denominada exatamente igual à propriedade da chave principal também foi correspondida como a chave estrangeira

Nenhuma propriedade de chave estrangeira

Embora seja recomendável ter uma propriedade de chave estrangeira definida na classe de entidade dependente, ela não é necessária. Se nenhuma propriedade de chave estrangeira for encontrada, uma propriedade de chave estrangeira de sombra será introduzida com o nome <navigation property name><principal key property name> ou <principal entity name><principal key property name> se nenhuma navegação estiver presente no tipo dependente.

public class Blog
{
    public int BlogId { get; set; }
    public string Url { get; set; }

    public List<Post> Posts { get; set; }
}

public class Post
{
    public int PostId { get; set; }
    public string Title { get; set; }
    public string Content { get; set; }

    public Blog Blog { get; set; }
}

Neste exemplo, a chave estrangeira de sombra é BlogId porque a prependência do nome de navegação seria redundante.

Observação

Se já existir uma propriedade com o mesmo nome, o nome da propriedade de sombra será sufixado com um número.

Propriedade de navegação única

Incluir apenas uma propriedade de navegação (sem navegação inversa e nenhuma propriedade de chave estrangeira) é suficiente para ter uma relação definida por convenção. Você também pode ter uma única propriedade de navegação e uma propriedade de chave estrangeira.

public class Blog
{
    public int BlogId { get; set; }
    public string Url { get; set; }

    public List<Post> Posts { get; set; }
}

public class Post
{
    public int PostId { get; set; }
    public string Title { get; set; }
    public string Content { get; set; }
}

Limitações

Quando há várias propriedades de navegação definidas entre dois tipos (ou seja, mais do que apenas um par de navegações que apontam entre si), as relações representadas pelas propriedades de navegação são ambíguas. Será necessário configurá-los manualmente para resolver a ambiguidade.

Excluir em cascata

Por convenção, a exclusão em cascata será definida como em cascata para relações necessárias e ClientSetNull para relações opcionais. Cascade significa que as entidades dependentes também são excluídas. ClientSetNull significa que as entidades dependentes que não estão carregadas na memória permanecerão inalteradas e deverão ser excluídas manualmente ou atualizadas para apontar para uma entidade principal válida. Para entidades que são carregadas na memória, EF Core tentará definir as propriedades de chave estrangeira como NULL.

Consulte a seção relações obrigatórias e opcionais para obter a diferença entre as relações obrigatórias e opcionais.

Consulte exclusão em cascata para obter mais detalhes sobre os diferentes comportamentos de exclusão e os padrões usados pela Convenção.

Configuração manual

Para configurar uma relação na API fluente, você começa identificando as propriedades de navegação que compõem a relação. HasOne ou HasMany identifica a propriedade de navegação no tipo de entidade no qual você está iniciando a configuração. Em seguida, você encadea uma chamada para WithOne ou WithMany para identificar a navegação inversa. HasOne/WithOnesão usadas para propriedades de navegação de referência e HasMany / WithMany são usadas para propriedades de navegação de coleção.

internal class MyContext : DbContext
{
    public DbSet<Blog> Blogs { get; set; }
    public DbSet<Post> Posts { get; set; }

    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        modelBuilder.Entity<Post>()
            .HasOne(p => p.Blog)
            .WithMany(b => b.Posts);
    }
}

public class Blog
{
    public int BlogId { get; set; }
    public string Url { get; set; }

    public List<Post> Posts { get; set; }
}

public class Post
{
    public int PostId { get; set; }
    public string Title { get; set; }
    public string Content { get; set; }

    public Blog Blog { get; set; }
}

Propriedade de navegação única

Se você tiver apenas uma propriedade de navegação, haverá sobrecargas sem parâmetros de WithOne e WithMany . Isso indica que há uma referência ou uma coleção conceitualmente na outra extremidade da relação, mas não há nenhuma propriedade de navegação incluída na classe de entidade.

internal class MyContext : DbContext
{
    public DbSet<Blog> Blogs { get; set; }
    public DbSet<Post> Posts { get; set; }

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

public class Blog
{
    public int BlogId { get; set; }
    public string Url { get; set; }

    public List<Post> Posts { get; set; }
}

public class Post
{
    public int PostId { get; set; }
    public string Title { get; set; }
    public string Content { get; set; }
}

Configurando propriedades de navegação

Observação

Esse recurso foi introduzido no EF Core 5,0.

Depois que a propriedade de navegação tiver sido criada, talvez seja necessário configurá-la ainda mais.

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

    modelBuilder.Entity<Blog>()
        .Navigation(b => b.Posts)
        .UsePropertyAccessMode(PropertyAccessMode.Property);
}

Observação

Essa chamada não pode ser usada para criar uma propriedade de navegação. Ele é usado apenas para configurar uma propriedade de navegação que foi criada anteriormente pela definição de uma relação ou de uma convenção.

Chave estrangeira

Você pode usar a API fluente para configurar qual propriedade deve ser usada como a propriedade de chave estrangeira para uma determinada relação:

internal class MyContext : DbContext
{
    public DbSet<Blog> Blogs { get; set; }
    public DbSet<Post> Posts { get; set; }

    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        modelBuilder.Entity<Post>()
            .HasOne(p => p.Blog)
            .WithMany(b => b.Posts)
            .HasForeignKey(p => p.BlogForeignKey);
    }
}

public class Blog
{
    public int BlogId { get; set; }
    public string Url { get; set; }

    public List<Post> Posts { get; set; }
}

public class Post
{
    public int PostId { get; set; }
    public string Title { get; set; }
    public string Content { get; set; }

    public int BlogForeignKey { get; set; }
    public Blog Blog { get; set; }
}

Chave estrangeira de sombra

Você pode usar a sobrecarga de cadeia de caracteres de HasForeignKey(...) para configurar uma propriedade de sombra como uma chave estrangeira (consulte Propriedades de sombra para obter mais informações). É recomendável adicionar explicitamente a propriedade Shadow ao modelo antes de usá-la como uma chave estrangeira (como mostrado abaixo).

internal class MyContext : DbContext
{
    public DbSet<Blog> Blogs { get; set; }
    public DbSet<Post> Posts { get; set; }

    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        // Add the shadow property to the model
        modelBuilder.Entity<Post>()
            .Property<int>("BlogForeignKey");

        // Use the shadow property as a foreign key
        modelBuilder.Entity<Post>()
            .HasOne(p => p.Blog)
            .WithMany(b => b.Posts)
            .HasForeignKey("BlogForeignKey");
    }
}

public class Blog
{
    public int BlogId { get; set; }
    public string Url { get; set; }

    public List<Post> Posts { get; set; }
}

public class Post
{
    public int PostId { get; set; }
    public string Title { get; set; }
    public string Content { get; set; }

    public Blog Blog { get; set; }
}

Nome da restrição de chave estrangeira

Por convenção, ao direcionar um banco de dados relacional, as restrições FOREIGN KEY são nomeadas como FK _ <dependent type name> _ <principal type name> _ <foreign key property name> . Para chaves estrangeiras compostas, <foreign key property name> torna-se uma lista separada por sublinhado de nomes de propriedade de chave estrangeira.

Você também pode configurar o nome da restrição da seguinte maneira:

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder.Entity<Post>()
        .HasOne(p => p.Blog)
        .WithMany(b => b.Posts)
        .HasForeignKey(p => p.BlogId)
        .HasConstraintName("ForeignKey_Post_Blog");
}

Sem propriedade de navegação

Você não precisa necessariamente fornecer uma propriedade de navegação. Você pode simplesmente fornecer uma chave estrangeira em um lado da relação.

internal class MyContext : DbContext
{
    public DbSet<Blog> Blogs { get; set; }
    public DbSet<Post> Posts { get; set; }

    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        modelBuilder.Entity<Post>()
            .HasOne<Blog>()
            .WithMany()
            .HasForeignKey(p => p.BlogId);
    }
}

public class Blog
{
    public int BlogId { get; set; }
    public string Url { get; set; }
}

public class Post
{
    public int PostId { get; set; }
    public string Title { get; set; }
    public string Content { get; set; }

    public int BlogId { get; set; }
}

Chave principal

Se desejar que a chave estrangeira referencie uma propriedade diferente da chave primária, você poderá usar a API Fluent para configurar a propriedade principal de chave para a relação. A propriedade que você configurar como a chave principal será configurada automaticamente como uma chave alternativa.

internal class MyContext : DbContext
{
    public DbSet<Car> Cars { get; set; }

    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        modelBuilder.Entity<RecordOfSale>()
            .HasOne(s => s.Car)
            .WithMany(c => c.SaleHistory)
            .HasForeignKey(s => s.CarLicensePlate)
            .HasPrincipalKey(c => c.LicensePlate);
    }
}

public class Car
{
    public int CarId { get; set; }
    public string LicensePlate { get; set; }
    public string Make { get; set; }
    public string Model { get; set; }

    public List<RecordOfSale> SaleHistory { get; set; }
}

public class RecordOfSale
{
    public int RecordOfSaleId { get; set; }
    public DateTime DateSold { get; set; }
    public decimal Price { get; set; }

    public string CarLicensePlate { get; set; }
    public Car Car { get; set; }
}

Relações obrigatórias e opcionais

Você pode usar a API fluente para configurar se a relação é necessária ou opcional. Por fim, isso controla se a propriedade de chave estrangeira é necessária ou opcional. Isso é mais útil quando você está usando uma chave estrangeira de estado de sombra. Se você tiver uma propriedade de chave estrangeira em sua classe de entidade, a exigência da relação será determinada com base em se a propriedade de chave estrangeira é necessária ou opcional (consulte as propriedades obrigatórias e opcionais para obter mais informações).

As propriedades de chave estrangeira estão localizadas no tipo de entidade dependente, portanto, se elas estiverem configuradas conforme necessário, isso significa que cada entidade dependente é necessária para ter uma entidade principal correspondente.

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder.Entity<Post>()
        .HasOne(p => p.Blog)
        .WithMany(b => b.Posts)
        .IsRequired();
}

Observação

IsRequired(false)A chamada também torna a propriedade de chave estrangeira opcional, a menos que seja configurada de outra forma.

Excluir em cascata

Você pode usar a API Fluent para configurar o comportamento de exclusão em cascata para uma determinada relação explicitamente.

Consulte exclusão em cascata para obter uma discussão detalhada de cada opção.

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder.Entity<Post>()
        .HasOne(p => p.Blog)
        .WithMany(b => b.Posts)
        .OnDelete(DeleteBehavior.Cascade);
}

Outros padrões de relação

Um para um

Relações um para um têm uma propriedade de navegação de referência em ambos os lados. Eles seguem as mesmas convenções que as relações um-para-muitos, mas um índice exclusivo é introduzido na propriedade Foreign Key para garantir que apenas um dependente esteja relacionado a cada entidade de segurança.

public class Blog
{
    public int BlogId { get; set; }
    public string Url { get; set; }

    public BlogImage BlogImage { get; set; }
}

public class BlogImage
{
    public int BlogImageId { get; set; }
    public byte[] Image { get; set; }
    public string Caption { get; set; }

    public int BlogId { get; set; }
    public Blog Blog { get; set; }
}

Observação

O EF escolherá uma das entidades como dependente, com base em sua capacidade de detectar uma propriedade de chave estrangeira. Se a entidade incorreta for escolhida como dependente, você poderá usar a API fluente para corrigir isso.

Ao configurar a relação com a API Fluent, você usa os HasOne WithOne métodos e.

Ao configurar a chave estrangeira, você precisa especificar o tipo de entidade dependente-Observe o parâmetro genérico fornecido HasForeignKey na lista abaixo. Em uma relação um-para-muitos, fica claro que a entidade com a navegação de referência é a dependente e aquela com a coleção é a principal. Mas isso não é tão em relação um-para-um-portanto, a necessidade de defini-lo explicitamente.

internal class MyContext : DbContext
{
    public DbSet<Blog> Blogs { get; set; }
    public DbSet<BlogImage> BlogImages { get; set; }

    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        modelBuilder.Entity<Blog>()
            .HasOne(b => b.BlogImage)
            .WithOne(i => i.Blog)
            .HasForeignKey<BlogImage>(b => b.BlogForeignKey);
    }
}

public class Blog
{
    public int BlogId { get; set; }
    public string Url { get; set; }

    public BlogImage BlogImage { get; set; }
}

public class BlogImage
{
    public int BlogImageId { get; set; }
    public byte[] Image { get; set; }
    public string Caption { get; set; }

    public int BlogForeignKey { get; set; }
    public Blog Blog { get; set; }
}

O lado dependente é considerado opcional por padrão, mas pode ser configurado conforme necessário. No entanto, o EF não validará se uma entidade dependente foi fornecida, portanto, essa configuração fará apenas uma diferença quando o mapeamento do banco de dados permitir que ela seja imposta. Um cenário comum para isso são tipos de propriedade de referência que usam divisão de tabela por padrão.

modelBuilder.Entity<Order>(
    ob =>
    {
        ob.OwnsOne(
            o => o.ShippingAddress,
            sa =>
            {
                sa.Property(p => p.Street).IsRequired();
                sa.Property(p => p.City).IsRequired();
            });

        ob.Navigation(o => o.ShippingAddress)
            .IsRequired();
    });

Com essa configuração, as colunas correspondentes a ShippingAddress serão marcadas como não anuláveis no banco de dados.

Observação

Se você estiver usando tipos de referência não anuláveis, a chamada IsRequired não será necessária.

Observação

A capacidade de configurar se o dependente é necessário foi introduzido no EF Core 5,0.

Muitos para muitos

Relações muitos para muitos exigem uma propriedade de navegação de coleção em ambos os lados. Eles serão descobertos por convenções como outros tipos de relações.

public class Post
{
    public int PostId { get; set; }
    public string Title { get; set; }
    public string Content { get; set; }

    public ICollection<Tag> Tags { get; set; }
}

public class Tag
{
    public string TagId { get; set; }

    public ICollection<Post> Posts { get; set; }
}

A maneira como essa relação é implementada no banco de dados é por uma tabela de junção que contém chaves estrangeiras para ambos Post e Tag . Por exemplo, é o que o EF criará em um banco de dados relacional para o modelo acima.

CREATE TABLE [Posts] (
    [PostId] int NOT NULL IDENTITY,
    [Title] nvarchar(max) NULL,
    [Content] nvarchar(max) NULL,
    CONSTRAINT [PK_Posts] PRIMARY KEY ([PostId])
);

CREATE TABLE [Tags] (
    [TagId] nvarchar(450) NOT NULL,
    CONSTRAINT [PK_Tags] PRIMARY KEY ([TagId])
);

CREATE TABLE [PostTag] (
    [PostsId] int NOT NULL,
    [TagsId] nvarchar(450) NOT NULL,
    CONSTRAINT [PK_PostTag] PRIMARY KEY ([PostsId], [TagsId]),
    CONSTRAINT [FK_PostTag_Posts_PostsId] FOREIGN KEY ([PostsId]) REFERENCES [Posts] ([PostId]) ON DELETE CASCADE,
    CONSTRAINT [FK_PostTag_Tags_TagsId] FOREIGN KEY ([TagsId]) REFERENCES [Tags] ([TagId]) ON DELETE CASCADE
);

Internamente, o EF cria um tipo de entidade para representar a tabela de junção que será referida como o tipo de entidade de junção. Dictionary<string, object> está sendo usado para lidar com qualquer combinação de propriedades de chave estrangeira, consulte tipos de entidade de conjunto de propriedades para obter mais informações. Mais de uma relação muitos para muitos pode existir no modelo, portanto, o tipo de entidade de junção deve receber um nome exclusivo, nesse caso PostTag . O recurso que permite isso é chamado de tipo de entidade de tipo compartilhado.

Importante

O tipo CLR usado para unir tipos de entidade por convenção pode ser alterado em versões futuras para melhorar o desempenho. Não dependa do tipo de junção, Dictionary<string, object> a menos que ele tenha sido explicitamente configurado, conforme descrito na próxima seção.

As navegações de muitos para muitos são chamadas de ignorar navegações à medida que ignoram efetivamente o tipo de entidade de junção. Se você estiver empregando a configuração em massa, todas as navegações ignorar poderão ser obtidas do GetSkipNavigations .

foreach (var entityType in modelBuilder.Model.GetEntityTypes())
{
    foreach (var skipNavigation in entityType.GetSkipNavigations())
    {
        Console.WriteLine(entityType.DisplayName() + "." + skipNavigation.Name);
    }
}

Configuração de tipo de entidade de junção

É comum aplicar a configuração ao tipo de entidade de junção. Essa ação pode ser realizada via UsingEntity .

modelBuilder
    .Entity<Post>()
    .HasMany(p => p.Tags)
    .WithMany(p => p.Posts)
    .UsingEntity(j => j.ToTable("PostTags"));

Os dados de semente de modelo podem ser fornecidos para o tipo de entidade de junção usando tipos anônimos. Você pode examinar a exibição de depuração de modelo para determinar os nomes de propriedade criados por convenção.

modelBuilder
    .Entity<Post>()
    .HasData(new Post { PostId = 1, Title = "First" });

modelBuilder
    .Entity<Tag>()
    .HasData(new Tag { TagId = "ef" });

modelBuilder
    .Entity<Post>()
    .HasMany(p => p.Tags)
    .WithMany(p => p.Posts)
    .UsingEntity(j => j.HasData(new { PostsPostId = 1, TagsTagId = "ef" }));

Os dados adicionais podem ser armazenados no tipo de entidade de junção, mas, para isso, é melhor criar um tipo CLR de bespoke. Ao configurar a relação com um tipo de entidade de junção personalizada, as chaves estrangeiras precisam ser especificadas explicitamente.

internal class MyContext : DbContext
{
    public MyContext(DbContextOptions<MyContext> options)
        : base(options)
    {
    }

    public DbSet<Post> Posts { get; set; }
    public DbSet<Tag> Tags { get; set; }

    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        modelBuilder.Entity<Post>()
            .HasMany(p => p.Tags)
            .WithMany(p => p.Posts)
            .UsingEntity<PostTag>(
                j => j
                    .HasOne(pt => pt.Tag)
                    .WithMany(t => t.PostTags)
                    .HasForeignKey(pt => pt.TagId),
                j => j
                    .HasOne(pt => pt.Post)
                    .WithMany(p => p.PostTags)
                    .HasForeignKey(pt => pt.PostId),
                j =>
                {
                    j.Property(pt => pt.PublicationDate).HasDefaultValueSql("CURRENT_TIMESTAMP");
                    j.HasKey(t => new { t.PostId, t.TagId });
                });
    }
}

public class Post
{
    public int PostId { get; set; }
    public string Title { get; set; }
    public string Content { get; set; }

    public ICollection<Tag> Tags { get; set; }
    public List<PostTag> PostTags { get; set; }
}

public class Tag
{
    public string TagId { get; set; }

    public ICollection<Post> Posts { get; set; }
    public List<PostTag> PostTags { get; set; }
}

public class PostTag
{
    public DateTime PublicationDate { get; set; }

    public int PostId { get; set; }
    public Post Post { get; set; }

    public string TagId { get; set; }
    public Tag Tag { get; set; }
}

Unindo a configuração de relações

O EF usa relações de 2 1 para muitos no tipo de entidade de junção para representar a relação muitos para muitos. Você pode configurar essas relações nos UsingEntity argumentos.

modelBuilder.Entity<Post>()
    .HasMany(p => p.Tags)
    .WithMany(p => p.Posts)
    .UsingEntity<Dictionary<string, object>>(
        "PostTag",
        j => j
            .HasOne<Tag>()
            .WithMany()
            .HasForeignKey("TagId")
            .HasConstraintName("FK_PostTag_Tags_TagId")
            .OnDelete(DeleteBehavior.Cascade),
        j => j
            .HasOne<Post>()
            .WithMany()
            .HasForeignKey("PostId")
            .HasConstraintName("FK_PostTag_Posts_PostId")
            .OnDelete(DeleteBehavior.ClientCascade));

Observação

A capacidade de configurar relações muitos para muitos foi introduzida no EF Core 5,0, para a versão anterior, use a abordagem a seguir.

Relações indiretas de muitos para muitos

Você também pode representar uma relação muitos para muitos adicionando apenas o tipo de entidade de junção e mapeando duas relações um-para-muitos separadas.

public class MyContext : DbContext
{
    public MyContext(DbContextOptions<MyContext> options)
        : base(options)
    {
    }

    public DbSet<Post> Posts { get; set; }
    public DbSet<Tag> Tags { get; set; }

    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        modelBuilder.Entity<PostTag>()
            .HasKey(t => new { t.PostId, t.TagId });

        modelBuilder.Entity<PostTag>()
            .HasOne(pt => pt.Post)
            .WithMany(p => p.PostTags)
            .HasForeignKey(pt => pt.PostId);

        modelBuilder.Entity<PostTag>()
            .HasOne(pt => pt.Tag)
            .WithMany(t => t.PostTags)
            .HasForeignKey(pt => pt.TagId);
    }
}

public class Post
{
    public int PostId { get; set; }
    public string Title { get; set; }
    public string Content { get; set; }

    public List<PostTag> PostTags { get; set; }
}

public class Tag
{
    public string TagId { get; set; }

    public List<PostTag> PostTags { get; set; }
}

public class PostTag
{
    public DateTime PublicationDate { get; set; }

    public int PostId { get; set; }
    public Post Post { get; set; }

    public string TagId { get; set; }
    public Tag Tag { get; set; }
}

Observação

O suporte para relações muitos-para-muitos do scaffolding do banco de dados ainda não foi adicionado. Consulte o problema de rastreamento.

Recursos adicionais