Tabela de ligação com Code First From Database usando Entity Framework

Dias atrás precisei desenvolver um sistema simples a partir de um banco de dados existente e tive dificuldades para trabalhar com relacionamento entre duas entidades através de uma tabela de ligação. Vejamos um exemplo de tabela de ligação na imagem abaixo:


Quando trabalhamos com Code First, a tabela de ligação acima(GeneroFilme) é gerada pelo próprio Entity Framework se definimos uma propriedade tipo ICollection para as entidades que se relacionam. Vejam...

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public class Filme
{
    public Filme()
    {
        Generos = new List();
    }
    public int FilmeId { get; set; }
    public string Nome { get; set; }
    public int? Ano { get; set; }
    public virtual ICollection Generos { get; set; } 
}
 
public class Genero
{
    public Genero()
    {
        Filmes = new List();
    }
    public int GeneroId { get; set; }
    public string Nome { get; set; }
    public virtual ICollection Filmes { get; set; }
}

Simples assim. Vamos ver agora como trabalhar com a tabela de ligação a partir de um banco existente.

Passo 1 - Banco de Dados
Abaixo o modelo de Banco de Dados que iremos trabalhar:
EFTableLink02
Não há necessidade da tabela GENEROS_FILMES possuir uma chave primária(GENERO_FILME_ID). Criei apenas para ilustrar um cenário que encontramos na maioria dos bancos de dados por aí.

Passo 2 - Criação do Projeto
Vamos criar um projeto ASP.NET Web Application, template MVC e sem autenticação:
EFTableLink03

Passo 3 - Criação dos modelos via Code First from Database
Com o projeto MVC criado, clique com o botão direito na pasta Models -> Add -> New Item. Selecione o tipo ADO.NET Entity Data Model.
EFTableLink04

Em seguida selecione "Code First from Database" e clique em Next.
EFTableLink05

Na tela seguinte clique em "New Connection"
EFTableLink06

Preencha com os dados da sua conexão do banco de dados e clique em OK. A tela de seleção da conexão será apresentada novamente, clique em Next.
EFTableLink07

Na próxima tela selecione as tabelas para as quais serão criadas as classes. Importante! Não selecione a tabela de ligação, pois não precisamos de uma classe de modelo para essa tabela.

Finalizada a configuração do Entity Data Model, vamos trabalhar na classe de contexto e modelos.

Passo 4 - Classe de contexto e modelos
Na pasta Models, foram criadas as classes abaixo:
EFTableLink09

Para não estender muito esse post, vou manter os nomes das classes e atributos conforme vieram do banco de dados. É recomendável utilizar as convenções de nome das entidades e atributos, "Filme", "Genero" e etc... mas não é o foco desse post. Clique aqui para saber como.

Vamos alterar a classe de contexto, EFContext.cs, que foi gerada automaticamente. Precisamos alterar o método OnModelCreating adicionando o relacionamento N pra N entre FILME e GENERO e especificar qual será a tabela de ligação já existente no banco, nesse caso a tabela "GENEROS_FILMES".
EFContext.cs - Antes

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public partial class EFContext : DbContext
{
    public EFContext()
        : base("name=EFContext")
    {
    }
 
    public virtual DbSet FILMES { get; set; }
    public virtual DbSet GENEROS { get; set; }
 
    protected override void OnModelCreating(DbModelBuilder modelBuilder)
    {
        modelBuilder.Entity()
            .Property(e => e.NOME)
            .IsUnicode(false);
 
        modelBuilder.Entity()
            .Property(e => e.NOME)
            .IsUnicode(false);
    }
}

EFContext.cs - Depois

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public partial class EFContext : DbContext
{
    public EFContext()
        : base("name=EFContext")
    {
    }
 
    public virtual DbSet FILMES { get; set; }
    public virtual DbSet GENEROS { get; set; }
 
    protected override void OnModelCreating(DbModelBuilder modelBuilder)
    {
	modelBuilder.Entity()
            .HasMany(f => f.FILMES).WithMany(g => g.GENEROS)
            .Map(t => t.MapLeftKey("GENERO_ID")
                .MapRightKey("FILME_ID")
                .ToTable("GENEROS_FILMES"));
    }
}

Altere as classes FILME.cs e GENERO.cs conforme abaixo
FILME.cs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
[Table("FILMES")]
public partial class FILME
{
    public FILME()                          //added
    {
        GENEROS = new HashSet();    //added
    }
    [Key]
    public int FILME_ID { get; set; }
 
    [Required]
    [StringLength(50)]
    public string NOME { get; set; }
 
    public int? ANO { get; set; }
 
    public virtual ICollection GENEROS { get; set; }    //added
}

Na classe acima, adicionamos o método construtor, linhas 4 a 7, para que não receba um valor nulo e relacionamos uma ICollection através da propriedade Generos na linha 17. Faça o mesmo para a classe GENERO.
GENERO.cs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
[Table("GENEROS")]
public partial class GENERO
{
    public GENERO()                         //added
    {
        FILMES = new HashSet();      //added
    }
    [Key]
    public int GENERO_ID { get; set; }
 
    [Required]
    [StringLength(50)]
    public string NOME { get; set; }
 
    public virtual ICollection FILMES { get; set; }  //added
}

Já temos os modelos e propriedades mapeados e estamos prontos para começar a trabalhar com o banco de dados existente. Vamos agora criar alguns CRUDS para inserção de Generos e Filmes.

Passo 6 - ViewModel para atribuição de Generos
Ao cadastrar um novo filme, vamos utilizar checkbox de seleção múltipla do gênero. Para isso vamos criar uma ViewModel no projeto e dentro dela, uma classe chamada GenerosAtribuidos.cs

1
2
3
4
5
6
public class GenerosAtribuidos
{
    public int GeneroId { get; set; }
    public string Nome { get; set; }
    public bool Atribuido { get; set; }
}

Passo 7 - Criação das Controllers e Views
Vamos criar a controller da entidade FILME conforme abaixo. Se preferir, utilize o scaffold e altere os métodos na controller depois.
FilmesController.cs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
public class FilmesController : Controller
{
    private EFContext db = new EFContext();
 
    // GET: Filmes
    public ActionResult Index()
    {
        var filme = new FILME();
        filme.GENEROS = new List();
        PopularGenerosAtribuidos(filme);
        return View(db.FILMES.ToList());
    }
 
    // GET: Filmes/Create
    public ActionResult Create()
    {
        var filme = new FILME();
        filme.GENEROS = new List();
        PopularGenerosAtribuidos(filme);
        return View();
    }
 
    // POST: Filmes/Create
    [HttpPost]
    [ValidateAntiForgeryToken]
    public ActionResult Create([Bind(Include = "FILME_ID,NOME,ANO")] FILME filme, string[] selectedGeneros)
    {
        if (selectedGeneros != null)
        {
            filme.GENEROS = new List();
            foreach (var item in selectedGeneros)
            {
                var generoToAdd = db.GENEROS.Find(int.Parse(item));
                filme.GENEROS.Add(generoToAdd);
            }
        }
        if (ModelState.IsValid)
        {
            db.FILMES.Add(filme);
            db.SaveChanges();
            return RedirectToAction("Index");
        }
        PopularGenerosAtribuidos(filme);
        return View(filme);
    }
 
    // GET: Filmes/Delete/5
    public ActionResult Delete(int? id)
    {
        if (id == null)
        {
            return new HttpStatusCodeResult(HttpStatusCode.BadRequest);
        }
        FILME filme = db.FILMES.Find(id);
        if (filme == null)
        {
            return HttpNotFound();
        }
        return View(filme);
    }
 
    // POST: Filmes/Delete/5
    [HttpPost, ActionName("Delete")]
    [ValidateAntiForgeryToken]
    public ActionResult DeleteConfirmed(int id)
    {
        FILME filme = db.FILMES.Find(id);
 
        // deletar referencias em GENEROS_FILMES ao excluir Filme
        var _filme = (from s in db.FILMES where s.FILME_ID == id select s).FirstOrDefault();
        foreach (GENERO item in _filme.GENEROS.ToList())
        {
            _filme.GENEROS.Remove(item);
        }
 
        db.FILMES.Remove(filme);
        try
        {
            db.SaveChanges();
        }
        catch
        {
            return View("~/Views/Shared/Error.cshtml");
        }
        return RedirectToAction("Index");
    }
 
    private void PopularGenerosAtribuidos(FILME filme)
    {
        var allGeneros = db.GENEROS;
        var filmeGeneros = new HashSet(filme.GENEROS.Select(c => c.GENERO_ID));
        var viewModel = new List();
        foreach (var item in allGeneros)
        {
            viewModel.Add(new GenerosAtribuidos
            {
                GeneroId = item.GENERO_ID,
                Nome = item.NOME,
                Atribuido = filmeGeneros.Contains(item.GENERO_ID)
            });
        }
        ViewBag.Generos = viewModel;
    }
    protected override void Dispose(bool disposing)
    {
        if (disposing)
        {
            db.Dispose();
        }
        base.Dispose(disposing);
    }
}

Filmes/Index.cshtml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
@model IEnumerable<EFManyToManyFromDatabase.Models.FILME>
 
@{
    ViewBag.Title = "Index";
}
 
<h2>Index</h2>
 
<p>
    @Html.ActionLink("Create New", "Create")
</p>
<table class="table">
    <tr>
        <th>
            @Html.DisplayNameFor(model => model.NOME)
        </th>
        <th>
            @Html.DisplayNameFor(model => model.ANO)
        </th>
        <th>
            GENEROS
        </th>
        <th></th>
    </tr>
 
    @foreach (var item in Model) {
    <tr>
        <td>
            @Html.DisplayFor(modelItem => item.NOME)
        </td>
        <td>
            @Html.DisplayFor(modelItem => item.ANO)
        </td>
        <td>
                @{
                    foreach (var genero in item.GENEROS)
                    {
                        @:  @genero.NOME;
                    }
                }
            </td>
        <td>
            @Html.ActionLink("Delete", "Delete", new { id=item.FILME_ID })
        </td>
    </tr>
    }
</table>

Filmes/Create.cshtml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
@model EFManyToManyFromDatabase.Models.FILME
 
@{
    ViewBag.Title = "Create";
}
 
<h2>Create</h2>
 
@using (Html.BeginForm()) 
{
    @Html.AntiForgeryToken()
 
    <div class="form-horizontal">
        <h4>FILME</h4>
        <hr />
        @Html.ValidationSummary(true, "", new { @class = "text-danger" })
        <div class="form-group">
            @Html.LabelFor(model => model.NOME, htmlAttributes: new { @class = "control-label col-md-2" })
            <div class="col-md-10">
                @Html.EditorFor(model => model.NOME, new { htmlAttributes = new { @class = "form-control" } })
                @Html.ValidationMessageFor(model => model.NOME, "", new { @class = "text-danger" })
            </div>
        </div>
 
        <div class="form-group">
            @Html.LabelFor(model => model.ANO, htmlAttributes: new { @class = "control-label col-md-2" })
            <div class="col-md-10">
                @Html.EditorFor(model => model.ANO, new { htmlAttributes = new { @class = "form-control" } })
                @Html.ValidationMessageFor(model => model.ANO, "", new { @class = "text-danger" })
            </div>
        </div>
        <div class="form-group">
            <div class="col-md-offset-2 col-md-10">
                <table>
                    <tr>
                        @{
                            int cnt = 0;
                            List<EFManyToManyFromDatabase.ViewModels.GenerosAtribuidos> generos = ViewBag.Generos;
 
                            foreach (var item in generos)
                            {
                                if (cnt++ % 3 == 0)
                                {
                                    @:</tr><tr>
                                }
                                @:<td>
                                    <input type="checkbox"
                                        name="selectedGeneros"
                                        value="@item.GeneroId"
                                        @(Html.Raw(item.Atribuido ? "checked=\"checked\"" : "")) />
                                        @item.Nome
                                @:</td>
                            }
                            @:</tr>
                        }
                </table>
            </div>
        </div>
        <div class="form-group">
            <div class="col-md-offset-2 col-md-10">
                <input type="submit" value="Create" class="btn btn-default" />
            </div>
        </div>
    </div>
}
 
<div>
    @Html.ActionLink("Back to List", "Index")
</div>
 
@section Scripts {
    @Scripts.Render("~/bundles/jqueryval")
}

Finalizamos o projeto. Para gerar crud do model Genero, utilize o scaffold. Basicamente, teremos um cadastro de Generos e Filmes, onde no cadastro dos filmes, teremos os generos disponíveis com checkbox de múltipla seleção. Abaixo, algumas telas do sistema:
EFTableLink10

EFTableLink12

Até a próxima!

Leave a Reply

Your email address will not be published. Required fields are marked *

This blog is kept spam free by WP-SpamFree.