<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Fábio César de Almeida</title>
    <description>The latest articles on DEV Community by Fábio César de Almeida (@fcalmeida).</description>
    <link>https://dev.to/fcalmeida</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F629165%2Fa7bb2a82-57da-4d61-b609-265cc7fd5ae2.jpg</url>
      <title>DEV Community: Fábio César de Almeida</title>
      <link>https://dev.to/fcalmeida</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/fcalmeida"/>
    <language>en</language>
    <item>
      <title>EF Core 5 Many to Many - Eliminando a tabela de junção em relacionamento de muitos para muitos</title>
      <dc:creator>Fábio César de Almeida</dc:creator>
      <pubDate>Tue, 22 Mar 2022 22:24:12 +0000</pubDate>
      <link>https://dev.to/fcalmeida/ef-core-5-many-to-many-eliminando-a-tabela-de-juncao-em-relacionamento-de-muitos-para-muitos-1l16</link>
      <guid>https://dev.to/fcalmeida/ef-core-5-many-to-many-eliminando-a-tabela-de-juncao-em-relacionamento-de-muitos-para-muitos-1l16</guid>
      <description>&lt;p&gt;Este é um exemplo de como criar um relacionamento many to many sem a necessidade de criar uma entidade de junção, e podendo mapear para uma tabela de banco de dados de forma explícita.&lt;/p&gt;

&lt;h3&gt;
  
  
  Novidade Entity Framework Core 5
&lt;/h3&gt;

&lt;p&gt;EF Core 5 trouxe um recurso bastante interessante e esperado por muitos programadores, e ao mesmo tempo, ainda não é muito conhecido. Se fizermos uma pesquisa sobre o assunto quase não vamos encontrar exemplos de como fazer um relacionamento many to many sem precisar usar uma entidade de junção usando fluent API para fazer o mapeamento para a tabela do banco de dados.&lt;/p&gt;

&lt;h2&gt;
  
  
  Sobre o exemplo
&lt;/h2&gt;

&lt;p&gt;O exemplo apresentado mostra como criar e fazer um relacionamento de muitos para muitos entre usuários e grupo de usuário, onde um usuário pode participar de mais de um grupo e os grupos podem ter mais de um usuário. Em outras palavras um clássico relacionamento de muitos para muitos.&lt;/p&gt;

&lt;h2&gt;
  
  
  EFCore3.Many2Many
&lt;/h2&gt;

&lt;p&gt;Esse projeto usa a versão 3 do EF Core, e implementa uma tabela de junção para fazer o relacionamento entre os usuários e grupos. O exemplo funciona nas versões mais recentes também, como a 5 e 6.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;public class User
{
    public int UserID { get; set; }
    public string UserName { get; set; }

    public virtual ICollection&amp;lt;UserGroup&amp;gt; UserGroup { get; set; } = new List&amp;lt;UserGroup&amp;gt;();
}

public class Group
{
   public int GroupID { get; set; }
   public string Name { get; set; }

   public virtual ICollection&amp;lt;UserGroup&amp;gt; UserGroup { get; set; } = new List&amp;lt;UserGroup&amp;gt;();
}
public class UserGroup
{
   public int UserID { get; set; }
   public int GroupID { get; set; }

   public virtual Group Group { get; set; }
   public virtual User User { get; set; }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Observe que temos três entidades, uma que abstrai o usuário, outra o grupo de usuário e uma terceira que faz a junção de muitos para muitos. As entidades usuário e grupo contém uma coleção de usuários e grupos (UserGroup) que é um relacionamento com a entidade de junção.&lt;br&gt;
Embora isso funcione, não é bem isso que esperamos de um ORM. O ideal é que ele de alguma forma faça a junção de forma implícita, sem a necessidade de criar uma entidade só para este fim.&lt;br&gt;
Para definir o nome da tabela de junção (do banco de dados), as FKs e o nome delas, usando fluente API, podemos fazer da seguinte forma:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;public class UserGroupConfig : IEntityTypeConfiguration&amp;lt;UserGroup&amp;gt;
{
    public void Configure(EntityTypeBuilder&amp;lt;UserGroup&amp;gt; builder)
    {
         builder.HasKey(t =&amp;gt; new { t.UserID, t.GroupID }).HasName("pk_user_group");

         builder.ToTable("tb_user_group");

         builder.Property(t =&amp;gt; t.UserID)
                .HasColumnName("UserID")
                .ValueGeneratedOnAdd()
                .IsRequired()
                .HasColumnType("int");

         builder.Property(t =&amp;gt; t.GroupID)
                .HasColumnName("GroupID")
                .ValueGeneratedOnAdd()
                .IsRequired()
                .HasColumnType("int");

         builder.HasOne(t =&amp;gt; t.User)
                .WithMany(t =&amp;gt; t.UserGroup)
                .HasForeignKey(t =&amp;gt; t.UserID)
                .HasConstraintName("fk_user_group_user");

         builder.HasOne(t =&amp;gt; t.Group)
                .WithMany(t =&amp;gt; t.UserGroup)
                .HasForeignKey(t =&amp;gt; t.GroupID)
                .HasConstraintName("fk_group_group_user");
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Abaixo fazemos o mapeamento da entidade de junção para a tabela do banco de dados, e conseguimos definir os nomes.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;         builder.HasOne(t =&amp;gt; t.User)
                .WithMany(t =&amp;gt; t.UserGroup)
                .HasForeignKey(t =&amp;gt; t.UserID)
                .HasConstraintName("fk_user_group_user");

         builder.HasOne(t =&amp;gt; t.Group)
                .WithMany(t =&amp;gt; t.UserGroup)
                .HasForeignKey(t =&amp;gt; t.GroupID)
                .HasConstraintName("fk_group_group_user");
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;No momento de adicionar um novo relacionamento, não é alguma coisa muito bonitinha, você precisa fazer o relacionamento criado uma nova instancia da entidade de junção.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;    var user = new User { UserName = "José da Silva" };

    context.User.Add(user); // Adiciona um novo usuário ao Contexto

    var group = new Group { Name = "Administradores" };

    // Relaciona o usuário ao Novo Grupo ***** Com entidade de junção
    group.UserGroup.Add(new UserGroup { UserID = user.UserID });

     // Adiciona um novo grupo ao Contexto
     context.Group.Add(group);

     // Persiste na banco de dados (in memory)
     context.SaveChanges();
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;E para recuperar também é chatinho, veja:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;    foreach (var itemUser in users)
        foreach (var itemGroup in itemUser.UserGroup) // Obtém valores many to many com entidade de relacionamento
            Console.WriteLine(itemUser.UserName + " é membro do grupo: " + itemGroup.Group.Name);

    foreach (var itemGroup in groups)
        foreach (var itemUser in itemGroup.UserGroup) // Obtém valores many to many com entidade de relacionamento
            Console.WriteLine(itemGroup.Name + " tem como membro o usuário: " + itemUser.User.UserName);

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  EFCore5.Many2Many
&lt;/h2&gt;

&lt;p&gt;Com o EF Core 5, as coisas ficam um pouco mais claras. Usaremos o mesmo exemplo, porém vamos fazer o mapeamento sem precisar criar uma entidade de junção.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;public class User
{
    public int UserID { get; set; }
    public string UserName { get; set; }

    public virtual ICollection&amp;lt;Group&amp;gt; Groups { get; set; } = new List&amp;lt;Group&amp;gt;();
}

public class Group
{
    public int GroupID { get; set; }
    public string Name { get; set; }

    public virtual ICollection&amp;lt;User&amp;gt; Users { get; set; } = new List&amp;lt;User&amp;gt;();
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Observe que agora não existe mais a entidade de junção e o relacionamento é direto entre usuário e grupo.&lt;br&gt;
Neste ponto o mapeamento das entidades para o banco de dados fica bastante simples, e não precisamos de muitas declarações para conseguirmos um resultado melhor. &lt;br&gt;
Neste caso eu fiz o mapeamento na própria classe GroupConfig.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;public void Configure(EntityTypeBuilder&amp;lt;Group&amp;gt; builder)
{
    builder.HasKey(t =&amp;gt; t.GroupID).HasName("pk_tb_group");

    builder.ToTable("tb_group");

    builder.Property(t =&amp;gt; t.GroupID)
           .HasColumnName("GroupID")
           .ValueGeneratedOnAdd()
           .IsRequired()
           .HasColumnType("int");

     builder.Property(t =&amp;gt; t.Name)
            .HasColumnName("Name")
            .HasColumnType("varchar")
            .HasMaxLength(100)
            .IsRequired();

    builder.HasMany(t =&amp;gt; t.Users)
           .WithMany(t =&amp;gt; t.Groups)
           .UsingEntity&amp;lt;Dictionary&amp;lt;string, object&amp;gt;&amp;gt;("UserGroup",
                leftTable =&amp;gt; leftTable.HasOne&amp;lt;User&amp;gt;()
                                      .WithMany()
                                      .HasForeignKey("UserID")
                                      .HasConstraintName("fk_user_group_user"),
                rightTable =&amp;gt; rightTable.HasOne&amp;lt;Group&amp;gt;()
                                      .WithMany()
                                      .HasForeignKey("GroupID")
                                      .HasConstraintName("fk_group_group_user"),
                j =&amp;gt;
                    {
                        j.HasKey("UserID", "GroupID").HasName("pk_group_user");
                        j.ToTable("tb_group_user");
                    });
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;No EF Core 5 podemos usar o UsingEntity para especificarmos uma junção, e isso é bem simples como o exemplo acima.&lt;/p&gt;

&lt;p&gt;Para adicionar a informação ao contexto também é mais direto, claro e fácil, veja:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;    var user = new User { UserName = "José da Silva" };

    context.User.Add(user); // Adiciona um novo usuário ao Contexto

    var group = new Group { Name = "Administradores" };

    // Relaciona o usuário ao Novo Grupo ***** Sem entidade de junção
    group.Users.Add(user);

    // Adiciona um novo grupo ao Contexto
    context.Group.Add(group);

    // Persiste na banco de dados (in memory)
    context.SaveChanges();
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;E a recuperação fica bem mais limpa e fácil.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;    foreach (var itemUser in users)
        foreach (var itemGroup in itemUser.Groups) // Obtém valores many to many sem entidade de relacionamento
            Console.WriteLine(itemUser.UserName + " é membro do grupo: " + itemGroup.Name);

    foreach (var itemGroup in groups)
        foreach (var itemUser in itemGroup.Users) // Obtém valores many to many sem entidade de relacionamento
            Console.WriteLine(itemGroup.Name + " tem como membro o usuário: " + itemUser.UserName);
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h1&gt;
  
  
  Conclusão
&lt;/h1&gt;

&lt;p&gt;O EF Core 5 é possível eliminar a tabela de junção, que e muito casos tornará a programação mais limpa e prática.&lt;/p&gt;

&lt;h1&gt;
  
  
  GitHub
&lt;/h1&gt;

&lt;p&gt;&lt;a href="https://github.com/fcalmeida/EFCore.Many2Many" rel="noopener noreferrer"&gt;Código de Exemplo&lt;/a&gt;&lt;/p&gt;

</description>
      <category>dotnet</category>
      <category>braziliandevs</category>
      <category>entityframeworkcore</category>
      <category>tutorial</category>
    </item>
  </channel>
</rss>
