ASP .NET 4.5 Web Forms - Criando a Camada de acesso a dados - II
Este tutorial descreve como criar, acessar e analisar dados de um banco de dados usando ASP.NET Web Forms e o Entity Framework Code-First.
Ao concluir este tutorial você terá uma nova pasta chamada Models no seu projeto e você vai ter construído as classes de acesso a dados nesta pasta.
Obs: Acompanhe todas as partes do curso neste link: http://www.macoratti.net/Cursos/aspn_450.htm
O que você vai aprender:
Estas são as características introduzidas no tutorial:
Integralmente baseado no artigo original : http://www.asp.net/web-forms/tutorials/aspnet-45/getting-started-with-aspnet-45-web-forms/create_the_data_access_layer (com algumas alterações e portado para a linguagem VB .NET)
Criando o Modelo de dados
O Entity Framework é um framework de mapeamento objeto-relacional (ORM) que permite que você trabalhe com os dados relacionais como objetos, eliminando a maior parte do código de acesso a dados que você normalmente precisa escrever.
Usando o Entity Framework, você pode emitir consultas utilizando LINQ, e então recuperar e manipular dados como objetos fortemente tipados. A LINQ fornece padrões para consulta e atualização de dados. O EF permite que você se concentrar em criar o resto de sua aplicação, ao invés de focar sobre os fundamentos de acesso a dados. Mais tarde, esta série tutorial, vamos mostrar como usar os dados para preencher a navegação e consultas de produtos.
O Entity Framework suporta um paradigma de desenvolvimento chamado Code First. O Code-Frist permite definir seus modelos de dados usando classes, então você pode mapear essas classes para um banco de dados existente ou usá-los para gerar um banco de dados. Neste tutorial, você vai criar o modelo de dados a partir das classes de modelo de dados. Então, você vai deixar o Entity Framework criar o banco de dados em tempo real a partir dessas novas classes.
Você vai começar criando as classes das entidades que definem o modelo de dados para a nossa aplicação Web Forms. A seguir você vai criar uma classe de contexto para gerar as classes de entidades e fornecer o acesso aos dados para o banco de dados. Você também vai criar uma classe inicializadora que será usada para preencher o banco de dados.
Referenciando o Entity Framework
Por padrão, o Entity Framework já está incluído quando você cria um novo projeto baseado no modelo ASP.NET Web Application. O Entity Framework pode ser instalado, desinstalado, e atualizados como um pacote NuGet.
Além de incluir o Entity Framework em seu aplicativo, você deve ter uma referência ao namespace System.Data.Entity para que você tenha acesso à funcionalidade central do Entity Framework. Essas classes permitem a você consultar, inserir, atualizar e excluir dados.
Na janela Solution Explorer, clique com botão direito do mouse sobre o nome do projeto e selecione a opção Add Reference;(Você pode clicar no menu Project->Add Reference)
A caixa de diálogo Reference Manager será exibida;
Selecione System.Data.Entity da lista na caixa de diálogo, se não estiver selecionado. Certifique-se a caixa de seleção ao lado do item é selecionado.
Clique em OK.
Este processo adiciona o assembly System.Data.Entity.dll ao seu projeto e permite fazer referência ao namespace System.Data.Entity de qualquer lugar em seu projeto.
Gerando as classes de entidades
As classes que você cria para definir o esquema de dados são chamadas de classes de entidades. Pense nas classes de entidade como na definição de tabelas. Onde cada propriedade da classe especifica uma coluna na tabela da base de dados. Essas classes fornecem uma interface objeto-relacional leve entre o código orientado a objetos e a estrutura da tabela do banco de dados relacional.
Neste tutorial, vamos começar pela adição de classes de entidade que representam os esquemas para produtos e categorias.
Clique com o botão direito do mouse sobre o nome do projeto e selecione Add e a seguir New Folder;
Altere o nome do novo Folder para Models;
A seguir clique com o botão direito sobre a pasta Models e selecione Add -> New Item;
Na janela Add new Item , selecione o item Code e a seguir Class informando o nome Produto.vb e clicando no botão Add;
O arquivo será exibido no editor. Digite o código abaixo no arquivo Produto.vb:
Obs: Vou mostrar as duas sintaxes possíveis. A esquerda a sintaxe anterior e à direita a nova sintaxe usando as propriedades automáticas:
Imports System.ComponentModel.DataAnnotations Namespace WingtipToys.Models Public Class Produto <ScaffoldColumn(False)> _ Public Property ProdutoID() As Integer Get Return m_ProdutoID End Get Set(value As Integer) m_ProdutoID = value End Set End Property Private m_ProdutoID As Integer <Required, StringLength(100), Display(Name:="Nome")> _ Public Property ProdutoNome() As String Get Return m_ProdutoNome End Get Set(value As String) m_ProdutoNome = value End Set End Property Private m_ProdutoNome As String <Required, StringLength(10000), Display(Name:="Descricao Produto"), DataType(DataType.MultilineText)> _ Public Property Descricao() As String Get Return m_Descricao End Get Set(value As String) m_Descricao = value End Set End Property Private m_Descricao As String Public Property CaminhoImagem() As String Get Return m_CaminhoImagem End Get Set(value As String) m_CaminhoImagem = value End Set End Property Private m_CaminhoImagem As String <Display(Name:="Preco")> _ Public Property PrecoUnitario() As System.Nullable(Of Double) Get Return m_PrecoUnitario End Get Set(value As System.Nullable(Of Double)) m_PrecoUnitario = value End Set End Property Private m_PrecoUnitario As System.Nullable(Of Double) Public Property CategoriaID() As System.Nullable(Of Integer) Get Return m_CategoriaID End Get Set(value As System.Nullable(Of Integer)) m_CategoriaID = value End Set End Property Private m_CategoriaID As System.Nullable(Of Integer) Public Overridable Property Categoria() As Categoria Get Return m_Categoria End Get Set(value As Categoria) m_Categoria = value End Set End Property Private m_Categoria As Categoria End Class End Namespace |
Imports System.ComponentModel.DataAnnotations Namespace WingtipToys.Models Public Class Produto <ScaffoldColumn(False)> _ Public Property ProdutoID() As Integer <Required, StringLength(100), Display(Name:="Nome")> _ Public Property ProdutoNome() As String <Required, StringLength(10000), Display(Name:="Descricao Produto"), DataType(DataType.MultilineText)> _ Public Property Descricao() As String Public Property CaminhoImagem() As String <Display(Name:="Preco")> _ Public Property PrecoUnitario() As System.Nullable(Of Double) Public Property CategoriaID() As System.Nullable(Of Integer) Public Overridable Property Categoria() As Categoria End Class End Namespace |
Vamos repetir os procedimentos acima e criar a classe Categoria.vb com o código informado abaixo:
Obs: Vou mostrar as duas sintaxes possíveis. A esquerda a sintaxe anterior e à direita a nova sintaxe usando as propriedades automáticas:
Imports System.Collections.Generic Imports System.ComponentModel.DataAnnotations Namespace WingtipToys.Models Public Class Categoria <ScaffoldColumn(False)> _ Public Property CategoriaID() As Integer Get Return m_CategoriaID End Get Set(value As Integer) m_CategoriaID = value End Set End Property Private m_CategoriaID As Integer <Required, StringLength(100), Display(Name:="Nome")> _ Public Property CategoriaNome() As String Get Return m_CategoriaNome End Get Set(value As String) m_CategoriaNome = value End Set End Property Private m_CategoriaNome As String <Display(Name:="Descricao Produto")> _ Public Property Descricao() As String Get Return m_Descricao End Get Set(value As String) m_Descricao = value End Set End Property Private m_Descricao As String Public Overridable Property Produtos() As ICollection(Of Produto) Get Return m_Produtos End Get Set(value As ICollection(Of Produto)) m_Produtos = value End Set End Property Private m_Produtos As ICollection(Of Produto) End Class End Namespace |
Imports
System.Collections.Generic Imports System.ComponentModel.DataAnnotations Namespace WingtipToys.Models Public Class Categoria <ScaffoldColumn(False)> _ Public Property CategoriaID() As Integer <Required, StringLength(100), Display(Name:="Nome")> _ Public Property CategoriaNome() As String <Display(Name:="Descricao Produto")> _ Public Property Descricao() As String Public Overridable Property Produtos() As ICollection(Of Produto) End Class End Namespace |
A classe Categoria representa o tipo de produto com o qual a aplicação foi projetada para vender (como "Carros", "Barcos", "Foguetes", e assim por diante), e a classe Produto representa os produtos (brinquedos) no banco de dados. Cada instância de um objeto Produto corresponderá a uma linha dentro de uma tabela do banco de dados, e cada propriedade da classe produto será mapeado para uma coluna na tabela. Mais adiante neste tutorial, vamos rever os dados dos produtos contidos no banco de dados.
Data Annotations
Você deve ter notado que certos membros das classes possuem atributos que especificam detalhes sobre o membro, como [ScaffoldColumn (false)]. Estas são as data annotations ou anotações de dados. Os atributos data annotations podem descrever como validar a entrada do usuário para esse membro, para especificar sua formatação, e para especificar como isso é modelado.
Os atributos Data Annotation foram introduzido no .NET 3.5 como uma forma de adicionar a validação para as classes usadas por aplicações ASP.NET. Desde aquela época, o RIA Services começou a usar anotações de dados e eles agora fazem parte do Silverlight também. No EF 4.0 o Code-First permite que você construa um EDM usando código (C#/VB .NET) e também permite realizar a validação com atributos Data Annotations.
Para este recurso devemos usar o namespace System.ComponentModel.DataAnnotations pois é ele que provê atributos de classes (usados para definir metadados) e métodos que podemos usar em nossas classes para alterar as convenções padrão e definir um comportamento personalizado que pode ser usado em vários cenários.
A seguir temos a relação das
principais Annotations suportadas pelo Entity Framework
CTP5 :
KeyAttribute - Usada para especificar que
uma propriedade/coluna é parte da chave primária da entidade e
se aplica apenas a propriedades escalares;
StringLengthAttribute - Usada para
especificar o tamanho máximo de uma string;
ConcurrencyCheckAttribute - Usada para
especificar que uma propriedade/coluna tem um modo de
concorrência "fixed " no modelo EDM;
RequiredAttribute : - Usada para especificar
que uma propriedade/coluna é não-nula e aplica-se a
propriedades escalares, complexas, e de navegação;
ColumnAttribute Usada para
especificar o nome da coluna, a posição e o tipo de dados ;
TableAttribute Usada para especificar
o nome da tabela e o esquema onde os objetos da classe serão
atualizados;
ForeignKeyAttribute - Usado em uma
propriedade de navegação para especificar a propriedade que
representa a chave estrangeira da relação
DatabaseGeneratedAttribute -Usada em uma
propriedade para especificar como o banco de dados gera um valor
para a propriedade, ou seja, Identity, Computed
ou None;
NotMappedAttribute Usada para definir que
a propriedade ou classe não estará no banco de dados;
A Classe de contexto
Para começar a usar as classes para acesso a dados, você deve definir uma classe de contexto. Como mencionado anteriormente, a classe de contexto gere as classes de entidade e proporciona acesso aos dados para a base de dados.
Este procedimento adiciona uma nova Classe de contexto na pasta Models:
Clique com o botão direto sobre a pasta Models e selecione Add -> New Item;
Na janela Add New Item selecione a opção Visual Basic ->Code e a seguir Class informando o nome ProdutoContexto.vb e clicando no botão Add;
A seguir digite o código abaixo na classe ProdutoContexto:
Obs: Vou mostrar as duas sintaxes possíveis. A esquerda a sintaxe anterior e à direita a nova sintaxe usando as propriedades automáticas:
Imports System.Data.Entity Namespace WingtipToys.Models Public Class ProdutoContexto Inherits DbContext Public Sub New() MyBase.New("WingtipToys") End Sub Public Property Categorias() As DbSet(Of Categoria) Get Return m_Categorias End Get Set(value As DbSet(Of Categoria)) m_Categorias = value End Set End Property Private m_Categorias As DbSet(Of Categoria) Public Property Produtos() As DbSet(Of Produto) Get Return m_Produtos End Get Set(value As DbSet(Of Produto)) m_Produtos = value End Set End Property Private m_Produtos As DbSet(Of Produto) End Class End Namespace |
Imports
System.Data.Entity Namespace WingtipToys.Models Public Class ProdutoContexto Inherits DbContext Public Sub New() MyBase.New("WingtipToys") End Sub Public Property Categorias() As DbSet(Of Categoria) Public Property Produtos() As DbSet(Of Produto) End Class End Namespace |
Este código adiciona o namespace System.Data.Entity para que você tenha acesso a todas as funcionalidades do núcleo do Entity Framework, o que inclui a capacidade de consulta, inserção, atualização e exclusão de dados, trabalhando com objetos fortemente tipados.
A nossa classe de contexto ProdutoContexto herda de DbContext e define as propriedades Categorias e Produtos.
Definindo a classe que popula o banco de dados
Vamos aproveitar e criar uma lógica personalizada para iniciar o banco de dados na primeira vez que o contexto for usado fornecendo alguns registros para as tabelas do banco de dados.
Repita o procedimento para criar uma nova classe na pasta Models do projeto com o nome ProdutoDataBaseInitializer.vb e a seguir digite o código abaixo nesta classe:
Obs: Eu vou usar os dados do projeto Original alterando apenas os nomes das categorias para numa tradução livre para o Português.
Imports System.Collections.Generic Imports System.Data.Entity Namespace WingtipToys.Models Public Class ProdutoDatabaseInitializer Inherits DropCreateDatabaseIfModelChanges(Of ProdutoContexto) Protected Overrides Sub Seed(context As ProdutoContexto) GetCategorias().ForEach(Function(c) context.Categorias.Add(c)) GetProdutos().ForEach(Function(p) context.Produtos.Add(p)) End Sub Private Shared Function GetCategorias() As List(Of Categoria) Dim categorias = New List(Of Categoria)() From { _ New Categoria() With {.CategoriaID = 1, .CategoriaNome = "Carros" _ }, _ New Categoria() With {.CategoriaID = 2, .CategoriaNome = "Aeronaves" _ }, _ New Categoria() With {.CategoriaID = 3, .CategoriaNome = "Caminhões" _ }, _ New Categoria() With {.CategoriaID = 4, .CategoriaNome = "Barcos" _ }, _ New Categoria() With {.CategoriaID = 5, .CategoriaNome = "Foguetes" _ } _ } Return categorias End Function Private Shared Function GetProdutos() As List(Of Produto) Dim produtos = New List(Of Produto)() From { _ New Produto() With { _ .ProdutoID = 1, _ .ProdutoNome = "Convertible Car", _ .Descricao = "This convertible car is fast! The engine is powered by a neutrino based battery (not included)." + "Power it up and let it go!", _ .CaminhoImagem = "carconvert.png", _ .PrecoUnitario = 22.5, _ .CategoriaID = 1 _ }, _ New Produto() With { _ .ProdutoID = 2, _ .ProdutoNome = "Old-time Car", _ .Descricao = "There's nothing old about this toy car, except it's looks. Compatible with other old toy cars.", _ .CaminhoImagem = "carearly.png", _ .PrecoUnitario = 15.949999999999999, _ .CategoriaID = 1 _ }, _ New Produto() With { _ .ProdutoID = 3, _ .ProdutoNome = "Fast Car", _ .Descricao = "Yes this car is fast, but it also floats in water.", _ .CaminhoImagem = "carfast.png", _ .PrecoUnitario = 32.990000000000002, _ .CategoriaID = 1 _ }, _ New Produto() With { _ .ProdutoID = 4, _ .ProdutoNome = "Super Fast Car", _ .Descricao = "Use this super fast car to entertain guests. Lights and doors work!", _ .CaminhoImagem = "carfaster.png", _ .PrecoUnitario = 8.9499999999999993, _ .CategoriaID = 1 _ }, _ New Produto() With { _ .ProdutoID = 5, _ .ProdutoNome = "Old Style Racer", _ .Descricao = "This old style racer can fly (with user assistance). Gravity controls flight duration." + "No batteries required.", _ .CaminhoImagem = "carracer.png", _ .PrecoUnitario = 34.950000000000003, _ .CategoriaID = 1 _ }, _ New Produto() With { _ .ProdutoID = 6, _ .ProdutoNome = "Ace Plane", _ .Descricao = "Authentic airplane toy. Features realistic color and details.", _ .CaminhoImagem = "planeace.png", _ .PrecoUnitario = 95.0, _ .CategoriaID = 2 _ }, _ New Produto() With { _ .ProdutoID = 7, _ .ProdutoNome = "Glider", _ .Descricao = "This fun glider is made from real balsa wood. Some assembly required.", _ .CaminhoImagem = "planeglider.png", _ .PrecoUnitario = 4.9500000000000002, _ .CategoriaID = 2 _ }, _ New Produto() With { _ .ProdutoID = 8, _ .ProdutoNome = "Paper Plane", _ .Descricao = "This paper plane is like no other paper plane. Some folding required.", _ .CaminhoImagem = "planepaper.png", _ .PrecoUnitario = 2.9500000000000002, _ .CategoriaID = 2 _ }, _ New Produto() With { _ .ProdutoID = 9, _ .ProdutoNome = "Propeller Plane", _ .Descricao = "Rubber band powered plane features two wheels.", _ .CaminhoImagem = "planeprop.png", _ .PrecoUnitario = 32.950000000000003, _ .CategoriaID = 2 _ }, _ New Produto() With { _ .ProdutoID = 10, _ .ProdutoNome = "Early Truck", _ .Descricao = "This toy truck has a real gas powered engine. Requires regular tune ups.", _ .CaminhoImagem = "truckearly.png", _ .PrecoUnitario = 15.0, _ .CategoriaID = 3 _ }, _ New Produto() With { _ .ProdutoID = 11, _ .ProdutoNome = "Fire Truck", _ .Descricao = "You will have endless fun with this one quarter sized fire truck.", _ .CaminhoImagem = "truckfire.png", _ .PrecoUnitario = 26.0, _ .CategoriaID = 3 _ }, _ New Produto() With { _ .ProdutoID = 12, _ .ProdutoNome = "Big Truck", _ .Descricao = "This fun toy truck can be used to tow other trucks that are not as big.", _ .CaminhoImagem = "truckbig.png", _ .PrecoUnitario = 29.0, _ .CategoriaID = 3 _ }, _ New Produto() With { _ .ProdutoID = 13, _ .ProdutoNome = "Big Ship", _ .Descricao = "Is it a boat or a ship. Let this floating vehicle decide by using its " + "artifically intelligent computer brain!", _ .CaminhoImagem = "boatbig.png", _ .PrecoUnitario = 95.0, _ .CategoriaID = 4 _ }, _ New Produto() With { _ .ProdutoID = 14, _ .ProdutoNome = "Paper Boat", _ .Descricao = "Floating fun for all! This toy boat can be assembled in seconds. Floats for minutes!" + "Some folding required.", _ .CaminhoImagem = "boatpaper.png", _ .PrecoUnitario = 4.9500000000000002, _ .CategoriaID = 4 _ }, _ New Produto() With { _ .ProdutoID = 15, _ .ProdutoNome = "Sail Boat", _ .Descricao = "Put this fun toy sail boat in the water and let it go!", _ .CaminhoImagem = "boatsail.png", _ .PrecoUnitario = 42.950000000000003, _ .CategoriaID = 4 _ }, _ New Produto() With { _ .ProdutoID = 16, _ .ProdutoNome = "Rocket", _ .Descricao = "This fun rocket will travel up to a height of 200 feet.", _ .CaminhoImagem = "rocket.png", _ .PrecoUnitario = 122.95, _ .CategoriaID = 5 _ } _ } Return produtos End Function End Class End Namespace |
Quando o banco de dados é criado e inicializado, a propriedade Seed é sobrescrita e definida. Quando a propriedade Seed for definida, os valores das categorias e produtos são usados para popular o banco de dados. Se você tentar atualizar os dados que usados (dados da semente), modificando o código acima, após o banco de dados ser criado, você não verá as atualizações quando você executar o aplicativo.
O motivo é que o código acima usa uma implementação da classe DropCreateDatabaseIfModelChanges para reconhecer se o modelo (esquema) mudou antes de redefinir os dados da semente. Se nenhuma mudança for feita para a categoria e as classes de entidade do produto, o banco de dados não será reinicializado com os dados da semente.
Neste ponto, você terá uma pasta models com quatro novas classes:
Configurando a aplicação para usar o modelo de dados
Agora que você já criou as classes que representam os dados, você tem que configurar o aplicativo para usar as classes.
No arquivo Global.asax, você adiciona o código que inicializa o modelo e no arquivo Web.config você adiciona as informações que indica qual banco de dados que você vai usar para armazenar os dados que são representados pelas classes.
Atualizando o arquivo Global.asax
Para inicializar os modelos de dados quando o aplicativo for iniciado, adicione o seguinte código destacado em amarelo para o método Application_Start no arquivo Global.asax.cs:
Imports System.Web.Optimization Imports System.Data.Entity Imports WingTipToys.WingtipToys.Models Public Class Global_asax Inherits HttpApplication Sub Application_Start(ByVal sender As Object, ByVal e As EventArgs) ' Fires when the application is started BundleConfig.RegisterBundles(BundleTable.Bundles) AuthConfig.RegisterOpenAuth() Database.SetInitializer(New ProdutoDatabaseInitializer()) End Sub Sub Application_BeginRequest(ByVal sender As Object, ByVal e As EventArgs) ' Fires at the beginning of each request End Sub Sub Application_AuthenticateRequest(ByVal sender As Object, ByVal e As EventArgs) ' Fires upon attempting to authenticate the use End Sub Sub Application_Error(ByVal sender As Object, ByVal e As EventArgs) ' Fires when an error occurs End Sub Sub Application_End(ByVal sender As Object, ByVal e As EventArgs) ' Fires when the application ends End Sub End Class |
Neste código, quando o aplicativo for iniciado, ela especifica o inicializador que será executado durante a primeira vez que os dados forem acessados.
Modificando o arquivo Web.Config
Embora o Código Entity Framework Code-First gere um banco de dados para você em um local padrão quando o banco de dados for preenchido com dados da semente, adicionar as suas próprias informações de conexão para a sua aplicação te da mais controle do banco de dados local.
Você especifica a conexão com o banco de dados definindo uma string de conexão no arquivo Web.config do aplicativo na raiz do projeto.
Ao adicionar uma string de conexão, você pode direcionar a localização do banco de dados (wingtiptoys.mdf) a ser construído no diretório de dados do aplicativo (App_Data), ao invés de seu local padrão.
Abra o arquivo Web.Config do projeto e defina o seguinte trecho de código para configurar a string de conexão:
<connectionStrings> <add name="DefaultConnection" connectionString="Data Source=(LocalDb)\v11.0;Initial Catalog=aspnet-WingtipToys-20120302100502;Integrated Security=True" providerName="System.Data.SqlClient" /> <add name="WingtipToys" connectionString="Data Source=(LocalDB)\v11.0;AttachDbFilename=|DataDirectory|\wingtiptoys.mdf;Integrated Security=True" providerName="System.Data.SqlClient " /> </connectionStrings> |
Quando o aplicativo for executado pela primeira vez, ele irá construir o banco de dados no local especificado pela string de conexão. Mas antes de executar o aplicativo, vamos dar um Build no projeto primeiro.
Executando um Build na aplicação
Para se certificar de que todas as classes e mudanças feitas nos arquivos do projeto estão corretas vamos executar um Build no projeto. A partir do menu Build clique em Build Solution.
Após isso a janela OutuPut exibirá as mensagens indicando se está tudo correto ou se temos que efetuar correções no código:
Se houver algum erro, verifique novamente os passos acima. A informação na janela de saída vai indicar qual arquivo tem o problema e onde no arquivo uma mudança é necessária. Esta informação irá permitir a você determinar que parte das etapas acima precisam ser corrigidas.
Neste tutorial criamos o modelo de dados, acrescentamos o código que será usado para inicializar o banco de dados. Configuramos também a aplicação para usar o modelo de dados quando o aplicativo for executado.
No próximo tutorial, vamos atualizar a interface do usuário, adicionar a navegação, e recuperar os dados a partir do banco de dados. Isso fará com que o banco de dados seja criado automaticamente com base nas classes de entidade que você criou neste tutorial.
Veja a continuação em : Criando a Interface com o usuário(UI) e Navegação - III
Referências:
Super DVD Vídeo Aulas - Vídeo Aula sobre VB .NET, ASP .NET e C#
Entity Framework 4.1 - ASP .NET com Code-First (C#) - Macoratti.net
EF4 - CTP5 As novas funcionadades do Code-Frist - Macoratti.net
Entity Framework 4.1 - Primeiros Contatos - I - Macoratti.net