.NET - Entity Framework 5 - Operações CRUD (revisitado) - 2


Na primeira parte do artigo definimos no nosso modelo de entidades usando o fluxo Model First e criamos as entidades Categoria e Produto e geramos o nosso banco de dados.

Vamos agora criar uma aplicação Windows Forms e realizar as operações CRUD no nosso modelo de entidades persistindo o resultado no banco de dados usando o Entity Framework 5 com LINQ to Entities.

Em nossa aplicação Windows Forms vamos implementar as seguintes funcionalidades:

Obs: Eu não vou criar uma camada de acesso aos dados para tornar o artigo mais simples visto que o objetivo é mostrar como realizar as operações CRUD usando o EF5.

Vamos aproveitar o formulário form1.vb criado no projeto Windows forms e alterar o seu nome para frmMenu.vb.

A seguir vamos incluir a partir da ToolBox o controle MenuStrip e definir um menu no formulário com as seguintes opções:

No menu PROJECT clique em Add Windows Forms e inclua os formulários : form1.vb, form2.vb, form3.vb, form4.vb e form5.vb que iremos usar no projeto.

Agora vamos incluir o código que irá implementar a chamada aos formulários do projeto:

Public Class frmMenu

    Private Sub ManutençãoToolStripMenuItem_Click(sender As Object, e As EventArgs) Handles ManutençãoToolStripMenuItem.Click
        'manutenção categoria
        My.Forms.Form1.Show()
    End Sub

    Private Sub ManutençãoToolStripMenuItem1_Click(sender As Object, e As EventArgs) Handles ManutençãoToolStripMenuItem1.Click
        'manutenção produtos
        My.Forms.Form2.Show()
    End Sub

    Private Sub ConsultaProdutosToolStripMenuItem_Click(sender As Object, e As EventArgs) Handles ConsultaProdutosToolStripMenuItem.Click
        'consulta produtos por categoria
        My.Forms.Form3.Show()
    End Sub

    Private Sub SairToolStripMenuItem_Click(sender As Object, e As EventArgs) Handles SairToolStripMenuItem.Click
        'encerrar aplicação
        If (MessageBox.Show("Confirma saída ? ", "Sair", MessageBoxButtons.YesNo, MessageBoxIcon.Information) = Windows.Forms.DialogResult.Yes) Then
            Application.Exit()
        End If
    End Sub
     Private Sub ExibirToolStripMenuItem_Click(sender As Object, e As EventArgs) Handles ExibirCategoriasToolStripMenuItem.Click
        'exibir categorias
        My.Forms.Form4.Show()
    End Sub

    Private Sub ExibirProdutosToolStripMenuItem_Click(sender As Object, e As EventArgs) Handles ExibirProdutosToolStripMenuItem.Click
        'exibir produtos
        My.Forms.Form5.Show()
    End Sub
End Class

Estamos usando o recurso My.Forms do Visual Basic para simplificar a chamada aos formulários do projeto.

Manutenção de Categorias

No menu Project clique em Add Windows Forms e aceite o nome padrão form1.vb.

A seguir a partir da ToolBox inclua os seguintes controles no formulário:

Defina os controles no formulário conforme o leiaute abaixo:

Logo após o início da declaração do formulário declaramos a variável codigo que será usada em todo o formulário:

Dim codigo As Integer = Nothing

1- Definindo o código do botão Localizar

Private Sub btnLocalizar_Click(sender As Object, e As EventArgs) Handles btnLocalizar.Click
        If String.IsNullOrEmpty(txtCodigo.Text) Then
            MessageBox.Show("Informe o código da categoria", "Localizar", MessageBoxButtons.OK, MessageBoxIcon.Information)
            Return
        Else
            codigo = Convert.ToInt32(txtCodigo.Text)
        End If

        Try
            Using ctx As New ProdutoContext
                Dim categoria = (From cat In ctx.Categorias Where cat.CategoriaId = codigo).Single
                txtNome.Text = categoria.Nome
            End Using
        Catch ex As Exception
            MessageBox.Show("Erro " + ex.Message, "Erro::Localizar", MessageBoxButtons.OK, MessageBoxIcon.Error)
        End Try
    End Sub

O código usa o recurso using e cria uma nova instância do contexto usando a seguir uma consulta LINQ para retornar uma entidade categoria cujo código seja igual ao informado.

Na consulta usamos o método de extensão Single que retorna uma única entidade. Se nada for encontrado será retornado uma exceção. Para contornar isso seria mais indicado usar SingleOrDefault como veremos mais adiante.

2- Incluindo Categorias

  Private Sub btnIncluir_Click(sender As Object, e As EventArgs) Handles btnIncluir.Click

        Try
            Using ctx As New ProdutoContext
                Dim novaCategoria As New Categoria
                novaCategoria.Nome = txtNome.Text
                ctx.Categorias.Add(novaCategoria)
                ctx.SaveChanges()
                MessageBox.Show("Categoria incluída com sucesso : " & novaCategoria.Nome.ToString, "Incluir", MessageBoxButtons.OK, MessageBoxIcon.Information)
                txtNome.Text = ""
                txtNome.Focus()
            End Using

        Catch ex As Exception
            MessageBox.Show("Erro " + ex.Message, "Erro::Incluir", MessageBoxButtons.OK, MessageBoxIcon.Error)
        End Try

    End Sub

No código criamos uma instância de Categoia e atribuímos os valores incluindo a entidade no contexto via método Add. Para persistir a nova entidade usamos o método SaveChanges().

3- Excluindo Categorias

Private Sub btnExcluir_Click(sender As Object, e As EventArgs) Handles btnExcluir.Click
        If String.IsNullOrEmpty(txtCodigo.Text) Then
            MessageBox.Show("Informe o código da categoria", "Excluir", MessageBoxButtons.OK, MessageBoxIcon.Information)
            Return
        Else
            codigo = Convert.ToInt32(txtCodigo.Text)
        End If

        Try
            Using ctx As New ProdutoContext
                Dim categ As Categoria = ctx.Categorias.First(Function(cat) cat.CategoriaId = codigo)
                txtNome.Text = categ.Nome
                ctx.Categorias.Remove(categ)
                ctx.SaveChanges()
            End Using
        Catch ex As Exception
            MessageBox.Show("Erro " + ex.Message, "Erro::Excluir", MessageBoxButtons.OK, MessageBoxIcon.Error)
        End Try

    End Sub

Aqui usamos uma expressão lambda para localizar a entidade Categoria que queremos excluir e a seguir usamos o método Remove para excluir a entidade do contexto persistindo a operação com SaveChanges.

4- Alterando Categorias

 Private Sub btnAlterar_Click(sender As Object, e As EventArgs) Handles btnAlterar.Click
        If String.IsNullOrEmpty(txtCodigo.Text) Then
            MessageBox.Show("Informe o código da categoria", "Alterar", MessageBoxButtons.OK, MessageBoxIcon.Information)
            Return
        Else
            codigo = Convert.ToInt32(txtCodigo.Text)
        End If
        If String.IsNullOrEmpty(txtNome.Text) Then
            MessageBox.Show("Informe o nome da categoria", "Alterar", MessageBoxButtons.OK, MessageBoxIcon.Information)
            Return
        End If

        Try
            Using ctx As New ProdutoContext
                Dim categoria = (From cat In ctx.Categorias Where cat.CategoriaId = codigo).SingleOrDefault
                If IsNothing(categoria) Then
                    MessageBox.Show("Categoria não localizada", "Localizar", MessageBoxButtons.OK, MessageBoxIcon.Information)
                    Return
                End If
                categoria.Nome = txtNome.Text
                ctx.SaveChanges()
            End Using
        Catch ex As Exception
            MessageBox.Show("Erro " + ex.Message, "Erro::Alterar", MessageBoxButtons.OK, MessageBoxIcon.Error)
        End Try
    End Sub

Na expressão LINQ usada para localizar a entidade que vamos alterar usamos o método de extensão SingleOrDefault. Se nada for encontrado este método irá retornar Nothing (null). Por isso verificamos se a entidade categoria é null. A seguir atribuímos o novo valor a entidade categoria e salvamos o resultado com SaveChanges().

Exibindo Categorias

O formulário form4.vb será usado para exibir as Categorias cadastradas no banco de dados. Vamos apenas retornar todas as categorias exibindo-as em um controle DataGridView.

Inclua no formulário form4.vb um controle GroupBox - grpCategorias, um controle DataGridView - gdvCategorias e um controle Button - btnSair , conforme o leiaute abaixo:

A seguir inclua o código abaixo neste formulário:

Public Class Form4
    Private Sub Form4_Load(sender As Object, e As EventArgs) Handles MyBase.Load
        carregaGrid()
    End Sub
    
    Private Sub carregaGrid()
        Dim ctx As New ProdutoContext
        Try
            Dim categorias = From cat In ctx.Categorias Select cat.CategoriaId, cat.Nome
            grpCategorias.Text = "Categorias : " & categorias.Count
            gdvCategorias.DataSource = categorias.ToList()
        Catch ex As Exception
            MessageBox.Show("Erro " + ex.Message, "Erro::Exibir", MessageBoxButtons.OK, MessageBoxIcon.Error)
        Finally
            ctx.Dispose()
        End Try
    End Sub

    Private Sub btnSair_Click(sender As Object, e As EventArgs) Handles btnSair.Click
        Me.Close()
    End Sub
End Class

A rotina CarregaGrid() seleciona todas as entidades categorias e exibindo o código e nome de cada uma delas no datagridview.

Manutenção de Produtos

O código da manutenção de Produtos é quase idêntico ao das Categorias por isso irei apenas exibir o código para cada uma das funcionalidades CRUD.

Abaixo vemos o formulário form2.vb que usa os mesmos controles do formulário para Categorias com exceção do controle Combobox - cboCategorias - que irá exibir as categorias cadastradas.

Logo após o início da declaração do formulário declaramos a variável codigo que será usada em todo o formulário:

Dim codigo As Integer = Nothing

1- Carregando a combobox com as categorias cadastradas

Private Sub carregaCombo()
        Try
            'cria uma instância do Contexto
            Using ctx As New ProdutoContext
                Dim consulta = ctx.Categorias.ToList()
                cboCategoria.DataSource = consulta
                cboCategoria.DisplayMember = "Nome"
                cboCategoria.ValueMember = "CategoriaId"
            End Using
        Catch ex As Exception
            MsgBox("Erro : " & ex.Message)
        End Try
    End Sub

Na rotina CarregaCombo obtemos todas as categorias com uma consulta LINQ e atribuímos o resultado a propriedade DataSource da Combobox definindo na sequência o nome a ser exibido com DisplayMember e o valor que será retornado da seleção com ValueMember.

A rotian carregaCombo é chamada no evento Load do formulário:

Private Sub Form2_Load(sender As Object, e As EventArgs) Handles MyBase.Load
        carregaCombo()
    End Sub

A seguir temos o código para cada um dos eventos Click dos demais botões de comando: Localizar, Incluir, Excluir e Alterar

Private Sub btnLocalizar_Click(sender As Object, e As EventArgs) Handles btnLocalizar.Click
        If String.IsNullOrEmpty(txtCodigo.Text) Then
            MessageBox.Show("Informe o código do produto", "Localizar", MessageBoxButtons.OK, MessageBoxIcon.Information)
            Return
        Else
            codigo = Convert.ToInt32(txtCodigo.Text)
        End If

        Try
            Using ctx As New ProdutoContext
                Dim produto = (From prod In ctx.Produtos Where prod.ProdutoId = codigo).SingleOrDefault
                If IsNothing(produto) Then
                    MessageBox.Show("Produto não localizado", "Localizar", MessageBoxButtons.OK, MessageBoxIcon.Information)
                    Return
                End If
                txtNome.Text = produto.Nome
                txtValor.Text = produto.Valor
                cboCategoria.SelectedIndex = produto.CategoriaCategoriaId - 1
            End Using
        Catch ex As Exception
            MessageBox.Show("Erro " + ex.Message, "Erro::Localizar", MessageBoxButtons.OK, MessageBoxIcon.Error)
        End Try
    End Sub

    Private Sub btnIncluir_Click(sender As Object, e As EventArgs) Handles btnIncluir.Click
        Try
            Using ctx As New ProdutoContext
                Dim novoProduto As New Produto
                novoProduto.Nome = txtNome.Text
                novoProduto.Valor = Convert.ToDecimal(txtValor.Text)
                novoProduto.CategoriaCategoriaId = cboCategoria.SelectedIndex + 1
                ctx.Produtos.Add(novoProduto)
                ctx.SaveChanges()
                MessageBox.Show("Produto incluído com sucesso : " & novoProduto.Nome.ToString, "Incluir", MessageBoxButtons.OK, MessageBoxIcon.Information)
                txtNome.Text = ""
                txtNome.Focus()
            End Using

        Catch ex As Exception
            MessageBox.Show("Erro " + ex.Message, "Erro::Incluir", MessageBoxButtons.OK, MessageBoxIcon.Error)
        End Try
    End Sub

    Private Sub btnExcluir_Click(sender As Object, e As EventArgs) Handles btnExcluir.Click
        If String.IsNullOrEmpty(txtCodigo.Text) Then
            MessageBox.Show("Informe o código do produto", "Excluir", MessageBoxButtons.OK, MessageBoxIcon.Information)
            Return
        Else
            codigo = Convert.ToInt32(txtCodigo.Text)
        End If

        Try
            Using ctx As New ProdutoContext
                Dim produt As Produto = ctx.Produtos.First(Function(prod) prod.ProdutoId = codigo)
                txtNome.Text = produt.Nome
                txtValor.Text = produt.Valor
                cboCategoria.SelectedIndex = produt.CategoriaCategoriaId - 1
                ctx.Produtos.Remove(produt)
                ctx.SaveChanges()
            End Using
        Catch ex As Exception
            MessageBox.Show("Erro " + ex.Message, "Erro::Excluir", MessageBoxButtons.OK, MessageBoxIcon.Error)
        End Try
    End Sub

    Private Sub btnAlterar_Click(sender As Object, e As EventArgs) Handles btnAlterar.Click
        If String.IsNullOrEmpty(txtCodigo.Text) Then
            MessageBox.Show("Informe o código do produto", "Alterar", MessageBoxButtons.OK, MessageBoxIcon.Information)
            Return
        Else
            codigo = Convert.ToInt32(txtCodigo.Text)
        End If
        If String.IsNullOrEmpty(txtNome.Text) Then
            MessageBox.Show("Informe o nome do produto", "Alterar", MessageBoxButtons.OK, MessageBoxIcon.Information)
            Return
        End If

        Try
            Using ctx As New ProdutoContext
                Dim produto = (From prd In ctx.Produtos Where prd.ProdutoId = codigo).SingleOrDefault
                If IsNothing(produto) Then
                    MessageBox.Show("Produto não localizado", "Alterar", MessageBoxButtons.OK, MessageBoxIcon.Information)
                    Return
                End If
                produto.Nome = txtNome.Text
                produto.Valor = txtValor.Text
                produto.CategoriaCategoriaId = cboCategoria.SelectedIndex + 1
                ctx.SaveChanges()
            End Using
        Catch ex As Exception
            MessageBox.Show("Erro " + ex.Message, "Erro::Alterar", MessageBoxButtons.OK, MessageBoxIcon.Error)
        End Try
    End Sub

Cabe destacar no código o tratamento dado a seleção feita na combobox.

Quando atribuímos o valor do código da categoria ao combobox devemos decrementar o mesmo em uma unidade visto que a propriedade SelectedIndex tem base zero e possui o primeiro elemento com valor 0 e assim sucessivamente.

cboCategoria.SelectedIndex = produto.CategoriaCategoriaId - 1

Quando damos  um valor selecionado da combo à propriedade CategoriaCategoriaId da entidade produto temos que incrementar o valor de uma unidade pelo motivo explicado acima.

produto.CategoriaCategoriaId = cboCategoria.SelectedIndex + 1

Outro destaque são as conversões feitas na propriedades valor quando usamos Convert.ToDecimal quando vamos incluir a entidade no contexto.

Exibindo de Produtos

A exibição dos produtos é idêntica ao das categorias dessa forma vou apenas exibir o formulário usado e seu código:

Leiaute do formulário form5.vb para exibir produtos:

Código do formulário form5.vb:

Public Class Form5

    Private Sub btnSair_Click(sender As Object, e As EventArgs) Handles btnSair.Click
        Me.Close()
    End Sub

    Private Sub Form5_Load(sender As Object, e As EventArgs) Handles MyBase.Load
        carregaGrid()
    End Sub
    Private Sub carregaGrid()
        Dim ctx As New ProdutoContext
        Try
            Dim produtos = From prd In ctx.Produtos Select prd.ProdutoId, prd.Nome, prd.Valor, prd.CategoriaCategoriaId
            grpProdutos.Text = "Produtos : " & produtos.Count
            gdvProdutos.DataSource = produtos.ToList()
        Catch ex As Exception
            MessageBox.Show("Erro " + ex.Message, "Erro::Exibir", MessageBoxButtons.OK, MessageBoxIcon.Error)
        Finally
            ctx.Dispose()
        End Try
    End Sub
End Class

Consultando Produtos por Categoria

O formulário form3.vb é usado para exibir os produtos para uma categoria selecionada. Para isso incluímos no formulário o controle Combobox - cboCategorias e um controle DataGridView - gdvProdutos conforme o leiaute abaixo:

No evento Load do formulário chamamos a rotina carregaCombo() que irá preencher a combobox com as categorias cadastradas:

 Private Sub Form3_Load(sender As Object, e As EventArgs) Handles MyBase.Load
        carregaCombo()
    End Sub

O código da rotina carregaCombo() é vista abaixo:

Private Sub carregaCombo()
 Try
            'cria uma instância do contexto
            Using ctx As New ProdutoContext
                Dim consulta = ctx.Categorias.Include("Produto").ToList()
                cboCategorias.DataSource = consulta
                cboCategorias.DisplayMember = "Nome"
                cboCategorias.ValueMember = "CategoriaId"
            End Using
        Catch ex As Exception
            MsgBox("Erro : " & ex.Message)
        End Try
 End Sub

O código cria uma nova instância para o ObjectContext e efetua uma consulta retornando todos os objetos da entidade Categorias;

Em seguida configuramos o controle Combobox para exibir o Nome (FirstName) das categorias retornadas e tratar o valor selecionado pelo código da categoria (CategoriaId);

Agora perceba que na consulta LINQ usamos a função Include("Produto"). Mas o que significa isso ? Por que não usamos apenas ctx.Categorias.ToList() ?

Quando usamos a função Include estamos usando o recurso Eager Load.

Eager Load é o mecanismo pelo qual uma associação, coleção ou atributo é carregado imediatamente quando o objeto principal é carregado.

Usando o Eager Load explicitamente com a função Include estamos carregando a coleção de objetos Produto relacionada com cada Categoria em uma única consulta.

Como os produtos relacionados deverão ser exibidos quando uma categoria for selecionada usamos o evento SelectedIndexChanged do controle ComboBox e definimos nele o código a seguir:

Private Sub cboCategorias_SelectedIndexChanged(sender As Object, e As EventArgs) Handles cboCategorias.SelectedIndexChanged
     ''Obtem o objeto para a categoria selecionada
     Dim oCat As Categoria = CType(Me.cboCategorias.SelectedItem, Categoria)
     ''exibe os produtos relacionados 
     gdvProdutos.DataSource = oCat.Produto.ToList()
     gdvProdutos.Columns("Categorias").Visible = False
     gdvProdutos.Columns("CategoriaCategoriaId").Visible = False
     'redimensiona as colunas conforme o tamanho 
     gdvProdutos.AutoResizeColumns(DataGridViewAutoSizeColumnsMode.AllCells)
End Sub

Então quando uma categoria for selecionada obtemos o objeto para a categoria e as entidades Produto relacionadas na consulta: gdvProdutos.DataSource = oCat.Produto.ToList(). Veja abaixo exemplo mostrando o resultado.

Se não tivéssemos usando o eager load com Include("Produto"), ou seja se a consulta fosse feita usando apenas ctx.Categorias.ToList() , ao selecionar uma categoria iremos obter uma exceção visto que os produtos não foram carregados na consulta.

Se usarmos ctx.Categorias.ToList() teremos o Lazy Load, ou em outras palavras : "se o programador não solicitar a carga, o relacionamento entre entidades não será recuperado." . E neste caso os produtos não serão carregados na consulta LINQ.

Lazy Load é o mecanismo utilizado pelos frameworks de persistência para carregar informações sobre demanda. Esse mecanismo torna as entidades mais leves, pois suas associações são carregadas apenas no momento em que o método que disponibiliza o dado associativo é chamado. Assim quando objetos são retornados por uma consulta, os objetos relacionados não são carregados ao mesmo tempo, ao invés, eles são carregados automaticamente quando a propriedade de navegação for acessada.  É também conhecido como "lazy loading".

Com isso mostramos como realizar as principais operações de manutenção de dados usando o Entity Framework 5.0 e o Visual Studio 2012 Express for desktop.

Pegue o projeto completo aqui: EF5_CRUD.zip (sem as referências)

João 6:36 Mas como já vos disse, vós me tendes visto, e contudo não credes.

João 6:37 Todo o que o Pai me dá virá a mim; e o que vem a mim de maneira nenhuma o lançarei fora.

Referências:


José Carlos Macoratti