Entity Framework - Guia rápido
O que é Entity Framework?
O Entity Framework foi lançado pela primeira vez em 2008, o principal meio da Microsoft de interagir entre aplicativos .NET e bancos de dados relacionais. Entity Framework é um Object Relational Mapper (ORM) que é um tipo de ferramenta que simplifica o mapeamento entre objetos em seu software para as tabelas e colunas de um banco de dados relacional.
Entity Framework (EF) é uma estrutura ORM de código aberto para ADO.NET que faz parte do .NET Framework.
Um ORM cuida de criar conexões de banco de dados e executar comandos, bem como obter resultados de consulta e materializar automaticamente esses resultados como seus objetos de aplicativo.
Um ORM também ajuda a controlar as mudanças nesses objetos e, quando instruído, também vai persistir essas mudanças no banco de dados para você.
Por que Entity Framework?
O Entity Framework é um ORM e os ORMs têm como objetivo aumentar a produtividade do desenvolvedor, reduzindo a tarefa redundante de persistência dos dados usados nas aplicações.
O Entity Framework pode gerar os comandos de banco de dados necessários para ler ou gravar dados no banco de dados e executá-los para você.
Se estiver fazendo consultas, você pode expressar suas consultas em seus objetos de domínio usando LINQ para entidades.
O Entity Framework executará a consulta relevante no banco de dados e, em seguida, materializará os resultados em instâncias de seus objetos de domínio para você trabalhar em seu aplicativo.
Existem outros ORMs no mercado, como NHibernate e LLBLGen Pro. A maioria dos ORMs normalmente mapeia os tipos de domínio diretamente para o esquema do banco de dados.
O Entity Framework tem uma camada de mapeamento mais granular para que você possa personalizar os mapeamentos, por exemplo, mapeando a única entidade para várias tabelas de banco de dados ou mesmo várias entidades para uma única tabela.
Entity Framework é a tecnologia de acesso a dados recomendada pela Microsoft para novos aplicativos.
ADO.NET parece se referir diretamente à tecnologia para conjuntos de dados e tabelas de dados.
Entity Framework é onde todo o investimento de movimento para a frente está sendo feito, o que já acontece há vários anos.
A Microsoft recomenda que você use o Entity Framework sobre ADO.NET ou LINQ to SQL para todos os novos desenvolvimentos.
Modelo Conceitual
Para desenvolvedores que estão acostumados com o desenvolvimento focado em banco de dados, a maior mudança com o Entity Framework é que ele permite que você se concentre em seu domínio de negócios. O que você deseja que seu aplicativo faça sem ser limitado pelo que o banco de dados é capaz de fazer?
Com o Entity Framework, o ponto focal é conhecido como um modelo conceitual. É um modelo dos objetos em seu aplicativo, não um modelo do banco de dados que você usa para manter os dados do aplicativo.
Seu modelo conceitual pode estar alinhado com o esquema do banco de dados ou pode ser bem diferente.
Você pode usar um Visual Designer para definir seu modelo conceitual, que pode então gerar as classes que você usará em seu aplicativo.
Você pode apenas definir suas classes e usar um recurso do Entity Framework chamado Code First. E então o Entity Framework compreenderá o modelo conceitual.
De qualquer maneira, o Entity Framework descobre como passar de seu modelo conceitual para seu banco de dados. Portanto, você pode consultar seus objetos de modelo conceitual e trabalhar diretamente com eles.
Características
A seguir estão os recursos básicos do Entity Framework. Esta lista é criada com base nos recursos mais notáveis e também a partir de perguntas frequentes sobre o Entity Framework.
- Entity Framework é uma ferramenta da Microsoft.
- O Entity Framework está sendo desenvolvido como um produto Open Source.
- O Entity Framework não está mais vinculado ou dependente do ciclo de lançamento do .NET.
- Funciona com qualquer banco de dados relacional com provedor Entity Framework válido.
- Geração de comandos SQL de LINQ para Entidades.
- O Entity Framework criará consultas parametrizadas.
- Rastreia alterações em objetos na memória.
- Permite inserir, atualizar e excluir a geração de comandos.
- Trabalha com um modelo visual ou com suas próprias aulas.
- Entity Framework armazenou suporte de procedimento.
A arquitetura do Entity Framework, de baixo para cima, consiste no seguinte -
Provedores de dados
Esses são provedores específicos de origem, que abstraem as interfaces ADO.NET para se conectar ao banco de dados ao programar em relação ao esquema conceitual.
Ele traduz as linguagens SQL comuns, como LINQ, por meio da árvore de comando, para uma expressão SQL nativa e a executa no sistema DBMS específico.
Cliente da Entidade
Esta camada expõe a camada de entidade à camada superior. O cliente de entidade oferece aos desenvolvedores a capacidade de trabalhar em entidades na forma de linhas e colunas usando consultas SQL de entidades sem a necessidade de gerar classes para representar o esquema conceitual. Entity Client mostra as camadas da estrutura da entidade, que são a funcionalidade principal. Essas camadas são chamadas de Modelo de Dados de Entidade.
o Storage Layer contém todo o esquema do banco de dados em formato XML.
o Entity Layer que também é um arquivo XML define as entidades e relacionamentos.
o Mapping layer é um arquivo XML que mapeia as entidades e relacionamentos definidos na camada conceitual com relacionamentos e tabelas reais definidos na camada lógica.
o Metadata services que também é representado no Entity Client fornece API centralizada para acessar metadados armazenados em camadas de Entidade, Mapeamento e Armazenamento.
Serviço de Objeto
A camada Object Services é o Object Context, que representa a sessão de interação entre os aplicativos e a fonte de dados.
O principal uso do Contexto do Objeto é realizar diferentes operações, como adicionar e excluir instâncias de entidades e salvar o estado alterado de volta no banco de dados com a ajuda de consultas.
É a camada ORM do Entity Framework, que representa o resultado dos dados para as instâncias de objeto das entidades.
Esses serviços permitem que o desenvolvedor use alguns dos recursos avançados do ORM, como mapeamento de chave primária, controle de alterações, etc., escrevendo consultas usando LINQ e Entity SQL.
O que há de novo no Entity Framework 6?
O Framework tem uma API complexa que permite que você tenha controle granular sobre tudo, desde a modelagem até o comportamento em tempo de execução. Parte do Entity Framework 5 reside dentro do .NET. E outra parte dele reside dentro de uma montagem adicional que é distribuída usando o NuGet.
A funcionalidade principal do Entity Framework é integrada ao .NET Framework.
O suporte Code First, é o que permite que o Entity Framework use classes no lugar de um modelo visual, e uma API mais leve para interagir com EF estão no pacote NuGet.
O núcleo é o que fornece a consulta, o controle de alterações e toda a transformação de suas consultas em consultas SQL, bem como do retorno de dados nos objetos.
Você pode usar o pacote EF 5 NuGet com .NET 4 e .NET 4.5.
Um grande ponto de confusão - o .NET 4.5 adicionou suporte para enums e dados espaciais às principais APIs do Entity Framework, o que significa que se você estiver usando EF 5 com .NET 4, não terá esses novos recursos. Você só os obterá ao combinar EF5 com .NET 4.5.
Vamos agora dar uma olhada no Entity Framework 6. As APIs principais que estavam dentro do .NET no Entity Framework 6 agora fazem parte do pacote NuGet.
Significa -
Todo o Entity Framework vive dentro deste assembly que é distribuído pelo NuGet
Você não dependerá do .NET para fornecer recursos específicos, como suporte a enum Entity Framework e suporte a dados especiais.
Você verá que um dos recursos do EF6 é que ele suporta enums e dados espaciais para .NET 4
Para começar a trabalhar no Entity Framework, você precisa instalar as seguintes ferramentas de desenvolvimento -
- Visual Studio 2013 ou superior
- SQL Server 2012 ou superior
- Atualizações do Entity Framework do pacote NuGet
A Microsoft fornece uma versão gratuita do Visual Studio que também contém o SQL Server e pode ser baixado em www.visualstudio.com .
Instalação
Step 1- Quando o download estiver concluído, execute o instalador. A seguinte caixa de diálogo será exibida.
Step 2 - Clique no botão Instalar e iniciará o processo de instalação.
Step 3- Assim que o processo de instalação for concluído com sucesso, você verá a seguinte caixa de diálogo. Feche esta caixa de diálogo e reinicie o computador, se necessário.
Step 4- Abra o Visual Studio a partir do menu inicial, que abrirá a caixa de diálogo a seguir. Vai demorar um pouco pela primeira vez para a preparação.
Step 5 - Assim que terminar, você verá a janela principal do Visual studio.
Vamos criar um novo projeto em Arquivo → Novo → Projeto
Step 1 - Selecione o aplicativo de console e clique no botão OK.
Step 2 - No Solution Explorer, clique com o botão direito em seu projeto.
Step 3 - Selecione Gerenciar Pacotes NuGet conforme mostrado na imagem acima, que abrirá a seguinte janela no Visual Studio.
Step 4 - Pesquise Entity Framework e instale a versão mais recente pressionando o botão de instalação.
Step 5- Clique em Ok. Assim que a instalação for concluída, você verá a seguinte mensagem na janela de saída.
Agora você está pronto para iniciar seu aplicativo.
Neste tutorial, usaremos um banco de dados universitário simples. Um banco de dados universitário pode ser muito mais complexo como um todo, mas para fins de demonstração e aprendizado, estamos usando a forma mais simples desse banco de dados. O diagrama a seguir contém três tabelas.
- Student
- Course
- Enrollment
Sempre que um termo banco de dados é usado, uma coisa vem diretamente à nossa mente: diferentes tipos de tabelas que têm algum tipo de relação. Existem três tipos de relacionamento entre tabelas e o relacionamento entre tabelas diferentes depende de como as colunas relacionadas são definidas.
- Relacionamento de um para muitos
- Relacionamento muitos para muitos
- Relacionamento Um para Um
Relacionamento de um para muitos
O relacionamento um-para-muitos é o tipo de relacionamento mais comum. Neste tipo de relacionamento, uma linha na tabela A pode ter muitas linhas correspondentes na tabela B, mas uma linha na tabela B pode ter apenas uma linha correspondente na tabela A. Por exemplo, no diagrama acima, as tabelas de Aluno e de Inscrição têm uma relação -para-muitos, cada aluno pode ter várias matrículas, mas cada inscrição pertence a apenas um aluno.
Relacionamento muitos para muitos
Em um relacionamento muitos para muitos, uma linha na tabela A pode ter muitas linhas correspondentes na tabela B e vice-versa. Você cria tal relacionamento definindo uma terceira tabela, chamada de tabela de junção, cuja chave primária consiste nas chaves estrangeiras da tabela A e da tabela B. Por exemplo, as tabelas Aluno e Curso têm relacionamento muitos para muitos que é definido por uma relação um-para-muitos de cada uma dessas tabelas para a tabela de inscrição.
Relacionamento Um para Um
No relacionamento um-para-um, uma linha da tabela A não pode ter mais de uma linha correspondente na tabela B e vice-versa. Um relacionamento um para um será criado se ambas as colunas relacionadas forem chaves primárias ou tiverem restrições exclusivas.
Esse tipo de relacionamento não é comum porque a maioria das informações relacionadas dessa forma seria uma tabela tudo-em-um. Você pode usar um relacionamento um-para-um para -
- Divida uma tabela com várias colunas.
- Isole parte de uma mesa por motivos de segurança.
- Armazene dados de curta duração e que possam ser facilmente excluídos simplesmente excluindo a tabela.
- Armazene informações que se aplicam apenas a um subconjunto da tabela principal.
O Entity Data Model (EDM) é uma versão estendida do modelo Entity-Relationship que especifica o modelo conceitual dos dados usando várias técnicas de modelagem. Também se refere a um conjunto de conceitos que descrevem a estrutura de dados, independentemente de sua forma armazenada.
EDM suporta um conjunto de tipos de dados primitivos que definem propriedades em um modelo conceitual. Precisamos considerar 3 partes principais que formam a base do Entity Framework e, coletivamente, são conhecidas como Entity Data Model. A seguir estão as três partes principais do EDM.
- O modelo de esquema de armazenamento
- O modelo conceitual
- O modelo de mapeamento
O modelo de esquema de armazenamento
O modelo de armazenamento também chamado de camada de definição de esquema de armazenamento (SSDL) representa a representação esquemática do armazenamento de dados de backend.
O modelo conceitual
O Modelo Conceitual, também chamado de Camada de Definição de Esquema Conceitual (CSDL), é o modelo de entidade real, contra o qual escrevemos nossas consultas.
O modelo de mapeamento
A camada de mapeamento é apenas um mapeamento entre o modelo conceitual e o modelo de armazenamento.
O esquema lógico e seu mapeamento com o esquema físico são representados como um EDM.
Visual Studio também fornece Entity Designer, para a criação visual do EDM e a especificação de mapeamento.
A saída da ferramenta é o arquivo XML (* .edmx) especificando o esquema e o mapeamento.
O arquivo Edmx contém artefatos de metadados do Entity Framework.
Linguagem de definição de esquema
O ADO.NET Entity Framework usa uma linguagem de definição de dados baseada em XML chamada Schema Definition Language (SDL) para definir o esquema EDM.
O SDL define os Tipos Simples semelhantes a outros tipos primitivos, incluindo String, Int32, Double, Decimal e DateTime, entre outros.
Uma Enumeração, que define um mapa de valores e nomes primitivos, também é considerada um tipo simples.
Enumerações são suportadas apenas a partir do framework versão 5.0.
Tipos complexos são criados a partir de uma agregação de outros tipos. Uma coleção de propriedades desses tipos define um Tipo de entidade.
O modelo de dados tem principalmente três conceitos-chave para descrever a estrutura de dados -
- Tipo de entidade
- Tipo de associação
- Property
Tipo de entidade
O tipo de entidade é o bloco de construção fundamental para descrever a estrutura de dados em EDM.
Em um modelo conceitual, os tipos de entidade são construídos a partir de propriedades e descrevem a estrutura dos conceitos de nível superior, como Alunos e Inscrições em um aplicativo de negócios.
Uma entidade representa um objeto específico, como um Aluno ou Matrícula específico.
Cada entidade deve ter uma chave de entidade exclusiva em um conjunto de entidades. Um conjunto de entidades é uma coleção de instâncias de um tipo de entidade específico. Conjuntos de entidades (e conjuntos de associações) são agrupados logicamente em um contêiner de entidade.
A herança é compatível com os tipos de entidade, ou seja, um tipo de entidade pode ser derivado de outro.
Tipo de Associação
É outro bloco de construção fundamental para descrever relacionamentos em EDM. Em um modelo conceitual, uma associação representa um relacionamento entre dois tipos de entidade, como Aluno e Inscrição.
Cada associação tem duas extremidades de associação que especificam os tipos de entidade envolvidos na associação.
Cada extremidade da associação também especifica uma multiplicidade de extremidade da associação que indica o número de entidades que podem estar nessa extremidade da associação.
Uma multiplicidade de extremidade de associação pode ter um valor de um (1), zero ou um (0..1) ou muitos (*).
As entidades em uma extremidade de uma associação podem ser acessadas por meio de propriedades de navegação ou por meio de chaves estrangeiras se estiverem expostas em um tipo de entidade.
Propriedade
Os tipos de entidade contêm propriedades que definem sua estrutura e características. Por exemplo, um tipo de entidade de Aluno pode ter propriedades como Id do Aluno, Nome etc.
Uma propriedade pode conter dados primitivos (como uma string, um número inteiro ou um valor booleano) ou dados estruturados (como um tipo complexo).
O Entity Framework permite que você consulte, insira, atualize e exclua dados, usando objetos Common Language Runtime (CLR), que são conhecidos como entidades. O Entity Framework mapeia as entidades e relacionamentos definidos em seu modelo para um banco de dados. Ele também fornece facilidades para -
- Materializar dados retornados do banco de dados como objetos de entidade
- Rastreie as alterações feitas nos objetos
- Lidar com simultaneidade
- Propagar alterações de objetos de volta ao banco de dados
- Vincular objetos a controles
A classe primária responsável por interagir com os dados como objetos é System.Data.Entity.DbContext. A API DbContext não é lançada como parte do .NET Framework. Para ser mais flexível e frequente com o lançamento de novos recursos para Code First e DbContext API, a equipe do Entity Framework distribui EntityFramework.dll por meio do recurso de distribuição NuGet da Microsoft.
O NuGet permite adicionar referências aos seus projetos .NET, puxando as DLLs relevantes diretamente para o seu projeto da Web.
Uma extensão do Visual Studio chamada Library Package Manager fornece uma maneira fácil de puxar o assembly apropriado da Web para seus projetos.
A API DbContext é principalmente voltada para simplificar sua interação com o Entity Framework.
Também reduz o número de métodos e propriedades de que você precisa para acessar tarefas comumente usadas.
Nas versões anteriores do Entity Framework, essas tarefas eram frequentemente complicadas de descobrir e codificar.
A classe de contexto gerencia os objetos de entidade durante o tempo de execução, o que inclui o preenchimento de objetos com dados de um banco de dados, controle de alterações e dados persistentes no banco de dados.
Definindo uma classe derivada de DbContext
A maneira recomendada de trabalhar com contexto é definir uma classe que deriva de DbContext e expõe propriedades DbSet que representam coleções das entidades especificadas no contexto. Se você estiver trabalhando com o EF Designer, o contexto será gerado para você. Se você estiver trabalhando com o Code First, normalmente escreverá o contexto sozinho.
O código a seguir é um exemplo simples que mostra que UniContext é derivado de DbContext.
Você pode usar propriedades automáticas com DbSet, como getter e setter.
Ele também torna o código muito mais limpo, mas você não é obrigado a usá-lo para criar um DbSet quando não há outra lógica para aplicar.
public class UniContext : DbContext {
public UniContext() : base("UniContext") { }
public DbSet<Student> Students { get; set; }
public DbSet<Enrollment> Enrollments { get; set; }
public DbSet<Course> Courses { get; set; }
}
Anteriormente, o EDM costumava gerar classes de contexto derivadas da classe ObjectContext.
Trabalhar com ObjectContext foi um pouco complexo.
DbContext é um wrapper em torno do ObjectContext que é realmente semelhante ao ObjectContext e é útil e fácil em todos os modelos de desenvolvimento, como Code First, Model First e Database First.
Consultas
Existem três tipos de consultas que você pode usar, como -
- Adicionando uma nova entidade.
- Alterar ou atualizar os valores de propriedade de uma entidade existente.
- Excluindo uma entidade existente.
Adicionando Novas Entidades
Adicionar um novo objeto com o Entity Framework é tão simples quanto construir uma nova instância do seu objeto e registrá-lo usando o método Add em DbSet. O código a seguir é para quando você deseja adicionar um novo aluno ao banco de dados.
private static void AddStudent() {
using (var context = new UniContext()) {
var student = new Student {
LastName = "Khan",
FirstMidName = "Ali",
EnrollmentDate = DateTime.Parse("2005-09-01")
};
context.Students.Add(student);
context.SaveChanges();
}
}
Alterando Entidades Existentes
Alterar objetos existentes é tão simples quanto atualizar o valor atribuído à (s) propriedade (s) que você deseja alterar e chamar SaveChanges. No código a seguir, o sobrenome de Ali foi alterado de Khan para Aslam.
private static void AddStudent() {
private static void ChangeStudent() {
using (var context = new UniContext()) {
var student = (from d in context.Students
where d.FirstMidName == "Ali" select d).Single();
student.LastName = "Aslam";
context.SaveChanges();
}
}
}
Excluindo Entidades Existentes
Para excluir uma entidade usando o Entity Framework, você usa o método Remove em DbSet. Remova obras para entidades existentes e recém-adicionadas. Chamar Remove em uma entidade que foi adicionada, mas ainda não salva no banco de dados, cancelará a adição da entidade. A entidade é removida do rastreador de alterações e não é mais rastreada pelo DbContext. Chamar Remove em uma entidade existente que está sendo controlada por alterações registrará a entidade para exclusão na próxima vez que SaveChanges for chamado. O exemplo a seguir mostra uma instância em que o aluno é removido do banco de dados cujo primeiro nome é Ali.
private static void DeleteStudent() {
using (var context = new UniContext()) {
var bay = (from d in context.Students where d.FirstMidName == "Ali" select d).Single();
context.Students.Remove(bay);
context.SaveChanges();
}
}
No Entity Framework, existem dois tipos de entidades que permitem aos desenvolvedores usar suas próprias classes de dados personalizadas junto com o modelo de dados, sem fazer nenhuma modificação nas próprias classes de dados.
- Entidades POCO
- Proxy Dinâmico
Entidades POCO
POCO significa objetos CLR "simples" que podem ser usados como objetos de domínio existentes com seu modelo de dados.
As classes de dados POCO que são mapeadas para entidades são definidas em um modelo de dados.
Ele também suporta a maioria dos mesmos comportamentos de consulta, inserção, atualização e exclusão como tipos de entidade que são gerados pelas ferramentas de Modelo de Dados de Entidade.
Você pode usar o modelo POCO para gerar tipos de entidade ignorantes de persistência a partir de um modelo conceitual.
Vamos dar uma olhada no seguinte exemplo de modelo de dados de entidade conceitual.
Para gerar entidades POCO para o modelo de entidade acima -
Step 1- Clique com o botão direito na janela do designer. Ele exibirá a seguinte caixa de diálogo.
Step 2 - Selecione Adicionar Item de Geração de Código ...
Step 3 - Selecione o Gerador EF 6.x DbContext, escreva o nome e clique no botão Adicionar.
Você verá em seu explorador de soluções que os modelos POCODemo.Context.tt e POCODemo.tt são gerados.
O POCODemo.Context gera o DbContext e os conjuntos de objetos que você pode retornar e usar para consultar, digamos para contexto, Alunos e Cursos, etc.
O outro modelo lida com todos os tipos Aluno, Cursos, etc. A seguir está o código para a classe Aluno que é gerado automaticamente a partir do Modelo de Entidade.
namespace ConsoleApplication1 {
using System;
using System.Collections.Generic;
public partial class Student {
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage",
"CA2214:DoNotCallOverridableMethodsInConstructors")]
public Student() {
this.Enrollments = new HashSet<Enrollment>();
}
public int ID { get; set; }
public string LastName { get; set; }
public string FirstMidName { get; set; }
public System.DateTime EnrollmentDate { get; set; }
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage",
CA2227:CollectionPropertiesShouldBeReadOnly")]
public virtual ICollection<Enrollment> Enrollments { get; set; }
}
}
Classes semelhantes são geradas para as tabelas de Curso e Matrícula do Modelo de Entidade.
Proxy Dinâmico
Ao criar instâncias de tipos de entidade POCO, o Entity Framework geralmente cria instâncias de um tipo derivado gerado dinamicamente que atua como um proxy para a entidade. Também se pode dizer que é uma classe de proxy de tempo de execução como uma classe wrapper da entidade POCO.
Você pode substituir algumas propriedades da entidade para executar ações automaticamente quando a propriedade é acessada.
Este mecanismo é usado para suportar carregamento lento de relacionamentos e rastreamento automático de alterações.
Esta técnica também se aplica aos modelos criados com Code First e EF Designer.
Se você quiser que o Entity Framework ofereça suporte ao carregamento lento dos objetos relacionados e rastreie as alterações nas classes POCO, as classes POCO devem atender aos seguintes requisitos -
A classe de dados personalizados deve ser declarada com acesso público.
A classe de dados personalizada não deve ser lacrada.
A classe de dados personalizados não deve ser abstrata.
A classe de dados personalizados deve ter um construtor público ou protegido que não tenha parâmetros.
Use um construtor protegido sem parâmetros se desejar que o método CreateObject seja usado para criar um proxy para a entidade POCO.
Chamar o método CreateObject não garante a criação do proxy: a classe POCO deve seguir os outros requisitos descritos neste tópico.
A classe não pode implementar as interfaces IEntityWithChangeTracker ou IEntityWithRelationships porque as classes de proxy implementam essas interfaces.
A opção ProxyCreationEnabled deve ser definida como true.
O exemplo a seguir é de classe de entidade de proxy dinâmico.
public partial class Course {
public Course() {
this.Enrollments = new HashSet<Enrollment>();
}
public int CourseID { get; set; }
public string Title { get; set; }
public int Credits { get; set; }
public virtual ICollection<Enrollment> Enrollments { get; set; }
}
Para desativar a criação de objetos proxy, defina o valor da propriedade ProxyCreationEnabled como false.
Em bancos de dados relacionais, relacionamento é uma situação que existe entre tabelas de bancos de dados relacionais por meio de chaves estrangeiras. Uma chave estrangeira (FK) é uma coluna ou combinação de colunas que é usada para estabelecer e impor um link entre os dados em duas tabelas. O diagrama a seguir contém três tabelas.
- Student
- Course
- Enrollment
No diagrama acima, você pode ver algum tipo de associação / relacionamento entre as tabelas. Existem três tipos de relacionamento entre tabelas e o relacionamento entre tabelas diferentes depende de como as colunas relacionadas são definidas.
- Relacionamento de um para muitos
- Relacionamento muitos para muitos
- Relacionamento Um para Um
Relacionamento de um para muitos
Um relacionamento um-para-muitos é o tipo mais comum de relacionamento.
Nesse tipo de relacionamento, uma linha na tabela A pode ter muitas linhas correspondentes na tabela B, mas uma linha na tabela B pode ter apenas uma linha correspondente na tabela A.
A chave estrangeira é definida na tabela que representa a extremidade múltipla do relacionamento.
Por exemplo, no diagrama acima, as tabelas Aluno e Matrícula têm relação um-para-muitos, cada aluno pode ter muitas matrículas, mas cada inscrição pertence a apenas um aluno.
Na estrutura da entidade, esses relacionamentos também podem ser criados com código. A seguir está um exemplo de classes de Aluno e de Matrícula que estão associadas a um relacionamento um para muitos.
public class Student {
public int ID { get; set; }
public string LastName { get; set; }
public string FirstMidName { get; set; }
public DateTime EnrollmentDate { get; set; }
public virtual ICollection<Enrollment> Enrollments { get; set; }
}
public class Enrollment {
public int EnrollmentID { get; set; }
public int CourseID { get; set; }
public int StudentID { get; set; }
public Grade? Grade { get; set; }
public virtual Course Course { get; set; }
public virtual Student Student { get; set; }
}
No código acima, você pode ver que a classe Student contém a coleção de Enrollment, mas a classe Enrollment tem um único objeto Student.
Relacionamento muitos para muitos
No relacionamento muitos para muitos, uma linha na tabela A pode ter muitas linhas correspondentes na tabela B e vice-versa.
Você pode criar tal relacionamento definindo uma terceira tabela, chamada de tabela de junção, cuja chave primária consiste nas chaves estrangeiras da tabela A e da tabela B.
Por exemplo, as tabelas Aluno e Curso têm relacionamento de muitos para muitos que é definido pelo relacionamento de um para muitos de cada uma dessas tabelas para a tabela de inscrição.
O código a seguir contém a classe do curso e as duas classes acima, ou seja, Student e Enrollment.
public class Course {
[DatabaseGenerated(DatabaseGeneratedOption.None)]
public int CourseID { get; set; }
public string Title { get; set; }
public int Credits { get; set; }
public virtual ICollection<Enrollment> Enrollments { get; set; }
}
Você pode ver que tanto a classe Curso quanto a classe Aluno têm coleções de objetos Enrollment, o que faz a relação muitos para muitos por meio da classe de junção Enrollment.
Relacionamento Um para Um
Em um relacionamento um para um, uma linha na tabela A não pode ter mais de uma linha correspondente na tabela B e vice-versa.
Um relacionamento um para um será criado se ambas as colunas relacionadas forem chaves primárias ou tiverem restrições exclusivas.
Em um relacionamento um-para-um, a chave primária atua adicionalmente como uma chave estrangeira e não há coluna de chave estrangeira separada para nenhuma das tabelas.
Esse tipo de relacionamento não é comum porque a maioria das informações relacionadas dessa forma estariam todas em uma tabela. Você pode usar um relacionamento um-para-um para -
- Divida uma tabela com várias colunas.
- Isole parte de uma mesa por motivos de segurança.
- Armazene dados de curta duração e que possam ser facilmente excluídos simplesmente excluindo a tabela.
- Armazene informações que se aplicam apenas a um subconjunto da tabela principal.
O código a seguir é para adicionar outro nome de classe StudentProfile que contém o ID de e-mail e a senha do aluno.
public class Student {
public int ID { get; set; }
public string LastName { get; set; }
public string FirstMidName { get; set; }
public DateTime EnrollmentDate { get; set; }
public virtual ICollection<Enrollment> Enrollments { get; set; }
public virtual StudentProfile StudentProfile { get; set; }
}
public class StudentProfile {
public StudentProfile() {}
public int ID { get; set; }
public string Email { get; set; }
public string Password { get; set; }
public virtual Student Student { get; set; }
}
Você pode ver que a classe de entidade Student contém a propriedade de navegação StudentProfile e a propriedade StudentProfile contém a propriedade de navegação Student.
Cada aluno possui apenas um e-mail e senha para fazer o login no domínio universitário. Essas informações podem ser adicionadas à tabela do Aluno, mas por razões de segurança, são separadas em outra tabela.
Tempo de vida
O tempo de vida de um contexto começa quando a instância é criada e termina quando a instância é descartada ou coletada como lixo.
O tempo de vida do contexto é uma decisão muito importante quando usamos ORMs.
O contexto está funcionando como um cache de entidade, o que significa que ele contém referências a todas as entidades carregadas que podem crescer muito rápido no consumo de memória e também pode causar vazamentos de memória.
No diagrama abaixo, você pode ver o nível superior do fluxo de trabalho de dados do aplicativo para o banco de dados por meio do Contexto e vice-versa.
Ciclo de Vida da Entidade
O ciclo de vida da entidade descreve o processo no qual uma entidade é criada, adicionada, modificada, excluída, etc. As entidades têm muitos estados durante seu tempo de vida. Antes de ver como recuperar o estado da entidade, vamos dar uma olhada no que é o estado da entidade. O estado é um enum do tipoSystem.Data.EntityState que declara os seguintes valores -
Added: A entidade é marcada como adicionada.
Deleted: A entidade está marcada como excluída.
Modified: A entidade foi modificada.
Unchanged: A entidade não foi modificada.
Detached: A entidade não é rastreada.
Mudanças de estado no ciclo de vida da entidade
Às vezes, o estado das entidades é definido automaticamente pelo contexto, mas também pode ser modificado manualmente pelo desenvolvedor. Embora todas as combinações de interruptores de um estado para outro sejam possíveis, algumas delas não têm sentido. Por exemplo,Added entidade para o Deleted estado, ou vice-versa.
Vamos discutir sobre os diferentes estados.
Estado inalterado
Quando uma entidade está inalterada, ela é vinculada ao contexto, mas não foi modificada.
Por padrão, uma entidade recuperada do banco de dados está neste estado.
Quando uma entidade é anexada ao contexto (com o método Attach), ela também está no estado Inalterado.
O contexto não pode rastrear mudanças em objetos aos quais ele não faz referência, então, quando eles são anexados, ele assume que eles estão inalterados.
Estado Independente
Desanexado é o estado padrão de uma entidade recém-criada porque o contexto não pode rastrear a criação de qualquer objeto em seu código.
Isso é verdadeiro mesmo se você instanciar a entidade dentro de um bloco de uso do contexto.
Desanexado é até mesmo o estado das entidades recuperadas do banco de dados quando o rastreamento está desabilitado.
Quando uma entidade é desanexada, ela não é vinculada ao contexto, portanto, seu estado não é rastreado.
Ele pode ser descartado, modificado, usado em combinação com outras classes ou usado de qualquer outra forma que você possa precisar.
Como não há rastreamento de contexto, não tem significado para o Entity Framework.
Estado adicionado
Quando uma entidade está no estado Adicionado, você tem poucas opções. Na verdade, você só pode separá-lo do contexto.
Naturalmente, mesmo que você modifique alguma propriedade, o estado permanece Adicionado, porque movê-lo para Modificado, Inalterado ou Excluído não faz sentido.
É uma nova entidade e não tem correspondência com uma linha no banco de dados.
Este é um pré-requisito fundamental para estar em um desses estados (mas esta regra não é imposta pelo contexto).
Estado modificado
Quando uma entidade é modificada, significa que ela estava no estado Inalterado e então alguma propriedade foi alterada.
Depois que uma entidade entra no estado Modificado, ela pode passar para o estado Desanexado ou Excluído, mas não pode voltar ao estado Inalterado, mesmo se você restaurar manualmente os valores originais.
Ele nem pode ser alterado para Adicionado, a menos que você desanexe e adicione a entidade ao contexto, porque uma linha com este ID já existe no banco de dados e você obteria uma exceção de tempo de execução ao persisti-la.
Estado excluído
Uma entidade entra no estado Excluído porque estava Inalterado ou Modificado e, em seguida, o método DeleteObject foi usado.
Este é o estado mais restritivo, porque é inútil mudar deste estado para qualquer outro valor exceto Desanexado.
o usingse quiser que todos os recursos que o contexto controla sejam descartados no final do bloco. Quando você usa ousing , o compilador cria automaticamente um bloco try / finally e chama dispose no bloco finally.
using (var context = new UniContext()) {
var student = new Student {
LastName = "Khan",
FirstMidName = "Ali",
EnrollmentDate = DateTime.Parse("2005-09-01")
};
context.Students.Add(student);
context.SaveChanges();
}
Ao trabalhar com contexto de longa duração, considere o seguinte -
À medida que você carrega mais objetos e suas referências na memória, o consumo de memória do contexto pode aumentar rapidamente. Isso pode causar problemas de desempenho.
Lembre-se de descartar o contexto quando não for mais necessário.
Se uma exceção fizer com que o contexto fique em um estado irrecuperável, todo o aplicativo pode ser encerrado.
As chances de problemas relacionados à simultaneidade aumentam à medida que aumenta o intervalo entre o momento em que os dados são consultados e atualizados.
Ao trabalhar com aplicativos da Web, use uma instância de contexto por solicitação.
Ao trabalhar com o Windows Presentation Foundation (WPF) ou Windows Forms, use uma instância de contexto por formulário. Isso permite que você use a funcionalidade de controle de alterações que o contexto fornece.
Regras de ouro
Web Applications
Agora é uma prática comum e recomendada que, para aplicativos da Web, o contexto seja usado por solicitação.
Em aplicativos da web, lidamos com solicitações que são muito curtas, mas retêm todas as transações do servidor; portanto, têm a duração adequada para o contexto viver.
Desktop Applications
Para aplicativos de desktop, como Win Forms / WPF, etc., o contexto é usado por formulário / caixa de diálogo / página.
Como não queremos ter o contexto como um singleton para nosso aplicativo, iremos descartá-lo quando passarmos de um formulário para outro.
Desta forma, ganharemos muitas das habilidades do contexto e não sofreremos as implicações de contextos de longa duração.
O Entity Framework fornece três abordagens para criar um modelo de entidade e cada uma tem seus próprios prós e contras.
- Código Primeiro
- Banco de dados primeiro
- Modelo primeiro
Neste capítulo, descreveremos brevemente a primeira abordagem do código. Alguns desenvolvedores preferem trabalhar com o Designer no Código, enquanto outros preferem apenas trabalhar com seu código. Para esses desenvolvedores, o Entity Framework tem um fluxo de trabalho de modelagem conhecido como Code First.
O fluxo de trabalho de modelagem do Code First visa um banco de dados que não existe e o Code First o criará.
Ele também pode ser usado se você tiver um banco de dados vazio e, em seguida, Code First adicionará novas tabelas também.
Code First permite que você defina seu modelo usando classes C # ou VB.Net.
A configuração adicional pode ser executada opcionalmente usando atributos em suas classes e propriedades ou usando uma API fluente.
Por que codificar primeiro?
O Code First é realmente composto de um conjunto de peças de um quebra-cabeça. Em primeiro lugar estão suas classes de domínio.
As classes de domínio não têm nada a ver com o Entity Framework. Eles são apenas os itens do domínio do seu negócio.
Entity Framework, então, tem um contexto que gerencia a interação entre essas classes e seu banco de dados.
O contexto não é específico para Code First. É um recurso do Entity Framework.
Code First inclui um construtor de modelo que inspeciona suas classes que o contexto está gerenciando e, em seguida, usa um conjunto de regras ou convenções para determinar como essas classes e os relacionamentos descrevem um modelo e como esse modelo deve ser mapeado para seu banco de dados.
Tudo isso acontece em tempo de execução. Você nunca verá este modelo, está apenas na memória.
A Code First tem a capacidade de usar esse modelo para criar um banco de dados, se necessário.
Ele também pode atualizar o banco de dados se o modelo mudar, usando um recurso chamado Code First Migrations.
Neste capítulo, vamos aprender como criar um modelo de dados de entidade no designer usando o fluxo de trabalho conhecido como Model First.
O Model First é ótimo para quando você está iniciando um novo projeto em que o banco de dados ainda nem existe.
O modelo é armazenado em um arquivo EDMX e pode ser visualizado e editado no Entity Framework Designer.
Em Model First, você define seu modelo em um designer do Entity Framework, em seguida, gera SQL, que criará o esquema de banco de dados para corresponder ao seu modelo e, em seguida, execute o SQL para criar o esquema em seu banco de dados.
As classes com as quais você interage em seu aplicativo são geradas automaticamente a partir do arquivo EDMX.
A seguir está um exemplo simples de criação de um novo projeto de console usando a abordagem Model First.
Step 1 - Abra o Visual Studio e selecione Arquivo → Novo → Projeto
Step 2 - Selecione Instalado → Modelos → Visual C # → Windows no painel esquerdo e, em seguida, no painel do meio, selecione Aplicativo de console.
Step 3 - Insira EFModelFirstDemo no campo Nome.
Step 4 - Para criar o modelo, primeiro clique com o botão direito em seu projeto de console no gerenciador de soluções e selecione Adicionar → Novos itens ...
A seguinte caixa de diálogo será aberta.
Step 5 - Selecione ADO.NET Entity Data Model no painel do meio e insira o nome ModelFirstDemoDB no campo Nome.
Step 6 - Clique no botão Adicionar que iniciará a caixa de diálogo Assistente de modelo de dados de entidade.
Step 7- Selecione o modelo Empty EF Designer e clique no botão Next. O Entity Framework Designer é aberto com um modelo em branco. Agora podemos começar a adicionar entidades, propriedades e associações ao modelo.
Step 8- Clique com o botão direito na superfície de design e selecione Propriedades. Na janela Propriedades, altere o Nome do contêiner de entidade para ModelFirstDemoDBContext.
Step 9 - Clique com o botão direito na superfície de design e selecione Add New → Entity…
A caixa de diálogo Adicionar entidade será aberta conforme mostrado na imagem a seguir.
Step 10 - Insira Aluno como nome da entidade e Id do Aluno como nome da propriedade e clique em OK.
Step 11 - Clique com o botão direito na nova entidade na superfície de design e selecione Adicionar Novo → Propriedade Escalar, insira Nome como o nome da propriedade.
Step 12 - Insira FirstName e, em seguida, adicione outras duas propriedades escalares, como LastName e EnrollmentDate.
Step 13 - Adicione mais duas Entidades de Curso e Inscrição seguindo todas as etapas mencionadas acima e também adicione algumas propriedades Escalares conforme mostrado nas etapas a seguir.
Step 14 - Temos três entidades no Visual Designer, vamos adicionar alguma associação ou relacionamento entre elas.
Step 15 - Clique com o botão direito na superfície de design e selecione Adicionar Novo → Associação ...
Step 16 - Faça um ponto final do relacionamento para Aluno com uma multiplicidade de um e o outro ponto final para Inscrição com uma multiplicidade de muitos.
Step 17 - Isso significa que um aluno tem muitas matrículas e a matrícula pertence a um aluno.
Step 18 - Certifique-se de que a caixa Adicionar propriedades de chave estrangeira à entidade 'Post' esteja marcada e clique em OK.
Step 19 - Da mesma forma, adicione mais uma associação entre Curso e Inscrição.
Step 20 - Seu modelo de dados se parecerá com a tela a seguir após adicionar associações entre entidades.
Agora temos um modelo simples a partir do qual podemos gerar um banco de dados e usar para ler e gravar dados. Vamos em frente e gerar o banco de dados.
Step 1 - Clique com o botão direito na superfície de design e selecione Gerar banco de dados do modelo ...
Step 2 - Você pode selecionar o banco de dados existente ou criar uma nova conexão clicando em Nova Conexão ...
Step 3 - Para criar um novo banco de dados, clique em Nova conexão ...
Step 4 - Insira o nome do servidor e o nome do banco de dados.
Step 5 - Clique em Avançar.
Step 6- Clique em Concluir. Isso adicionará o arquivo * .edmx.sql ao projeto. Você pode executar scripts DDL no Visual Studio abrindo o arquivo .sql, clique com o botão direito e selecione Executar.
Step 7 - A seguinte caixa de diálogo será exibida para conectar ao banco de dados.
Step 8 - Na execução bem-sucedida, você verá a seguinte mensagem.
Step 9 - Vá para o explorador de servidores, você verá que o banco de dados é criado com três tabelas que são especificadas.
Em seguida, precisamos trocar nosso modelo para gerar código que faz uso da API DbContext.
Step 1 - Clique com o botão direito em um local vazio do seu modelo no EF Designer e selecione Adicionar Item de Geração de Código ...
Você verá que a seguinte caixa de diálogo Adicionar Novo Item é aberta.
Step 2 - Selecione EF 6.x DbContext Generator no painel do meio e insira ModelFirstDemoModel no campo Nome.
Step 3 - Você verá em seu explorador de soluções que os modelos ModelFirstDemoModel.Context.tt e ModelFirstDemoModel.tt são gerados.
O ModelFirstDemoModel.Context gera o DbCcontext e os conjuntos de objetos que você pode retornar e usar para consultar, digamos, para o contexto, alunos e cursos etc.
O outro modelo trata de todos os tipos Aluno, Cursos etc. A seguir está a classe Aluno, que é gerada automaticamente a partir do Modelo de Entidade.
A seguir está o código C # no qual alguns dados são inseridos e recuperados do banco de dados.
using System;
using System.Linq;
namespace EFModelFirstDemo {
class Program {
static void Main(string[] args) {
using (var db = new ModelFirstDemoDBContext()) {
// Create and save a new Student
Console.Write("Enter a name for a new Student: ");
var firstName = Console.ReadLine();
var student = new Student {
StudentID = 1,
FirstName = firstName
};
db.Students.Add(student);
db.SaveChanges();
var query = from b in db.Students
orderby b.FirstName select b;
Console.WriteLine("All student in the database:");
foreach (var item in query) {
Console.WriteLine(item.FirstName);
}
Console.WriteLine("Press any key to exit...");
Console.ReadKey();
}
}
}
}
Quando o código acima for executado, você receberá a seguinte saída -
Enter a name for a new Student:
Ali Khan
All student in the database:
Ali Khan
Press any key to exit...
Recomendamos que você execute o exemplo acima passo a passo para melhor compreensão.
Neste capítulo, vamos aprender como criar um modelo de dados de entidade com a abordagem Database First.
A abordagem do banco de dados primeiro fornece uma alternativa para as abordagens Code First e Model First para o Entity Data Model. Ele cria códigos de modelo (classes, propriedades, DbContext etc.) do banco de dados no projeto e essas classes tornam-se o elo entre o banco de dados e o controlador.
O Database First Approach cria a estrutura da entidade a partir de um banco de dados existente. Usamos todas as outras funcionalidades, como a sincronização de modelo / banco de dados e a geração de código, da mesma forma que as usamos na abordagem Model First.
Vamos dar um exemplo simples. Já temos um banco de dados que contém 3 tabelas conforme mostrado na imagem a seguir.
Step 1 - Vamos criar um novo projeto de console com o nome DatabaseFirstDemo.
Step 2 - Para criar o modelo, primeiro clique com o botão direito do mouse em seu projeto de console no gerenciador de soluções e selecione Adicionar → Novos itens ...
Step 3 - Selecione ADO.NET Entity Data Model no painel do meio e insira o nome DatabaseFirstModel no campo Nome.
Step 4 - Clique no botão Adicionar que iniciará a caixa de diálogo Assistente de modelo de dados de entidade.
Step 5 - Selecione EF Designer do banco de dados e clique no botão Avançar.
Step 6 - Selecione o banco de dados existente e clique em Avançar.
Step 7 - Escolha Entity Framework 6.xe clique em Avançar.
Step 8 - Selecione todas as visualizações de tabelas e procedimentos armazenados que deseja incluir e clique em Concluir.
Você verá que o modelo de entidade e as classes POCO são gerados a partir do banco de dados.
Vamos agora recuperar todos os alunos do banco de dados escrevendo o seguinte código no arquivo program.cs.
using System;
using System.Linq;
namespace DatabaseFirstDemo {
class Program {
static void Main(string[] args) {
using (var db = new UniContextEntities()) {
var query = from b in db.Students
orderby b.FirstMidName select b;
Console.WriteLine("All All student in the database:");
foreach (var item in query) {
Console.WriteLine(item.FirstMidName +" "+ item.LastName);
}
Console.WriteLine("Press any key to exit...");
Console.ReadKey();
}
}
}
}
Quando o programa acima for executado, você receberá a seguinte saída -
All student in the database:
Ali Khan
Arturo finand
Bill Gates
Carson Alexander
Gytis Barzdukas
Laura Norman
Meredith Alonso
Nino Olivetto
Peggy Justice
Yan Li
Press any key to exit...
Quando o programa acima for executado, você verá os nomes de todos os alunos que foram inseridos anteriormente no banco de dados.
Recomendamos que você execute o exemplo acima passo a passo para melhor compreensão.
Neste capítulo, vamos nos concentrar na construção de modelos com o Designer ou o Database First ou apenas usando o Code First. A seguir estão algumas diretrizes que o ajudarão a decidir qual fluxo de trabalho de modelagem escolher.
Já vimos exemplos de modelagem Code First, modelagem Database First e um fluxo de trabalho de modelagem Model First.
Os fluxos de trabalho Database First e Model First usaram o Designer, mas um começa com o banco de dados para criar um modelo e o outro começa no modelo para criar um banco de dados.
Para os desenvolvedores que não desejam usar o Visual Designer mais a geração de código, o Entity Framework tem um fluxo de trabalho completamente diferente chamado Code First.
O fluxo de trabalho típico para Code First é ótimo para novos aplicativos onde você nem mesmo tem um banco de dados. Você define suas classes e código e, em seguida, deixa o Code First descobrir como seu banco de dados deve ser.
Também é possível iniciar o Code First com um banco de dados e isso torna o Code First uma contradição. Mas há uma ferramenta que permite que você faça engenharia reversa de um banco de dados em classes, o que é uma ótima maneira de obter uma vantagem inicial na codificação.
Dadas essas opções, vamos dar uma olhada na Árvore de Decisão.
Se você preferir trabalhar com um Visual Designer no código gerado, você vai querer escolher um dos fluxos de trabalho que envolvem o EF Designer. Se o seu banco de dados já existe, então o Database First é o seu caminho.
Se você quiser usar um Visual Designer em um projeto totalmente novo sem um banco de dados, você vai querer usar o Model First.
Se você deseja apenas trabalhar com código e não com um Designer, então Code First é provavelmente para você, juntamente com a opção de usar a ferramenta que faz a engenharia reversa do banco de dados em classes.
Se você já possui classes, sua melhor aposta é usá-las com o Code First.
Nos capítulos anteriores, você aprendeu três maneiras diferentes de definir um modelo de dados de entidade.
Dois deles, o Database First e o Model First, dependiam do designer do Entity Framework combinado com a geração de código.
O terceiro, Code First, permite que você ignore um designer visual e apenas escreva seu próprio código.
Independentemente do caminho que você escolher, você acabará com classes de domínio e uma ou mais classes DbContext do Entity Framework permitem que você recupere e mantenha dados relevantes para essas classes.
A API DbContext em seus aplicativos é usada como uma ponte entre suas classes e seu banco de dados. O DbContext é uma das classes mais importantes do Entity Framework.
Ele permite expressar e executar consultas.
Ele pega os resultados da consulta do banco de dados e os transforma em instâncias de nossas classes de modelo.
Ele pode acompanhar as alterações nas entidades, incluindo adição e exclusão, e então dispara a criação de instruções de inserção, atualização e exclusão que são enviadas ao banco de dados sob demanda.
A seguir estão as classes de contexto de anúncio de domínio nas quais realizaremos diferentes operações neste capítulo. Este é o mesmo exemplo que criamos no capítulo, Database First Approach.
Implementação de classe de contexto
using System;
using System.Data.Entity;
using System.Data.Entity.Infrastructure;
using System.Data.Entity.Core.Objects;
using System.Linq;
namespace DatabaseFirstDemo {
public partial class UniContextEntities : DbContext {
public UniContextEntities(): base("name = UniContextEntities") {}
protected override void OnModelCreating(DbModelBuilder modelBuilder) {
throw new UnintentionalCodeFirstException();
}
public virtual DbSet<Course> Courses { get; set; }
public virtual DbSet<Enrollment> Enrollments { get; set; }
public virtual DbSet<Student> Students { get; set; }
}
}
Implementação de classes de domínio
Aula do curso
namespace DatabaseFirstDemo {
using System;
using System.Collections.Generic;
public partial class Course {
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage",
"CA2214:DoNotCallOverridableMethodsInConstructors")]
public Course() {
this.Enrollments = new HashSet<Enrollment>();
}
public int CourseID { get; set; }
public string Title { get; set; }
public int Credits { get; set; }
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage",
"CA2227:CollectionPropertiesShouldBeReadOnly")]
public virtual ICollection<Enrollment> Enrollments { get; set; }
}
}
Aula de estudante
namespace DatabaseFirstDemo {
using System;
using System.Collections.Generic;
public partial class Student {
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage",
"CA2214:DoNotCallOverridableMethodsInConstructors")]
public Student() {
this.Enrollments = new HashSet<Enrollment>();
}
public int ID { get; set; }
public string LastName { get; set; }
public string FirstMidName { get; set; }
public System.DateTime EnrollmentDate { get; set; }
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage",
"CA2227:CollectionPropertiesShouldBeReadOnly")]
public virtual ICollection<Enrollment> Enrollments { get; set; }
}
}
Aula de matrícula
namespace DatabaseFirstDemo {
using System;
using System.Collections.Generic;
public partial class Enrollment {
public int EnrollmentID { get; set; }
public int CourseID { get; set; }
public int StudentID { get; set; }
public Nullable<int> Grade { get; set; }
public virtual Course Course { get; set; }
public virtual Student Student { get; set; }
}
}
Criar operação
Adicionar um novo objeto com o Entity Framework é tão simples quanto construir uma nova instância do seu objeto e registrá-lo usando o método Add em DbSet. O código a seguir permite adicionar um novo aluno ao banco de dados.
class Program {
static void Main(string[] args) {
var newStudent = new Student();
//set student name
newStudent.FirstMidName = "Bill";
newStudent.LastName = "Gates";
newStudent.EnrollmentDate = DateTime.Parse("2015-10-21");
newStudent.ID = 100;
//create DBContext object
using (var dbCtx = new UniContextEntities()) {
//Add Student object into Students DBset
dbCtx.Students.Add(newStudent);
// call SaveChanges method to save student into database
dbCtx.SaveChanges();
}
}
}
Operação de atualização
Alterar objetos existentes é tão simples quanto atualizar o valor atribuído à (s) propriedade (s) que você deseja alterar e chamar SaveChanges. Por exemplo, o código a seguir é usado para alterar o sobrenome de Ali de Khan para Aslam.
using (var context = new UniContextEntities()) {
var student = (from d in context.Students where d.FirstMidName == "Ali" select d).Single();
student.LastName = "Aslam";
context.SaveChanges();
}
Excluir operação
Para excluir uma entidade usando o Entity Framework, você usa o método Remove em DbSet. Remova obras para entidades existentes e recém-adicionadas. Chamar Remove em uma entidade que foi adicionada, mas ainda não salva no banco de dados, cancelará a adição da entidade. A entidade é removida do rastreador de alterações e não é mais rastreada pelo DbContext. Chamar Remove em uma entidade existente que está sendo controlada por alterações registrará a entidade para exclusão na próxima vez que SaveChanges for chamado. O exemplo a seguir é de um código em que o aluno é removido do banco de dados cujo primeiro nome é Ali.
using (var context = new UniContextEntities()) {
var bay = (from d in context.Students where d.FirstMidName == "Ali" select d).Single();
context.Students.Remove(bay);
context.SaveChanges();
}
Leia a operação
Ler os dados existentes do banco de dados é muito simples. A seguir está o código em que todos os dados da tabela do Aluno são recuperados e, em seguida, um programa será exibido com o nome e o sobrenome dos alunos em ordem alfabética.
using (var db = new UniContextEntities()) {
var query = from b in db.Students orderby b.FirstMidName select b;
Console.WriteLine("All All student in the database:");
foreach (var item in query) {
Console.WriteLine(item.FirstMidName +" "+ item.LastName);
}
Console.WriteLine("Press any key to exit...");
Console.ReadKey();
}
Qualquer desenvolvedor de acesso a dados enfrenta dificuldade ao responder à pergunta sobre a simultaneidade de dados, “O que acontece se mais de uma pessoa estiver editando os mesmos dados ao mesmo tempo?”
Os mais afortunados entre nós lidam com regras de negócios que dizem "sem problemas, o último em vitórias".
Nesse caso, a simultaneidade não é um problema. Mais provavelmente, não é tão simples assim, e não existe solução mágica para resolver todos os cenários de uma vez.
Por padrão, o Entity Framework seguirá o caminho de “último em vitórias”, o que significa que a atualização mais recente é aplicada mesmo se outra pessoa atualizou os dados entre a hora em que os dados foram recuperados e a hora em que os dados foram salvos.
Vamos dar um exemplo para entender melhor. O exemplo a seguir adiciona uma nova coluna VersionNo na tabela Course.
Vá para o designer e clique com o botão direito na janela do designer e selecione atualizar o modelo do banco de dados ...
Você verá que outra coluna foi adicionada à entidade do curso.
Clique com o botão direito do mouse na coluna recém-criada VersionNo, selecione Propriedades e altere ConcurrencyMode para Fixed, conforme mostrado na imagem a seguir.
Com o ConcurrencyMode de Course.VersionNo definido como Fixo, sempre que um Curso for atualizado, o comando Atualizar procurará o Curso usando seu EntityKey e sua propriedade VersionNo.
Vamos dar uma olhada em um cenário simples. Dois usuários recuperam o mesmo curso ao mesmo tempo e o usuário 1 altera o título desse curso para Matemática e salva as alterações antes do usuário 2. Mais tarde, quando o usuário 2 altera o título daquele curso que foi recuperado antes do usuário 1 salvar suas alterações, naquele caso o usuário 2 obterá exceção de simultaneidade"User2: Optimistic Concurrency exception occured".
using System;
using System.Data.Entity;
using System.Data.Entity.Infrastructure;
using System.Linq;
namespace DatabaseFirstDemo {
class Program {
static void Main(string[] args) {
Course c1 = null;
Course c2 = null;
//User 1 gets Course
using (var context = new UniContextEntities()) {
context.Configuration.ProxyCreationEnabled = false;
c1 = context.Courses.Where(s ⇒ s.CourseID == 1).Single();
}
//User 2 also get the same Course
using (var context = new UniContextEntities()) {
context.Configuration.ProxyCreationEnabled = false;
c2 = context.Courses.Where(s ⇒ s.CourseID == 1).Single();
}
//User 1 updates Course Title
c1.Title = "Edited from user1";
//User 2 updates Course Title
c2.Title = "Edited from user2";
//User 1 saves changes first
using (var context = new UniContextEntities()) {
try {
context.Entry(c1).State = EntityState.Modified;
context.SaveChanges();
} catch (DbUpdateConcurrencyException ex) {
Console.WriteLine("User1: Optimistic Concurrency exception occurred");
}
}
//User 2 saves changes after User 1.
//User 2 will get concurrency exection
//because CreateOrModifiedDate is different in the database
using (var context = new UniContextEntities()) {
try {
context.Entry(c2).State = EntityState.Modified;
context.SaveChanges();
} catch (DbUpdateConcurrencyException ex) {
Console.WriteLine("User2: Optimistic Concurrency exception occurred");
}
}
}
}
}
Em todas as versões do Entity Framework, sempre que você executa SaveChanges()para inserir, atualizar ou excluir o banco de dados, o framework envolverá essa operação em uma transação. Quando você invoca SaveChanges, o contexto inicia automaticamente uma transação e a confirma ou reverte, dependendo se a persistência foi bem-sucedida.
Tudo isso é transparente para você e você nunca precisará lidar com isso.
Esta transação dura apenas o suficiente para executar a operação e, em seguida, é concluída.
Quando você executa outra operação, uma nova transação é iniciada.
Entity Framework 6 fornece o seguinte -
Database.BeginTransaction ()
É um método simples e fácil dentro de um DbContext existente para iniciar e concluir transações para usuários.
Ele permite que várias operações sejam combinadas na mesma transação e, portanto, todas são confirmadas ou todas são revertidas como uma.
Ele também permite que o usuário especifique mais facilmente o nível de isolamento da transação.
Database.UseTransaction ()
Ele permite que o DbContext use uma transação, que foi iniciada fora do Entity Framework.
Vamos dar uma olhada no exemplo a seguir, onde várias operações são realizadas em uma única transação. O código é como -
class Program {
static void Main(string[] args) {
using (var context = new UniContextEntities()) {
using (var dbContextTransaction = context.Database.BeginTransaction()) {
try {
Student student = new Student() {
ID = 200,
FirstMidName = "Ali",
LastName = "Khan",
EnrollmentDate = DateTime.Parse("2015-12-1")
};
context.Students.Add(student);
context.Database.ExecuteSqlCommand(@"UPDATE Course SET Title =
'Calculus'" + "WHERE CourseID = 1045");
var query = context.Courses.Where(c ⇒ c.CourseID == 1045);
foreach (var item in query) {
Console.WriteLine(item.CourseID.ToString()
+ " " + item.Title + " " + item.Credits);
}
context.SaveChanges();
var query1 = context.Students.Where(s ⇒ s.ID == 200);
foreach (var item in query1) {
Console.WriteLine(item.ID.ToString()
+ " " + item.FirstMidName + " " + item.LastName);
}
dbContextTransaction.Commit();
} catch (Exception) {
dbContextTransaction.Rollback();
}
}
}
}
}
O início de uma transação requer que a conexão da loja subjacente esteja aberta.
Portanto, chamar Database.BeginTransaction () abrirá a conexão, se ainda não estiver aberta.
Se DbContextTransaction abriu a conexão, ele a fechará quando Dispose () for chamado.
Uma visão é um objeto que contém dados obtidos por uma consulta predefinida. Uma visão é um objeto virtual ou tabela cujo conjunto de resultados é derivado de uma consulta. É muito semelhante a uma tabela real porque contém colunas e linhas de dados. A seguir estão alguns usos típicos de visualizações -
- Filtrar dados de tabelas subjacentes
- Filtrar dados para fins de segurança
- Centralize os dados distribuídos em vários servidores
- Crie um conjunto reutilizável de dados
As visualizações podem ser usadas de maneira semelhante à de tabelas. Para usar a visualização como uma entidade, primeiro você precisa adicionar visualizações do banco de dados ao EDM. Depois de adicionar vistas ao seu modelo, você pode trabalhar com ele da mesma maneira que as entidades normais, exceto para as operações Criar, Atualizar e Excluir.
Vamos dar uma olhada em como adicionar visualizações ao modelo do banco de dados.
Step 1 - Crie um novo projeto de aplicativo de console.
Step 2 - Clique com o botão direito no projeto no gerenciador de soluções e selecione Adicionar → Novo item.
Step 3 - Selecione ADO.NET Entity Data Model no painel do meio e insira o nome ViewModel no campo Nome.
Step 4 - Clique no botão Adicionar que iniciará a caixa de diálogo Assistente de modelo de dados de entidade.
Step 5 - Selecione EF Designer do banco de dados e clique no botão Avançar.
Step 6 - Selecione o banco de dados existente e clique em Avançar.
Step 7 - Escolha Entity Framework 6.xe clique em Avançar.
Step 8 - Selecione tabelas e visualizações de seu banco de dados e clique em Concluir.
Você pode ver na janela do designer que uma visualização é criada e você pode usá-la no programa como uma entidade.
No explorador de soluções, você pode ver que a classe MyView também é gerada a partir do banco de dados.
Vejamos um exemplo em que todos os dados são recuperados da visualização. A seguir está o código -
class Program {
static void Main(string[] args) {
using (var db = new UniContextEntities()) {
var query = from b in db.MyViews
orderby b.FirstMidName select b;
Console.WriteLine("All student in the database:");
foreach (var item in query) {
Console.WriteLine(item.FirstMidName + " " + item.LastName);
}
Console.WriteLine("Press any key to exit...");
Console.ReadKey();
}
}
}
Quando o código acima for executado, você receberá a seguinte saída -
All student in the database:
Ali Khan
Arturo finand
Bill Gates
Carson Alexander
Gytis Barzdukas
Laura Norman
Meredith Alonso
Nino Olivetto
Peggy Justice
Yan Li
Press any key to exit...
Recomendamos que você execute o exemplo acima passo a passo para melhor compreensão.
Um índice é uma estrutura de dados em disco baseada em tabelas e visualizações. Os índices tornam a recuperação de dados mais rápida e eficiente, na maioria dos casos. No entanto, sobrecarregar uma tabela ou exibição com índices pode afetar desagradavelmente o desempenho de outras operações, como inserções ou atualizações.
A indexação é o novo recurso na estrutura de entidade onde você pode melhorar o desempenho de seu aplicativo Code First reduzindo o tempo necessário para consultar dados do banco de dados.
Você pode adicionar índices ao seu banco de dados usando o Index atributo e substitui o padrão Unique e Clustered configurações para obter o índice mais adequado ao seu cenário.
Vamos dar uma olhada no código a seguir, no qual o atributo Index é adicionado na classe Course para CourseID.
public partial class Course {
public Course() {
this.Enrollments = new HashSet<Enrollment>();
}
[Index]
public int CourseID { get; set; }
public string Title { get; set; }
public int Credits { get; set; }
public byte[] VersionNo { get; set; }
public virtual ICollection<Enrollment> Enrollments { get; set; }
}
A chave criada acima não é exclusiva, não é agrupada. Existem sobrecargas disponíveis para substituir esses padrões -
Para tornar um índice um índice de cluster, você precisa especificar IsClustered = true
Da mesma forma, você também pode tornar um índice um índice exclusivo especificando IsUnique = true
Vamos dar uma olhada no código C # a seguir, em que um índice é agrupado e exclusivo.
public partial class Course {
public Course() {
this.Enrollments = new HashSet<Enrollment>();
}
[Index(IsClustered = true, IsUnique = true)]
public int CourseID { get; set; }
public string Title { get; set; }
public int Credits { get; set; }
public byte[] VersionNo { get; set; }
public virtual ICollection<Enrollment> Enrollments { get; set; }
}
O atributo de índice pode ser usado para criar um índice exclusivo no banco de dados. No entanto, isso não significa que EF será capaz de raciocinar sobre a exclusividade da coluna ao lidar com relacionamentos, etc. Esse recurso é geralmente referido como suporte para “restrições exclusivas”.
O Entity Framework permite que você use procedimentos armazenados no Entity Data Model em vez de, ou em combinação com, sua geração automática de comando.
Você pode usar procedimentos armazenados para executar lógica predefinida em tabelas de banco de dados, e muitas organizações têm políticas em vigor que exigem o uso desses procedimentos armazenados.
Ele também pode especificar que EF deve usar seus procedimentos armazenados para inserir, atualizar ou excluir entidades.
Embora os comandos construídos dinamicamente sejam seguros, eficientes e geralmente tão bons ou melhores do que aqueles que você mesmo escreve, há muitos casos em que já existem procedimentos armazenados e as práticas da sua empresa podem restringir o uso direto das tabelas.
Como alternativa, você pode apenas desejar ter controle explícito sobre o que é executado na loja e preferir criar procedimentos armazenados.
O exemplo a seguir cria um novo projeto em Arquivo → Novo → Projeto.
Step 1 - Selecione o aplicativo de console no painel do meio e insira StoredProceduresDemo no campo de nome.
Step 2 - No explorador de servidores, clique com o botão direito em seu banco de dados.
Step 3 - Selecione Nova consulta e insira o código a seguir no editor T-SQL para adicionar uma nova tabela em seu banco de dados.
IF NOT EXISTS (SELECT * FROM sys.objects WHERE object_id =
OBJECT_ID(N'[dbo].[StudentGrade]') AND type in (N'U'))
BEGIN
CREATE TABLE [dbo].[StudentGrade](
[EnrollmentID] [int] IDENTITY(1,1) NOT NULL,
[CourseID] [int] NOT NULL,
[StudentID] [int] NOT NULL,
[Grade] [decimal](3, 2) NULL,
CONSTRAINT [PK_StudentGrade] PRIMARY KEY CLUSTERED (
[EnrollmentID] ASC
)
WITH (IGNORE_DUP_KEY = OFF) ON [PRIMARY]
) ON [PRIMARY]
END
GO
Step 4 - Clique com o botão direito no editor e selecione Executar.
Step 5- Clique com o botão direito em seu banco de dados e clique em atualizar. Você verá a tabela recém-adicionada em seu banco de dados.
Step 6 - No Server explorer, clique com o botão direito do mouse em seu banco de dados novamente.
Step 7 - Selecione Nova Consulta e digite o seguinte código no editor T-SQL para adicionar um procedimento armazenado em seu banco de dados, que retornará as notas dos Alunos.
IF NOT EXISTS (SELECT * FROM sys.objects WHERE object_id =
OBJECT_ID(N'[dbo].[GetStudentGrades]') AND type in (N'P', N'PC'))
BEGIN
EXEC dbo.sp_executesql @statement = N'
CREATE PROCEDURE [dbo].[GetStudentGrades]
@StudentID int
AS
SELECT EnrollmentID, Grade, CourseID, StudentID FROM dbo.StudentGrade
WHERE StudentID = @StudentID
'
END
GO
Step 8 - Clique com o botão direito no editor e selecione Executar.
Step 9- Clique com o botão direito em seu banco de dados e clique em atualizar. Você verá que um procedimento armazenado é criado em seu banco de dados.
Step 10 - Clique com o botão direito do mouse no nome do projeto no Solution Explorer e selecione Add → New Item.
Step 11 - Em seguida, selecione ADO.NET Entity Data Model no painel Templates.
Step 12 - Insira SPModel como nome e clique em Adicionar.
Step 13 - Na caixa de diálogo Choose Model Contents, selecione EF designer do banco de dados e clique em Next.
Step 14 - Selecione seu banco de dados e clique em Avançar.
Step 15 - Na caixa de diálogo Choose Your Database Objects, clique em tables, views.
Step 16 - Selecione a função GetStudentGradesForCourse localizada no nó Stored Procedures and Functions e clique em Concluir.
Step 17 - Selecione Exibir → Outras janelas → Navegador de modelo de dados de entidade e clique com o botão direito em GetStudentGrades em Importações de funções e selecione Editar.
Isso produzirá o seguinte diálogo.
Step 18 - Clique no botão de rádio Entities e selecione StudentGrade na combobox como tipo de retorno deste procedimento armazenado e clique em Ok.
Vamos dar uma olhada no código C # a seguir, no qual todas as notas serão recuperadas passando a ID do aluno como parâmetro no procedimento armazenado GetStudentGrades.
class Program {
static void Main(string[] args) {
using (var context = new UniContextEntities()) {
int studentID = 22;
var studentGrades = context.GetStudentGrades(studentID);
foreach (var student in studentGrades) {
Console.WriteLine("Course ID: {0}, Title: {1}, Grade: {2} ",
student.CourseID, student.Course.Title, student.Grade);
}
Console.ReadKey();
}
}
}
Quando o código acima for compilado e executado, você receberá a seguinte saída -
Course ID: 4022, Title: Microeconomics, Grade: 3.00
Course ID: 4041, Title: Macroeconomics, Grade: 3.50
Recomendamos que você execute o exemplo acima passo a passo para melhor compreensão.
Neste capítulo, vamos ver como fazer alterações em entidades que não estão sendo rastreadas por um contexto. As entidades que não estão sendo rastreadas por um contexto são conhecidas como entidades 'desconectadas'.
Para a maioria dos aplicativos de camada única, onde a interface do usuário e as camadas de acesso ao banco de dados são executadas no mesmo processo do aplicativo, você provavelmente executará apenas operações em entidades que estão sendo rastreadas por um contexto.
As operações em entidades desconectadas são muito mais comuns em aplicativos N-Tier.
Os aplicativos N-Tier envolvem buscar alguns dados em um servidor e devolvê-los, pela rede, a uma máquina cliente.
O aplicativo cliente então manipula esses dados antes de retorná-los ao servidor para serem persistidos.
A seguir estão as duas etapas que precisam ser realizadas com o gráfico de entidade desconectada ou até mesmo uma única entidade desconectada.
Anexe entidades com a nova instância de contexto e torne o contexto ciente dessas entidades.
Defina EntityStates apropriados para essas entidades manualmente.
Vamos dar uma olhada no código a seguir, no qual a entidade Student é adicionada a duas entidades Enrollment.
class Program {
static void Main(string[] args) {
var student = new Student {
ID = 1001,
FirstMidName = "Wasim",
LastName = "Akram",
EnrollmentDate = DateTime.Parse("2015-10-10"),
Enrollments = new List<Enrollment> {
new Enrollment{EnrollmentID = 2001,CourseID = 4022, StudentID = 1001 },
new Enrollment{EnrollmentID = 2002,CourseID = 4025, StudentID = 1001 },
}
};
using (var context = new UniContextEntities()) {
context.Students.Add(student);
Console.WriteLine("New Student ({0} {1}): {2}",
student.FirstMidName, student.LastName, context.Entry(student).State);
foreach (var enrollment in student.Enrollments) {
Console.WriteLine("Enrollment ID: {0} State: {1}",
enrollment.EnrollmentID, context.Entry(enrollment).State);
}
Console.WriteLine("Press any key to exit...");
Console.ReadKey();
}
}
}
O código constrói uma nova instância Student, que também faz referência a duas novas instâncias Enrollment em sua propriedade Enrollments.
Em seguida, o novo aluno é adicionado a um contexto usando o método Add.
Depois que o aluno é adicionado, o código usa o método DbContext.Entry para obter acesso às informações de controle de alterações que o Entity Framework tem sobre o novo aluno.
A partir dessas informações de controle de alterações, a propriedade State é usada para gravar o estado atual da entidade.
Este processo é então repetido para cada uma das Matrículas recém-criadas que são referenciadas pelo novo Aluno. Se você executar o aplicativo, receberá a seguinte saída -
New Student (Wasim Akram): Added
Enrollment ID: 2001 State: Added
Enrollment ID: 2002 State: Added
Press any key to exit...
Enquanto DbSet.Add é usado para informar ao Entity Framework sobre novas entidades, DbSet.Attach é usado para informar ao Entity Framework sobre entidades existentes. O método Attach marcará uma entidade no estado Inalterado.
Vamos dar uma olhada no código C # a seguir, no qual uma entidade desconectada é anexada ao DbContext.
class Program {
static void Main(string[] args) {
var student = new Student {
ID = 1001,
FirstMidName = "Wasim",
LastName = "Akram",
EnrollmentDate = DateTime.Parse("2015-10-10"),
Enrollments = new List<Enrollment> {
new Enrollment { EnrollmentID = 2001, CourseID = 4022, StudentID = 1001 },
new Enrollment { EnrollmentID = 2002, CourseID = 4025, StudentID = 1001 },
}
};
using (var context = new UniContextEntities()) {
context.Students.Attach(student);
Console.WriteLine("New Student ({0} {1}): {2}",
student.FirstMidName, student.LastName, context.Entry(student).State);
foreach (var enrollment in student.Enrollments) {
Console.WriteLine("Enrollment ID: {0} State: {1}", enrollment.EnrollmentID,
context.Entry(enrollment).State);
}
Console.WriteLine("Press any key to exit...");
Console.ReadKey();
}
}
}
Quando o código acima é executado com o método Attach (), você receberá a seguinte saída.
New Student (Wasim Akram): Unchanged
Enrollment ID: 2001 State: Unchanged
Enrollment ID: 2002 State: Unchanged
Press any key to exit...
Neste capítulo, vamos aprender como mapear funções com valor de tabela (TVFs) usando o Entity Framework Designer e como chamar um TVF de uma consulta LINQ.
Atualmente, os TVFs são compatíveis apenas com o fluxo de trabalho do Database First.
Ele foi introduzido pela primeira vez no Entity Framework versão 5.
Para usar os TVFs, você deve direcionar o .NET Framework 4.5 ou superior.
É muito semelhante aos procedimentos armazenados, mas com uma diferença fundamental, ou seja, o resultado de um TVF é composível. Isso significa que os resultados de um TVF podem ser usados em uma consulta LINQ, enquanto os resultados de um procedimento armazenado não podem.
Vamos dar uma olhada no seguinte exemplo de criação de um novo projeto em Arquivo → Novo → Projeto.
Step 1 - Selecione o aplicativo de console no painel do meio e insira TableValuedFunctionDemo no campo de nome.
Step 2 - No explorador de servidores, clique com o botão direito em seu banco de dados.
Step 3 - Selecione Nova consulta e insira o código a seguir no editor T-SQL para adicionar uma nova tabela em seu banco de dados.
IF NOT EXISTS (SELECT * FROM sys.objects WHERE object_id =
OBJECT_ID(N'[dbo].[StudentGrade]') AND type in (N'U'))
BEGIN
CREATE TABLE [dbo].[StudentGrade](
[EnrollmentID] [int] IDENTITY(1,1) NOT NULL,
[CourseID] [int] NOT NULL,
[StudentID] [int] NOT NULL,
[Grade] [decimal](3, 2) NULL,
CONSTRAINT [PK_StudentGrade] PRIMARY KEY CLUSTERED ([EnrollmentID] ASC)
WITH (IGNORE_DUP_KEY = OFF) ON [PRIMARY]
) ON [PRIMARY]
END
GO
Step 4 - Clique com o botão direito no editor e selecione Executar.
Step 5- Clique com o botão direito em seu banco de dados e clique em atualizar. Você verá a tabela recém-adicionada em seu banco de dados.
Step 6- Agora crie uma função que retornará as notas dos alunos para o curso. Insira o código a seguir no editor T-SQL.
CREATE FUNCTION [dbo].[GetStudentGradesForCourse]
(@CourseID INT)
RETURNS TABLE
RETURN
SELECT [EnrollmentID],
[CourseID],
[StudentID],
[Grade]
FROM [dbo].[StudentGrade]
WHERE CourseID = @CourseID
Step 7 - Clique com o botão direito no editor e selecione Executar.
Agora você pode ver que a função foi criada.
Step 8 - Clique com o botão direito no nome do projeto no Solution Explorer e selecione Add → New Item.
Step 9 - Em seguida, selecione ADO.NET Entity Data Model no painel Templates.
Step 10 - Insira TVFModel como nome e clique em Adicionar.
Step 11 - Na caixa de diálogo Choose Model Contents, selecione EF designer do banco de dados e clique em Next.
Step 12 - Selecione seu banco de dados e clique em Avançar.
Step 13 - Na caixa de diálogo Escolha seus objetos de banco de dados, selecione tabelas, visualizações.
Step 14 - Selecione a função GetStudentGradesForCourse localizada no nó Stored Procedures and Functions e clique em Concluir.
Step 15 - Selecione Exibir → Outras janelas → Navegador de modelo de dados de entidade e clique com o botão direito em GetStudentGradesForCourse em Importações de função e selecione Editar.
Você verá a seguinte caixa de diálogo.
Step 16 - Clique no botão de rádio Entidades e selecione Enrollment na combobox como tipo de retorno desta Função e clique em Ok.
Vamos dar uma olhada no código C # a seguir, no qual todas as notas dos alunos que estão matriculados no ID do curso = 4022 no banco de dados serão recuperadas.
class Program {
static void Main(string[] args) {
using (var context = new UniContextEntities()) {
var CourseID = 4022;
// Return all the best students in the Microeconomics class.
var students = context.GetStudentGradesForCourse(CourseID);
foreach (var result in students) {
Console.WriteLine("Student ID: {0}, Grade: {1}",
result.StudentID, result.Grade);
}
Console.ReadKey();
}
}
}
Quando o código acima for compilado e executado, você receberá a seguinte saída -
Student ID: 1, Grade: 2
Student ID: 4, Grade: 4
Student ID: 9, Grade: 3.5
Recomendamos que você execute o exemplo acima passo a passo para melhor compreensão.
No Entity Framework, você pode consultar suas classes de entidade usando LINQ. Você também pode executar consultas usando SQL bruto diretamente no banco de dados usando DbCOntext. As técnicas podem ser aplicadas igualmente a modelos criados com Code First e EF Designer.
Consulta SQL em entidade existente
O método SqlQuery em DbSet permite que uma consulta SQL bruta seja escrita para retornar instâncias de entidade. Os objetos retornados serão rastreados pelo contexto da mesma forma que seriam se fossem retornados por uma consulta LINQ. Por exemplo -
class Program {
static void Main(string[] args) {
using (var context = new UniContextEntities()) {
var students = context.Students.SqlQuery("SELECT * FROM dbo.Student").ToList();
foreach (var student in students) {
string name = student.FirstMidName + " " + student.LastName;
Console.WriteLine("ID: {0}, Name: {1}, \tEnrollment Date {2} ",
student.ID, name, student.EnrollmentDate.ToString());
}
Console.ReadKey();
}
}
}
O código acima irá recuperar todos os alunos do banco de dados.
Consulta SQL para tipos de não entidade
Uma consulta SQL que retorna instâncias de qualquer tipo, incluindo tipos primitivos, pode ser criada usando o método SqlQuery na classe Database. Por exemplo -
class Program {
static void Main(string[] args) {
using (var context = new UniContextEntities()) {
var studentNames = context.Database.SqlQuery
<string>("SELECT FirstMidName FROM dbo.Student").ToList();
foreach (var student in studentNames) {
Console.WriteLine("Name: {0}", student);
}
Console.ReadKey();
}
}
}
Comandos SQL para o banco de dados
O método ExecuteSqlCommnad é usado para enviar comandos que não sejam de consulta ao banco de dados, como o comando Inserir, Atualizar ou Excluir. Vamos dar uma olhada no código a seguir, no qual o nome do aluno é atualizado como ID = 1
class Program {
static void Main(string[] args) {
using (var context = new UniContextEntities()) {
//Update command
int noOfRowUpdated = context.Database.ExecuteSqlCommand("Update
student set FirstMidName = 'Ali' where ID = 1");
context.SaveChanges();
var student = context.Students.SqlQuery("SELECT * FROM
dbo.Student where ID = 1").Single();
string name = student.FirstMidName + " " + student.LastName;
Console.WriteLine("ID: {0}, Name: {1}, \tEnrollment Date {2} ",
student.ID, name, student.EnrollmentDate.ToString());
Console.ReadKey();
}
}
}
O código acima irá recuperar o primeiro nome de todos os alunos do banco de dados.
No Entity Framework, esse recurso permitirá que você defina uma propriedade em uma classe de domínio que seja um tipo de enum e mapeie-a para uma coluna de banco de dados de um tipo inteiro. O Entity Framework converterá então o valor do banco de dados de e para o enum relevante conforme ele consulta e salva os dados.
Os tipos enumerados têm todos os tipos de benefícios ao trabalhar com propriedades que têm um número fixo de respostas.
A segurança e a confiabilidade de um aplicativo aumentam quando você usa enumerações.
A enumeração torna muito mais difícil para o usuário cometer erros e problemas como ataques de injeção são inexistentes.
No Entity Framework, uma enumeração pode ter os seguintes tipos subjacentes -
- Byte
- Int16
- Int32
- Int64
- SByte
O tipo subjacente padrão dos elementos de enumeração é int.
Por padrão, o primeiro enumerador tem o valor 0 e o valor de cada enumerador sucessivo é aumentado em 1.
Vamos dar uma olhada no exemplo a seguir, no qual criaremos uma entidade no designer e adicionaremos algumas propriedades.
Step 1 - Crie um novo projeto a partir da opção de menu Arquivo → Novo → Projeto.
Step 2 - No painel esquerdo, selecione o aplicativo de console.
Step 3 - Insira EFEnumDemo como o nome do projeto e clique em OK.
Step 4 - Clique com o botão direito do mouse no nome do projeto no Solution Explorer e selecione a opção de menu Adicionar → Novo Item.
Step 5 - Selecione ADO.NET Entity Data Model no painel Templates.
Step 6 - Digite EFEnumModel.edmx para o nome do arquivo e clique em Adicionar.
Step 7 - Na página Entity Data Model Wizard, selecione Empty EF designer Model.
Step 8 - Clique em Concluir
Step 9 - Em seguida, clique com o botão direito na janela do designer e selecione Adicionar → Entidade.
A caixa de diálogo Nova entidade aparece conforme mostrado na imagem a seguir.
Step 10 - Insira Department como um nome de entidade e DeptID como um nome de propriedade, deixe o tipo de propriedade como Int32 e clique em OK.
Step 11 - Clique com o botão direito na entidade e selecione Adicionar Novo → Propriedade Escalar.
Step 12 - Renomeie a nova propriedade para DeptName.
Step 13 - Altere o tipo da nova propriedade para Int32 (por padrão, a nova propriedade é do tipo String).
Step 14 - Para alterar o tipo, abra a janela Propriedades e altere a propriedade Type para Int32.
Step 15 - No Entity Framework Designer, clique com o botão direito na propriedade Nome e selecione Converter em enum.
Step 16 - Na caixa de diálogo Adicionar tipo de Enum, digite DepartmentNames para o nome do tipo de Enum, altere o tipo subjacente para Int32 e adicione os seguintes membros ao tipo: Physics, Chemistry, Computer, and Economics.
Step 17 - Clique em Ok.
Se você alternar para a janela Model Browser, verá que o tipo também foi adicionado ao nó Enum Types.
Vamos gerar o banco de dados a partir do modelo, seguindo todas as etapas mencionadas no capítulo de abordagem Model First.
Step 1 - Clique com o botão direito na superfície do Entity Designer e selecione Generate Database from Model.
A caixa de diálogo Choose Your Data Connection do Generate Database Wizard é exibida.
Step 2 - Clique no botão Nova conexão.
Step 3 - Insira o nome do servidor e EnumDemo para o banco de dados e clique em OK.
Step 4 - Uma caixa de diálogo perguntando se você deseja criar um novo banco de dados aparecerá, clique em Sim.
Step 5- Clique em Avançar e o Assistente para Criação de Banco de Dados gera linguagem de definição de dados (DDL) para a criação de um banco de dados. Agora clique em Concluir.
Step 6 - Clique com o botão direito do mouse em T-SQL Editor e selecione Executar.
Step 7 - Para visualizar o esquema gerado, clique com o botão direito do mouse no nome do banco de dados no SQL Server Object Explorer e selecione Atualizar.
Você verá a tabela Departments no banco de dados.
Vamos dar uma olhada no exemplo a seguir, no qual alguns novos objetos Department para o contexto são adicionados e salvos. E então recupere o departamento de informática.
class Program {
static void Main(string[] args) {
using (var context = new EFEnumModelContainer()) {
context.Departments.Add(new Department { DeptName = DepartmentNames.Physics});
context.Departments.Add(new Department { DeptName = DepartmentNames.Computer});
context.Departments.Add(new Department { DeptName = DepartmentNames.Chemistry});
context.Departments.Add(new Department { DeptName = DepartmentNames.Economics});
context.SaveChanges();
var department = (
from d in context.Departments
where d.DeptName == DepartmentNames.Computer
select d
).FirstOrDefault();
Console.WriteLine(
"Department ID: {0}, Department Name: {1}",
department.DeptID, department.DeptName
);
Console.ReadKey();
}
}
}
Quando o código acima for executado, você receberá a seguinte saída -
Department ID: 2, Department Name: Computer
Recomendamos que você execute o exemplo acima passo a passo para melhor compreensão.
Asynchronous programmingenvolve a execução de operações em segundo plano para que o thread principal possa continuar suas próprias operações. Dessa forma, o thread principal pode manter a interface do usuário responsiva enquanto o thread em segundo plano está processando a tarefa em questão.
O Entity Framework 6.0 oferece suporte a operações assíncronas para consultar e salvar dados.
As operações assíncronas podem ajudar seu aplicativo das seguintes maneiras -
- Torne seu aplicativo mais responsivo às interações do usuário
- Melhore o desempenho geral do seu aplicativo
Você pode executar operações assíncronas de várias maneiras. Mas as palavras-chave async / await foram introduzidas no .NET Framework 4.5, o que simplifica seu trabalho.
A única coisa que você precisa seguir é o padrão async / await conforme ilustrado pelo fragmento de código a seguir.
Vamos dar uma olhada no exemplo a seguir (sem usar async / await) em que o método DatabaseOperations salva um novo aluno no banco de dados e, em seguida, recupera todos os alunos do banco de dados e, no final, alguma mensagem adicional é impressa no console.
class Program {
static void Main(string[] args) {
Console.WriteLine("Database Operations Started");
DatabaseOperations();
Console.WriteLine();
Console.WriteLine("Database Operations Completed");
Console.WriteLine();
Console.WriteLine("Entity Framework Tutorials");
Console.ReadKey();
}
public static void DatabaseOperations() {
using (var context = new UniContextEntities()) {
// Create a new student and save it
context.Students.Add(new Student {
FirstMidName = "Akram",
LastName = "Khan",
EnrollmentDate = DateTime.Parse(DateTime.Today.ToString())});
Console.WriteLine("Calling SaveChanges.");
context.SaveChanges();
Console.WriteLine("SaveChanges completed.");
// Query for all Students ordered by first name
var students = (from s in context.Students
orderby s.FirstMidName select s).ToList();
// Write all students out to Console
Console.WriteLine();
Console.WriteLine("All Student:");
foreach (var student in students) {
string name = student.FirstMidName + " " + student.LastName;
Console.WriteLine(" " + name);
}
}
}
}
Quando o código acima for executado, você receberá a seguinte saída -
Calling SaveChanges.
SaveChanges completed.
All Student:
Akram Khan
Ali Khan
Ali Alexander
Arturo Anand
Bill Gates
Gytis Barzdukas
Laura Nornan
Meredith fllonso
Nino Olioetto
Peggy Justice
Yan Li
Entity Framework Tutorials
Vamos usar o novo async e aguardar as palavras-chave e fazer as seguintes alterações em Program.cs
Adicione o namespace System.Data.Entity que fornecerá métodos de extensão assíncronos EF.
Adicione o namespace System.Threading.Tasks, que nos permitirá usar o tipo de tarefa.
Atualizar DatabaseOperations ser marcado como async e devolver um Task.
Chame a versão Async de SaveChanges e aguarde sua conclusão.
Chame a versão Async de ToList e aguarde o resultado.
class Program {
static void Main(string[] args) {
var task = DatabaseOperations();
Console.WriteLine();
Console.WriteLine("Entity Framework Tutorials");
task.Wait();
Console.ReadKey();
}
public static async Task DatabaseOperations() {
using (var context = new UniContextEntities()) {
// Create a new blog and save it
context.Students.Add(new Student {
FirstMidName = "Salman",
LastName = "Khan",
EnrollmentDate = DateTime.Parse(DateTime.Today.ToString())});
Console.WriteLine("Calling SaveChanges.");
await context.SaveChangesAsync();
Console.WriteLine("SaveChanges completed.");
// Query for all Students ordered by first name
var students = await (from s in context.Students
orderby s.FirstMidName select s).ToListAsync();
// Write all students out to Console
Console.WriteLine();
Console.WriteLine("All Student:");
foreach (var student in students) {
string name = student.FirstMidName + " " + student.LastName;
Console.WriteLine(" " + name);
}
}
}
}
Na execução, ele produzirá a seguinte saída.
Calling SaveChanges.
Entity Framework Tutorials
SaveChanges completed.
All Student:
Akram Khan
Ali Khan
Ali Alexander
Arturo Anand
Bill Gates
Gytis Barzdukas
Laura Nornan
Meredith fllonso
Nino Olioetto
Peggy Justice
Salman Khan
Yan Li
Agora que o código é assíncrono, você pode observar um fluxo de execução diferente do seu programa.
SaveChanges começa a enviar o novo Aluno para o banco de dados e, em seguida, o método DatabaseOperations retorna (embora não tenha concluído a execução) e o fluxo do programa no método Main continua.
A mensagem é então gravada no console.
O encadeamento gerenciado é bloqueado na chamada Aguardar até que a operação do banco de dados seja concluída. Assim que terminar, o restante de nosso DatabaseOperations será executado.
SaveChanges é concluído.
Todos os alunos são recuperados do banco de dados e gravados no Console.
Recomendamos que você execute o exemplo acima passo a passo para melhor compreensão.
O Entity Framework agora permite que você se beneficie do Entity Framework sem forçar todas as partes do seu aplicativo a conhecer o Entity Framework, separando as entidades da infraestrutura. Você pode criar classes que podem se concentrar em suas regras de negócios, independentemente de como são persistentes (onde os dados são armazenados e como os dados vão e vêm entre seus objetos).
Criação de entidades ignorantes persistentes
O parágrafo anterior descreveu um método que não tem conhecimento íntimo da fonte dos dados que consome. Isso destaca a essência da ignorância da persistência, que ocorre quando suas classes e muitas de nossas camadas de aplicativos em torno delas não se importam como os dados são armazenados.
Na versão .NET 3.5 do Entity Framework, se você quisesse usar classes preexistentes, era necessário modificá-las, forçando-as a derivar de EntityObject.
No .NET 4, isso não é mais necessário. Você não precisa modificar suas entidades para que elas participem das operações do Entity Framework.
Isso nos permite construir aplicativos que adotam o acoplamento fraco e a separação de interesses.
Com esses padrões de codificação, suas classes estão preocupadas apenas com seus próprios trabalhos e, muitas camadas de seu aplicativo, incluindo a IU, não têm dependências de lógica externa, como as APIs do Entity Framework, embora essas APIs externas sejam capazes de interagir com nossos entidades.
Existem 2 maneiras (conectada e desconectada) ao persistir uma entidade com o Entity Framework. Ambas as formas têm sua própria importância. No caso de um cenário conectado, as mudanças são rastreadas pelo contexto, mas no caso de um cenário desconectado, precisamos informar o contexto sobre o estado da entidade.
Cenários Conectados
O cenário conectado é quando uma entidade é recuperada do banco de dados e modificada no mesmo contexto. Para um cenário conectado, vamos supor que temos um serviço Windows e estamos fazendo algumas operações de negócios com essa entidade, então vamos abrir o contexto, percorrer todas as entidades, fazer nossas operações de negócios e salvar as alterações com o mesmo contexto que nós abriu no início.
Vamos dar uma olhada no exemplo a seguir, no qual os alunos são recuperados do banco de dados e atualizam o primeiro nome dos alunos e, em seguida, salvam as alterações no banco de dados.
class Program {
static void Main(string[] args) {
using (var context = new MyContext()) {
var studentList = context.Students.ToList();
foreach (var stdnt in studentList) {
stdnt.FirstMidName = "Edited " + stdnt.FirstMidName;
}
context.SaveChanges();
//// Display all Students from the database
var students = (from s in context.Students
orderby s.FirstMidName select s).ToList<Student>();
Console.WriteLine("Retrieve all Students from the database:");
foreach (var stdnt in students) {
string name = stdnt.FirstMidName + " " + stdnt.LastName;
Console.WriteLine("ID: {0}, Name: {1}", stdnt.ID, name);
}
Console.ReadKey();
}
}
}
Quando o código acima for compilado e executado, você receberá a seguinte saída e verá que a palavra editada é anexada antes do primeiro nome, conforme mostrado na seguinte saída.
Retrieve all Students from the database:
ID: 1, Name: Edited Edited Alain Bomer
ID: 2, Name: Edited Edited Mark Upston
Cenários desconectados
O cenário desconectado é quando uma entidade é recuperada do banco de dados e modificada em um contexto diferente. Vamos supor que queremos exibir alguns dados em uma camada de apresentação e estamos usando algum aplicativo de n camadas, portanto, seria melhor abrir o contexto, buscar os dados e finalmente fechar o contexto. Uma vez que buscamos os dados e fechamos o contexto, as entidades que buscamos não são mais rastreadas e este é o cenário desconectado.
Vamos dar uma olhada no código a seguir, no qual uma nova entidade de aluno desconectada é adicionada a um contexto usando o método Add.
class Program {
static void Main(string[] args) {
var student = new Student {
ID = 1001,
FirstMidName = "Wasim",
LastName = "Akram",
EnrollmentDate = DateTime.Parse( DateTime.Today.ToString())
};
using (var context = new MyContext()) {
context.Students.Add(student);
context.SaveChanges();
//// Display all Students from the database
var students = (from s in context.Students
orderby s.FirstMidName select s).ToList<Student>();
Console.WriteLine("Retrieve all Students from the database:");
foreach (var stdnt in students) {
string name = stdnt.FirstMidName + " " + stdnt.LastName;
Console.WriteLine("ID: {0}, Name: {1}", stdnt.ID, name);
}
Console.ReadKey();
}
}
}
Quando o código acima for compilado e executado, você receberá a seguinte saída.
Retrieve all Students from the database:
ID: 1, Name: Edited Edited Edited Alain Bomer
ID: 2, Name: Edited Edited Edited Mark Upston
ID: 3, Name: Wasim Akram
LINQ to Entities
Um dos conceitos mais importantes para entender o LINQ to Entities é que ele é uma linguagem declarativa. O foco está em definir quais informações você precisa, ao invés de como obter as informações.
Isso significa que você pode gastar mais tempo trabalhando com dados e menos tempo tentando descobrir o código subjacente necessário para executar tarefas como acessar o banco de dados.
É importante entender que as linguagens declarativas não removem nenhum controle do desenvolvedor, mas ajudam o desenvolvedor a concentrar a atenção no que é importante.
LINQ to Entities Essential Keywords
É importante conhecer as palavras-chave básicas usadas para criar uma consulta LINQ. Existem apenas algumas palavras-chave a serem lembradas, mas você pode combiná-las de várias maneiras para obter resultados específicos. A lista a seguir contém essas palavras-chave básicas e fornece uma descrição simples de cada uma.
Sr. Não. | Palavra-chave e descrição |
---|---|
1 | Ascending Especifica que uma operação de classificação ocorre do menor (ou menor) elemento de um intervalo para o elemento mais alto de um intervalo. Normalmente, esta é a configuração padrão. Por exemplo, ao realizar uma classificação alfabética, a classificação estaria no intervalo de A a Z. |
2 | By Especifica o campo ou expressão usado para implementar um agrupamento. O campo ou expressão define uma chave usada para realizar a tarefa de agrupamento. |
3 | Descending Especifica que uma operação de classificação ocorre do maior (ou mais alto) elemento de um intervalo para o elemento mais baixo de um intervalo. Por exemplo, ao realizar uma classificação alfabética, a classificação estaria no intervalo de Z a A. |
4 | Equals Usado entre as cláusulas esquerda e direita de uma instrução de junção para juntar a fonte de dados contextuais primária à fonte de dados contextuais secundária. O campo ou expressão à esquerda da palavra-chave equals especifica a fonte de dados primária, enquanto o campo ou expressão à direita da palavra-chave equals especifica a fonte de dados secundária. |
5 | From Especifica a fonte de dados usada para obter as informações necessárias e define uma variável de intervalo. Esta variável tem o mesmo propósito que uma variável usada para iteração em um loop. |
6 | Group Organiza a saída em grupos usando o valor-chave que você especificar. Use várias cláusulas de grupo para criar vários níveis de organização de saída. A ordem das cláusulas de grupo determina a profundidade em que um determinado valor-chave aparece na ordem de agrupamento. Você combina esta palavra-chave com por para criar um contexto específico. |
7 | In Usado de várias maneiras. Nesse caso, a palavra-chave determina a fonte de banco de dados contextual usada para uma consulta. Ao trabalhar com uma junção, a palavra-chave in é usada para cada fonte de banco de dados contextual usada para a junção. |
8 | Into Especifica um identificador que você pode usar como referência para cláusulas de consulta LINQ, como junção, grupo e seleção. |
9 | Join Cria uma única fonte de dados a partir de duas fontes de dados relacionadas, como em uma configuração mestre / detalhada. Uma junção pode especificar uma junção interna, de grupo ou externa esquerda, com a junção interna como padrão. Você pode ler mais sobre junções em msdn.microsoft.com |
10 | Let Define uma variável de intervalo que você pode usar para armazenar resultados de subexpressão em uma expressão de consulta. Normalmente, a variável de intervalo é usada para fornecer uma saída enumerada adicional ou para aumentar a eficiência de uma consulta (de forma que uma tarefa específica, como encontrar o valor em minúsculas de uma string, não precise ser feita mais de uma vez). |
11 | On Especifica o campo ou expressão usado para implementar uma junção. O campo ou expressão define um elemento que é comum a ambas as fontes de dados contextuais. |
12 | Orderby Cria uma ordem de classificação para a consulta. Você pode adicionar a palavra-chave crescente ou decrescente para controlar a ordem da classificação. Use várias cláusulas orderby para criar vários níveis de classificação. A ordem das cláusulas orderby determina a ordem em que as expressões de classificação são tratadas, portanto, usar uma ordem diferente resultará em uma saída diferente. |
13 | Where Define o que LINQ deve recuperar da fonte de dados. Você usa uma ou mais expressões booleanas para definir as especificações do que recuperar. As expressões booleanas são separadas umas das outras usando && (AND) e || Operadores (OR). |
14 | Select Determina a saída da consulta LINQ especificando quais informações retornar. Essa instrução define o tipo de dados dos elementos que LINQ retorna durante o processo de iteração. |
Projeção
As consultas de projeção melhoram a eficiência de seu aplicativo, recuperando apenas campos específicos de seu banco de dados.
Depois de ter os dados, você pode querer projetá-los ou filtrá-los conforme necessário para moldar os dados antes da saída.
A principal tarefa de qualquer expressão LINQ to Entities é obter dados e fornecê-los como saída.
A seção “Desenvolvimento de consultas LINQ to Entities” deste capítulo demonstra as técnicas para realizar essa tarefa básica.
Vamos dar uma olhada no código a seguir, no qual a lista de alunos será recuperada.
using (var context = new UniContextEntities()) {
var studentList = from s in context.Students select s;
foreach (var student in studentList) {
string name = student.FirstMidName + " " + student.LastName;
Console.WriteLine("ID : {0}, Name: {1}", student.ID, name);
}
}
Objeto Único
Para recuperar um único objeto de aluno, você pode usar os métodos enumeráveis First () ou FirstOrDefault, que retornam o primeiro elemento de uma sequência. A diferença entre First e FirstOrDefault é que First () lançará uma exceção, se não houver dados de resultado para os critérios fornecidos, enquanto FirstOrDefault () retorna o valor padrão null, se não houver dados de resultado. No trecho de código abaixo, o primeiro aluno da lista será recuperado, cujo primeiro nome é Ali.
using (var context = new UniContextEntities()) {
var student = (from s in context.Students where s.FirstMidName
== "Ali" select s).FirstOrDefault<Student>();
string name = student.FirstMidName + " " + student.LastName;
Console.WriteLine("ID : {0}, Name: {1}", student.ID, name);
}
Você também pode usar Single () ou SingleOrDefault para obter um único objeto de aluno que retorna um único elemento específico de uma sequência. No exemplo a seguir, um único aluno é recuperado cujo ID é 2.
using (var context = new UniContextEntities()) {
var student = (from s in context.Students where s.ID
== 2 select s).SingleOrDefault<Student>();
string name = student.FirstMidName + " " + student.LastName;
Console.WriteLine("ID : {0}, Name: {1}", student.ID, name);
Console.ReadKey();
}
Lista de Objetos
Se você deseja recuperar a lista de alunos cujo primeiro nome é Ali, você pode usar o método enumerável ToList ().
using (var context = new UniContextEntities()) {
var studentList = (from s in context.Students where s.FirstMidName
== "Ali" select s).ToList();
foreach (var student in studentList) {
string name = student.FirstMidName + " " + student.LastName;
Console.WriteLine("ID : {0}, Name: {1}", student.ID, name);
}
Console.ReadKey();
}
Ordem
Para recuperar dados / lista em qualquer ordem particular, você pode usar a palavra-chave orderby. No código a seguir, a lista de trechos do aluno será recuperada em ordem crescente.
using (var context = new UniContextEntities()) {
var studentList = (from s in context.Students orderby
s.FirstMidName ascending select s).ToList();
foreach (var student in studentList) {
string name = student.FirstMidName + " " + student.LastName;
Console.WriteLine("ID : {0}, Name: {1}", student.ID, name);
}
Console.ReadKey();
}
Consulta de Estrutura de Entidade de Projeção Vs Padrão
Suponhamos que você tenha um modelo de aluno que contém ID, FirstMidName, LastName e EnrollmentDate. Se você quiser retornar uma lista de Alunos, uma consulta padrão retornará todos os campos. Mas se você deseja obter apenas uma lista de alunos que contenham os campos ID, FirstMidName e LastName. É aqui que você deve usar uma consulta de projeção. A seguir está o exemplo simples de consulta de projeção.
using (var context = new UniContextEntities()) {
var studentList = from s in context.Students
orderby s.FirstMidName ascending
where s.FirstMidName == "Ali"
select new {s.ID, s.FirstMidName, s.LastName};
foreach (var student in studentList) {
string name = student.FirstMidName + " " + student.LastName;
Console.WriteLine("ID : {0}, Name: {1}", student.ID, name);
}
Console.ReadKey();
}
A consulta de projeção acima exclui o campo EnrollmentDate. Isso tornará seu aplicativo muito mais rápido.
No Entity Framework 6.0, um novo recurso é introduzido, conhecido como Logging SQL. Ao trabalhar com o Entity Framework, ele envia comandos ou uma consulta SQL equivalente ao banco de dados para fazer operações CRUD (Criar, Ler, Atualizar e Excluir).
Este recurso do Entity Framework é capturar uma consulta SQL equivalente gerada internamente pelo Entity Framework e fornecê-la como saída.
Antes do Entity Framework 6, sempre que havia a necessidade de rastrear consultas e comandos de banco de dados, o desenvolvedor não tinha opção a não ser usar algum utilitário de rastreamento de terceiros ou ferramenta de rastreamento de banco de dados.
No Entity Framework 6, esse novo recurso fornece uma maneira simples de registrar todas as operações realizadas pelo Entity Framework.
Todas as atividades realizadas pelo Entity Framework são registradas usando DbContext.Database.Log.
Vamos dar uma olhada no código a seguir, no qual um novo aluno é adicionado ao banco de dados.
class Program {
static void Main(string[] args) {
using (var context = new UniContextEntities()) {
context.Database.Log = Console.Write;
// Create a new student and save it
context.Students.Add(new Student {
FirstMidName = "Salman",
LastName = "Khan",
EnrollmentDate = DateTime.Parse(DateTime.Today.ToString())
});
context.SaveChanges();
Console.ReadKey();
}
}
}
Quando o código acima for executado, você receberá a seguinte saída, que na verdade é o log de todas as atividades realizadas pelo EF no código acima.
Opened connection at 10/28/2015 6:27:35 PM +05:00
Started transaction at 10/28/2015 6:27:35 PM +05:00
INSERT [dbo].[Student]([LastName], [FirstMidName], [EnrollmentDate])
VALUES (@0, @1, @2)
SELECT [ID]
FROM [dbo].[Student]
WHERE @@ROWCOUNT > 0 AND [ID] = scope_identity()
-- @0: 'Khan' (Type = String, Size = -1)
-- @1: 'Salman' (Type = String, Size = -1)
-- @2: '10/28/2015 12:00:00 AM' (Type = DateTime)
-- Executing at 10/28/2015 6:27:35 PM +05:00
-- Completed in 5 ms with result: SqlDataReader
Committed transaction at 10/28/2015 6:27:35 PM +05:00
Closed connection at 10/28/2015 6:27:35 PM +05:00
Quando a propriedade Log é definida, as seguintes atividades são registradas -
SQL para todos os diferentes tipos de comandos, por exemplo, consultas, incluindo inserções, atualizações e exclusões geradas como parte de SaveChanges
Parameters
Se o comando está sendo executado de forma assíncrona ou não
Um carimbo de data / hora indicando quando o comando começou a ser executado
O comando foi concluído com sucesso ou falhou
Alguma indicação do valor do resultado
A quantidade aproximada de tempo necessária para executar o comando
Registrando em outro lugar
Se você já tem alguma estrutura de registro e ela define um método de registro, você também pode registrá-la em outro lugar.
Vamos dar uma olhada no exemplo a seguir, no qual temos outra classe MyLogger.
class Program {
static void Main(string[] args) {
using (var context = new UniContextEntities()) {
context.Database.Log = s ⇒ MyLogger.Log("EFLoggingDemo", s);
// Create a new student and save it
context.Students.Add(new Student {
FirstMidName = "Salman",
LastName = "Khan",
EnrollmentDate = DateTime.Parse(DateTime.Today.ToString())
});
context.SaveChanges();
Console.ReadKey();
}
}
}
public class MyLogger {
public static void Log(string application, string message) {
Console.WriteLine("Application: {0}, EF Message: {1} ",application, message);
}
}
Recomendamos que você execute o exemplo acima passo a passo para melhor compreensão.
No Entity Framework 6.0, há outro novo recurso conhecido como Interceptorou interceptação. O código de interceptação é construído em torno do conceito deinterception interfaces. Por exemplo, a interface IDbCommandInterceptor define métodos que são chamados antes de EF fazer uma chamada para ExecuteNonQuery, ExecuteScalar, ExecuteReader e métodos relacionados.
O Entity Framework pode realmente brilhar usando interceptação. Usando essa abordagem, você pode capturar muito mais informações temporariamente, sem ter que desordenar seu código.
Para implementar isso, você precisa criar seu próprio interceptor customizado e registrá-lo de acordo.
Depois que uma classe que implementa a interface IDbCommandInterceptor foi criada, ela pode ser registrada com o Entity Framework usando a classe DbInterception.
A interface IDbCommandInterceptor possui seis métodos e você precisa implementar todos esses métodos. A seguir estão a implementação básica desses métodos.
Vamos dar uma olhada no código a seguir no qual a interface IDbCommandInterceptor é implementada.
public class MyCommandInterceptor : IDbCommandInterceptor {
public static void Log(string comm, string message) {
Console.WriteLine("Intercepted: {0}, Command Text: {1} ", comm, message);
}
public void NonQueryExecuted(DbCommand command,
DbCommandInterceptionContext<int> interceptionContext) {
Log("NonQueryExecuted: ", command.CommandText);
}
public void NonQueryExecuting(DbCommand command,
DbCommandInterceptionContext<int> interceptionContext) {
Log("NonQueryExecuting: ", command.CommandText);
}
public void ReaderExecuted(DbCommand command,
DbCommandInterceptionContext<DbDataReader> interceptionContext) {
Log("ReaderExecuted: ", command.CommandText);
}
public void ReaderExecuting(DbCommand command,
DbCommandInterceptionContext<DbDataReader> interceptionContext) {
Log("ReaderExecuting: ", command.CommandText);
}
public void ScalarExecuted(DbCommand command,
DbCommandInterceptionContext<object> interceptionContext) {
Log("ScalarExecuted: ", command.CommandText);
}
public void ScalarExecuting(DbCommand command,
DbCommandInterceptionContext<object> interceptionContext) {
Log("ScalarExecuting: ", command.CommandText);
}
}
Registrando Interceptadores
Depois que uma classe que implementa uma ou mais interfaces de interceptação foi criada, ela pode ser registrada com EF usando a classe DbInterception conforme mostrado no código a seguir.
DbInterception.Add(new MyCommandInterceptor());
Os interceptores também podem ser registrados no nível do domínio do aplicativo usando a configuração baseada em código DbConfiguration, conforme mostrado no código a seguir.
public class MyDBConfiguration : DbConfiguration {
public MyDBConfiguration() {
DbInterception.Add(new MyCommandInterceptor());
}
}
Você também pode configurar o arquivo de configuração do interceptor usando o código -
<entityFramework>
<interceptors>
<interceptor type = "EFInterceptDemo.MyCommandInterceptor, EFInterceptDemo"/>
</interceptors>
</entityFramework>
O suporte de tipo espacial foi introduzido no Entity Framework 5. Um conjunto de operadores também está incluído para permitir consultas para analisar dados espaciais. Por exemplo, uma consulta pode filtrar com base na distância entre duas localizações geográficas.
O Entity Framework permitirá que novos tipos de dados espaciais sejam expostos como propriedades em suas classes e os mapeie para colunas espaciais em seu banco de dados.
Você também poderá escrever consultas LINQ que usam os operadores espaciais para filtrar, classificar e agrupar com base em cálculos espaciais realizados no banco de dados.
Existem dois tipos principais de dados espaciais -
O tipo de dados geográficos armazena dados elipsoidais, por exemplo, coordenadas de latitude e longitude GPS.
O tipo de dados de geometria representa o sistema de coordenadas euclidianas (planas).
Vamos dar uma olhada no seguinte exemplo de campo de críquete.
Step 1 - Crie um novo projeto a partir da opção de menu Arquivo → Novo → Projeto.
Step 2 - No painel esquerdo, selecione o aplicativo de console.
Step 3 - Clique com o botão direito no nome do projeto e selecione Gerenciar Pacotes NuGet ...
Step 4 - Instale o Entity Framework.
Step 5 - Adicionar referência ao assembly System.Data.Entity e também adicionar a instrução System.Data.Spatial using para tipos de dados espaciais.
Step 6 - Adicione a seguinte classe no arquivo Program.cs.
public class CricketGround {
public int ID { get; set; }
public string Name { get; set; }
public DbGeography Location { get; set; }
}
Step 7 - Além de definir entidades, você precisa definir uma classe que deriva de DbContext e expõe as propriedades DbSet <TEntity>.
Em Program.cs adicione a definição de contexto.
public partial class CricketGroundContext : DbContext {
public DbSet<CricketGround> CricketGrounds { get; set; }
}
Step 8 - Adicione o seguinte código à função principal, que adicionará dois novos objetos CricketGround ao contexto.
class Program {
static void Main(string[] args) {
using (var context = new CricketGroundContext()) {
context.CricketGrounds.Add(new CricketGround() {
Name = "Shalimar Cricket Ground",
Location = DbGeography.FromText("POINT(-122.336106 47.605049)"),
});
context.CricketGrounds.Add(new CricketGround() {
Name = "Marghazar Stadium", Location = DbGeography
.FromText("POINT(-122.335197 47.646711)"),
});
context.SaveChanges();
var myLocation = DbGeography.FromText("POINT(-122.296623 47.640405)");
var cricketGround = (from cg in context.CricketGrounds
orderby cg.Location.Distance(myLocation) select cg).FirstOrDefault();
Console.WriteLine("The closest Cricket Ground to you is: {0}.", cricketGround.Name);
}
}
}
As propriedades espaciais são inicializadas usando o método DbGeography.FromText. O ponto geográfico representado como WellKnownText é passado para o método e salva os dados. Depois disso, o objeto CricketGround será recuperado onde sua localização for mais próxima da localização especificada.
Quando o código acima for executado, você receberá a seguinte saída -
The closest Cricket Ground to you is: Marghazar Stadium
Recomendamos que você execute o exemplo acima passo a passo para melhor compreensão.
A herança torna possível criar modelos complexos que refletem melhor como os desenvolvedores pensam e também reduzem o trabalho necessário para interagir com esses modelos. A herança usada com entidades tem a mesma finalidade que a herança usada com classes, portanto, os desenvolvedores já sabem o básico de como esse recurso funciona.
Vamos dar uma olhada no exemplo a seguir e criar um novo projeto de aplicativo de console.
Step 1 - Adicione ADO.NET Entity Data Model clicando com o botão direito do mouse no nome do projeto e selecione Add → New Item…
Step 2 - Adicione uma entidade e nomeie-a como Pessoa, seguindo todas as etapas mencionadas no capítulo Abordagem Modelar Primeiro.
Step 3 - Adicione algumas propriedades escalares como mostrado na imagem a seguir.
Step 4 - Vamos adicionar mais duas entidades Student e Teacher, que herdará as propriedades de Person Table.
Step 5 - Agora adicione a entidade Aluno e selecione Pessoa na caixa de combinação do tipo Base, conforme mostrado na imagem a seguir.
Step 6 - Da mesma forma, adicione entidade Professor.
Step 7 - Agora adicione a propriedade escalar EnrollmentDate à entidade aluno e a propriedade HireDate à entidade Professor.
Step 8 - Vamos gerar o banco de dados.
Step 9 - Clique com o botão direito na superfície de design e selecione Gerar banco de dados do modelo ...
Step 10- Para criar um novo banco de dados, clique em Nova conexão ... O seguinte diálogo será aberto. Clique OK.
Step 11- Clique em Concluir. Isso adicionará o arquivo * .edmx.sql ao projeto. Você pode executar scripts DDL no Visual Studio abrindo o arquivo .sql. Agora clique com o botão direito e selecione Executar.
Step 12 - Vá para o explorador de servidores, você verá que o banco de dados é criado com três tabelas que são especificadas.
Step 13 - Você também pode ver que as seguintes classes de domínio também são geradas automaticamente.
public partial class Person {
public int ID { get; set; }
public string FirstMidName { get; set; }
public string LastName { get; set; }
}
public partial class Student : Person {
public System.DateTime EnrollmentDate { get; set; }
}
public partial class Teacher : Person {
public System.DateTime HireDate { get; set; }
}
A seguir está a classe Context.
public partial class InheritanceModelContainer : DbContext {
public InheritanceModelContainer() :
base("name = InheritanceModelContainer") {}
protected override void OnModelCreating(DbModelBuilder modelBuilder) {
throw new UnintentionalCodeFirstException();
}
public virtual DbSet<Person> People { get; set; }
}
Vamos adicionar alguns alunos e professores ao banco de dados e depois recuperá-los do banco de dados.
class Program {
static void Main(string[] args) {
using (var context = new InheritanceModelContainer()) {
var student = new Student {
FirstMidName = "Meredith",
LastName = "Alonso",
EnrollmentDate = DateTime.Parse(DateTime.Today.ToString())
};
context.People.Add(student);
var student1 = new Student {
FirstMidName = "Arturo",
LastName = "Anand",
EnrollmentDate = DateTime.Parse(DateTime.Today.ToString())
};
context.People.Add(student1);
var techaer = new Teacher {
FirstMidName = "Peggy",
LastName = "Justice",
HireDate = DateTime.Parse(DateTime.Today.ToString())
};
context.People.Add(techaer);
var techaer1 = new Teacher {
FirstMidName = "Yan",
LastName = "Li",
HireDate = DateTime.Parse(DateTime.Today.ToString())
};
context.People.Add(techaer1);
context.SaveChanges();
}
}
}
Alunos e professores são adicionados ao banco de dados. Não recupere alunos e professores, oOfType o método precisa ser usado, o que retornará o Aluno e o Professor relacionados ao departamento especificado.
Console.WriteLine("All students in database");
Console.WriteLine("");
foreach (var student in context.People.OfType<Student>()) {
string name = student.FirstMidName + " " + student.LastName;
Console.WriteLine("ID: {0}, Name: {1}, \tEnrollment Date {2} ",
student.ID, name, student.EnrollmentDate.ToString());
}
Console.WriteLine("");
Console.WriteLine("************************************************************ *****");
Console.WriteLine("");
Console.WriteLine("All teachers in database");
Console.WriteLine("");
foreach (var teacher in context.People.OfType<Teacher>()) {
string name = teacher.FirstMidName + " " + teacher.LastName;
Console.WriteLine("ID: {0}, Name: {1}, \tHireDate {2} ",
teacher.ID, name, teacher.HireDate.ToString());
}
Console.WriteLine("");
Console.WriteLine("************************************************************ *****");
Console.ReadKey();
Na primeira consulta, ao usar OfType <Student> (), você não poderá acessar HireDate porque a propriedade HireDate faz parte da entidade do professor e, da mesma forma, a propriedade EnrollmentDate não estará acessível ao usar OfType <Teacher> ()
Quando o código acima for executado, você receberá a seguinte saída -
All students in database
ID: 1, Name: Meredith Alonso, Enrollment Date 10/30/2015 12:00:00 AM
ID: 2, Name: Arturo Anand, Enrollment Date 10/30/2015 12:00:00 AM
*****************************************************************
All teachers in database
ID: 3, Name: Peggy Justice, HireDate 10/30/2015 12:00:00 AM
ID: 4, Name: Yan Li, HireDate 10/30/2015 12:00:00 AM
*****************************************************************
Recomendamos que você execute o exemplo acima passo a passo para melhor compreensão.
No Entity Framework 5 e em versões anteriores do Entity Framework, o código foi dividido entre as bibliotecas principais (principalmente System.Data.Entity.dll) enviadas como parte do .NET Framework e as bibliotecas adicionais (principalmente EntityFramework.dll) foram distribuídas e enviado usando NuGet conforme mostrado no diagrama a seguir.
No Entity Framework 6, as APIs principais que antes faziam parte do .NET framework também são enviadas e distribuídas como parte do pacote NuGet.
Isso foi necessário para permitir que o Entity Framework se tornasse o código-fonte aberto. No entanto, como consequência, os aplicativos precisarão ser reconstruídos sempre que houver necessidade de migrar ou atualizar seu aplicativo de versões mais antigas do Entity Framework para EF 6.
O processo de migração é simples se seu aplicativo usa DbContext, que foi enviado no EF 4.1 e posterior. Mas se seu aplicativo for ObjectContext, pouco mais trabalho será necessário.
Vamos dar uma olhada nas etapas a seguir que você precisa realizar para atualizar um aplicativo existente para EF6.
Step 1 - A primeira etapa é direcionar o .NET Framework 4.5.2 e posterior, clique com o botão direito do mouse em seu projeto e selecione as propriedades.
Step 2 - Clique com o botão direito em seu projeto novamente e selecione Gerenciar Pacotes NuGet ...
Step 3- Na guia Online, selecione EntityFramework e clique em Instalar. Certifique-se de que as referências do assembly para System.Data.Entity.dll sejam removidas.
Quando você instala o pacote EF6 NuGet, ele deve remover automaticamente todas as referências a System.Data.Entity do seu projeto.
Step 4 - Se você tiver qualquer modelo criado com o EF Designer, também precisará atualizar os modelos de geração de código para gerar código compatível com EF6.
Step 5 - No Gerenciador de Soluções em seu arquivo edmx, exclua os modelos de geração de código existentes que normalmente serão nomeados <edmx_file_name> .tt e <edmx_file_name> .Context.tt.
Step 6 - Abra o seu modelo no EF Designer, clique com o botão direito na superfície de design e selecione Adicionar Item de Geração de Código ...
Step 7 - Adicione o modelo de geração de código EF 6.x apropriado.
Ele também irá gerar código compatível com EF6 automaticamente.
Se seus aplicativos usam EF 4.1 ou posterior, você não precisará alterar nada no código, porque os namespaces para os tipos DbContext e Code First não mudaram.
Mas se seu aplicativo estiver usando uma versão mais antiga do Entity Framework, tipos como ObjectContext que estavam anteriormente em System.Data.Entity.dll foram movidos para novos namespaces.
Step 8 - Você precisará atualizar seu uso ou diretivas de importação para compilar com EF6.
A regra geral para alterações de namespace é que qualquer tipo em System.Data. * Seja movido para System.Data.Entity.Core. *. A seguir estão alguns deles -
- System.Data.EntityException ⇒ System.Data.Entity.Core.EntityException
- System.Data.Objects.ObjectContext ⇒ System.Data.Entity.Core.Objects.ObjectContext;
- System.Data.Objects.DataClasses.RelationshipManager ⇒ System.Data.Entity.Core.Objects.DataClasses.RelationshipManager;
Alguns tipos estão nos namespaces do Core porque não são usados diretamente para a maioria dos aplicativos baseados em DbContext.
- System.Data.EntityState ⇒ System.Data.Entity.EntityState
- System.Data.Objects.DataClasses.EdmFunctionAttribute ⇒ System.Data.Entity.DbFunctionAttribute
Seu projeto existente do Entity Framework funcionará no Entity Framework 6.0 sem grandes alterações.
O carregamento rápido é o processo pelo qual uma consulta para um tipo de entidade também carrega entidades relacionadas como parte da consulta. O carregamento rápido é alcançado pelo uso doInclude method.
Isso significa que a solicitação de dados relacionados seja retornada junto com os resultados da consulta do banco de dados. Existe apenas uma conexão com a fonte de dados, uma quantidade maior de dados é retornada na consulta inicial.
Por exemplo, ao consultar alunos, carregue antecipadamente suas matrículas. Os alunos e suas matrículas serão recuperados em uma única consulta.
Vamos dar uma olhada no exemplo a seguir, no qual todos os alunos com suas respectivas matrículas são recuperados do banco de dados usando o carregamento antecipado.
class Program {
static void Main(string[] args) {
using (var context = new UniContextEntities()) {
// Load all students and related enrollments
var students = context.Students
.Include(s ⇒ s.Enrollments).ToList();
foreach (var student in students) {
string name = student.FirstMidName + " " + student.LastName;
Console.WriteLine("ID: {0}, Name: {1}", student.ID, name);
foreach (var enrollment in student.Enrollments) {
Console.WriteLine("Enrollment ID: {0}, Course ID: {1}",
enrollment.EnrollmentID, enrollment.CourseID);
}
}
Console.ReadKey();
}
}
}
Quando o código acima for compilado e executado, você receberá a seguinte saída.
ID: 1, Name: Ali Alexander
Enrollment ID: 1, Course ID: 1050
Enrollment ID: 2, Course ID: 4022
Enrollment ID: 3, Course ID: 4041
ID: 2, Name: Meredith Alonso
Enrollment ID: 4, Course ID: 1045
Enrollment ID: 5, Course ID: 3141
Enrollment ID: 6, Course ID: 2021
ID: 3, Name: Arturo Anand
Enrollment ID: 7, Course ID: 1050
ID: 4, Name: Gytis Barzdukas
Enrollment ID: 8, Course ID: 1050
Enrollment ID: 9, Course ID: 4022
Abaixo estão algumas das outras formas de consultas de carregamento antecipado que podem ser usadas.
// Load one Student and its related enrollments
var student1 = context.Students
.Where(s ⇒ s.FirstMidName == "Ali")
.Include(s ⇒ s.Enrollments).FirstOrDefault();
// Load all Students and related enrollments
// using a string to specify the relationship
var studentList = context.Students
.Include("Enrollments").ToList();
// Load one Student and its related enrollments
// using a string to specify the relationship
var student = context.Students
.Where(s ⇒ s.FirstMidName == "Salman")
.Include("Enrollments").FirstOrDefault();
Vários níveis
Também é possível carregar rapidamente vários níveis de entidades relacionadas. As consultas a seguir mostram exemplos de Aluno, Matrículas e Curso.
// Load all Students, all related enrollments, and all related courses
var studentList = context.Students
.Include(s ⇒ s.Enrollments.Select(c ⇒ c.Course)).ToList();
// Load all Students, all related enrollments, and all related courses
// using a string to specify the relationships
var students = context.Students
.Include("Enrollments.Course").ToList();
Recomendamos que você execute o exemplo acima passo a passo para melhor compreensão.
O carregamento lento é o processo pelo qual uma entidade ou coleção de entidades é carregada automaticamente do banco de dados na primeira vez que uma propriedade referente à entidade / entidades é acessada. O carregamento lento significa atrasar o carregamento dos dados relacionados, até que você os solicite especificamente.
Ao usar os tipos de entidade POCO, o carregamento lento é obtido criando instâncias de tipos de proxy derivados e, em seguida, substituindo as propriedades virtuais para adicionar o gancho de carregamento.
O carregamento lento é basicamente o padrão.
Se você deixar a configuração padrão e não informar explicitamente ao Entity Framework em sua consulta que deseja algo diferente do carregamento lento, o carregamento lento será o que você obterá.
Por exemplo, ao usar a classe de entidade Aluno, os Enrollments relacionados serão carregados na primeira vez que a propriedade de navegação Enrollments for acessada.
A propriedade de navegação deve ser definida como pública, virtual. Contexto vaiNOT faça o carregamento lento se a propriedade não for definida como virtual.
A seguir está uma classe de Aluno que contém a propriedade de navegação de Inscrições.
public partial class Student {
public Student() {
this.Enrollments = new HashSet<Enrollment>();
}
public int ID { get; set; }
public string LastName { get; set; }
public string FirstMidName { get; set; }
public System.DateTime EnrollmentDate { get; set; }
public virtual ICollection<Enrollment> Enrollments { get; set; }
}
Vamos dar uma olhada em um exemplo simples no qual a lista de alunos é carregada do banco de dados primeiro e, em seguida, carregará as matrículas de um aluno específico sempre que você precisar.
class Program {
static void Main(string[] args) {
using (var context = new UniContextEntities()) {
//Loading students only
IList<Student> students = context.Students.ToList<Student>();
foreach (var student in students) {
string name = student.FirstMidName + " " + student.LastName;
Console.WriteLine("ID: {0}, Name: {1}", student.ID, name);
foreach (var enrollment in student.Enrollments) {
Console.WriteLine("Enrollment ID: {0}, Course ID: {1}",
enrollment.EnrollmentID, enrollment.CourseID);
}
}
Console.ReadKey();
}
}
}
Quando o código acima for compilado e executado, você receberá a seguinte saída.
ID: 1, Name: Ali Alexander
Enrollment ID: 1, Course ID: 1050
Enrollment ID: 2, Course ID: 4022
Enrollment ID: 3, Course ID: 4041
ID: 2, Name: Meredith Alonso
Enrollment ID: 4, Course ID: 1045
Enrollment ID: 5, Course ID: 3141
Enrollment ID: 6, Course ID: 2021
ID: 3, Name: Arturo Anand
Enrollment ID: 7, Course ID: 1050
ID: 4, Name: Gytis Barzdukas
Enrollment ID: 8, Course ID: 1050
Enrollment ID: 9, Course ID: 4022
ID: 5, Name: Yan Li
Enrollment ID: 10, Course ID: 4041
ID: 6, Name: Peggy Justice
Enrollment ID: 11, Course ID: 1045
ID: 7, Name: Laura Norman
Enrollment ID: 12, Course ID: 3141
Desligue o Lazy Loading
O carregamento lento e a serialização não combinam bem e, se você não tomar cuidado, pode acabar consultando o banco de dados inteiro apenas porque o carregamento lento está ativado. É uma boa prática desativar o carregamento lento antes de serializar uma entidade.
Desativando para propriedades de navegação específicas
O carregamento lento da coleção Enrollments pode ser desativado tornando a propriedade Enrollments não virtual, conforme mostrado no exemplo a seguir.
public partial class Student {
public Student() {
this.Enrollments = new HashSet<Enrollment>();
}
public int ID { get; set; }
public string LastName { get; set; }
public string FirstMidName { get; set; }
public System.DateTime EnrollmentDate { get; set; }
public ICollection<Enrollment> Enrollments { get; set; }
}
Desligar para todas as entidades
O carregamento lento pode ser desativado para todas as entidades no contexto, definindo um sinalizador na propriedade Configuração como falso, conforme mostrado no exemplo a seguir.
public partial class UniContextEntities : DbContext {
public UniContextEntities(): base("name=UniContextEntities") {
this.Configuration.LazyLoadingEnabled = false;
}
protected override void OnModelCreating(DbModelBuilder modelBuilder) {
throw new UnintentionalCodeFirstException();
}
}
Depois de desligar o carregamento lento, agora, ao executar o exemplo acima novamente, você verá que as inscrições não são carregadas e apenas os dados dos alunos são recuperados.
ID: 1, Name: Ali Alexander
ID: 2, Name: Meredith Alons
ID: 3, Name: Arturo Anand
ID: 4, Name: Gytis Barzduka
ID: 5, Name: Yan Li
ID: 6, Name: Peggy Justice
ID: 7, Name: Laura Norman
ID: 8, Name: Nino Olivetto
Recomendamos que você execute o exemplo acima passo a passo para melhor compreensão.
Quando você desabilitou o carregamento lento, ainda é possível carregar entidades relacionadas de forma lenta, mas isso deve ser feito com uma chamada explícita.
Ao contrário do carregamento lento, não há ambigüidade ou possibilidade de confusão sobre quando uma consulta é executada.
Para fazer isso, você usa o método Load na entrada da entidade relacionada.
Para um relacionamento um para muitos, chame o método Load em Collection.
E para um relacionamento um para um, chame o método Load na Referência.
Vamos dar uma olhada no exemplo a seguir, no qual o carregamento lento é desabilitado e, em seguida, o aluno cujo primeiro nome é Ali é recuperado.
As informações do aluno são então gravadas no console. Se você olhar o código, as informações de inscrições também serão gravadas, mas a entidade Enrollments ainda não foi carregada, portanto, o loop foreach não será executado.
Depois que a entidade de inscrições for carregada explicitamente, agora as informações do aluno e as informações de inscrições serão gravadas na janela do console.
class Program {
static void Main(string[] args) {
using (var context = new UniContextEntities()) {
context.Configuration.LazyLoadingEnabled = false;
var student = (from s in context.Students where s.FirstMidName ==
"Ali" select s).FirstOrDefault<Student>();
string name = student.FirstMidName + " " + student.LastName;
Console.WriteLine("ID: {0}, Name: {1}", student.ID, name);
foreach (var enrollment in student.Enrollments) {
Console.WriteLine("Enrollment ID: {0}, Course ID: {1}",
enrollment.EnrollmentID, enrollment.CourseID);
}
Console.WriteLine();
Console.WriteLine("Explicitly loaded Enrollments");
Console.WriteLine();
context.Entry(student).Collection(s ⇒ s.Enrollments).Load();
Console.WriteLine("ID: {0}, Name: {1}", student.ID, name);
foreach (var enrollment in student.Enrollments) {
Console.WriteLine("Enrollment ID: {0}, Course ID: {1}",
enrollment.EnrollmentID, enrollment.CourseID);
}
Console.ReadKey();
}
}
}
Quando o exemplo acima for executado, você receberá a seguinte saída. Primeiro, apenas as informações do aluno são exibidas e depois de carregar explicitamente a entidade de matrículas, tanto o aluno quanto suas informações de matrícula relacionadas são exibidas.
ID: 1, Name: Ali Alexander
Explicitly loaded Enrollments
ID: 1, Name: Ali Alexander
Enrollment ID: 1, Course ID: 1050
Enrollment ID: 2, Course ID: 4022
Enrollment ID: 3, Course ID: 4041
Recomendamos que você execute o exemplo acima passo a passo para melhor compreensão.
Neste capítulo, vamos aprender sobre as técnicas de validação que podem ser usadas no ADO.NET Entity Framework para validar os dados do modelo. O Entity Framework fornece uma grande variedade de recursos de validação que podem ser implementados em uma interface de usuário para validação do lado do cliente ou podem ser usados para validação do lado do servidor.
No Entity Framework, a validação de dados é parte da solução para capturar dados inválidos em um aplicativo.
O Entity Framework valida todos os dados antes de serem gravados no banco de dados por padrão, usando uma ampla variedade de métodos de validação de dados.
No entanto, o Entity Framework vem após a validação de dados da interface do usuário. Portanto, nesse caso, há uma necessidade de validação de entidade para lidar com quaisquer exceções que EF lança e mostra uma mensagem genérica.
Existem algumas técnicas de validação de dados para melhorar sua verificação de erros e como passar mensagens de erro de volta para o usuário.
DbContext tem um método Overridable chamado ValidateEntity. Quando você chama SaveChanges, o Entity Framework chama esse método para cada entidade em seu cache cujo estado não seja Inalterado. Você pode colocar a lógica de validação diretamente aqui, conforme mostrado no exemplo a seguir para a Entidade do Aluno.
public partial class UniContextEntities : DbContext {
protected override System.Data.Entity.Validation
.DbEntityValidationResult ValidateEntity(DbEntityEntry entityEntry,
System.Collections.Generic.IDictionary<object, object> items) {
if (entityEntry.Entity is Student) {
if (entityEntry.CurrentValues.GetValue<string>("FirstMidName") == "") {
var list = new List<System.Data.Entity
.Validation.DbValidationError>();
list.Add(new System.Data.Entity.Validation
.DbValidationError("FirstMidName", "FirstMidName is required"));
return new System.Data.Entity.Validation
.DbEntityValidationResult(entityEntry, list);
}
}
if (entityEntry.CurrentValues.GetValue<string>("LastName") == "") {
var list = new List<System.Data.Entity
.Validation.DbValidationError>();
list.Add(new System.Data.Entity.Validation
.DbValidationError("LastName", "LastName is required"));
return new System.Data.Entity.Validation
.DbEntityValidationResult(entityEntry, list);
}
return base.ValidateEntity(entityEntry, items);
}
}
No método ValidateEntity acima, as propriedades FirstMidName e LastName da entidade Student são verificadas se alguma dessas propriedades tiver uma string vazia, então ela retornará uma mensagem de erro.
Vamos dar uma olhada em um exemplo simples em que um novo aluno é criado, mas o FirstMidName do aluno é uma string vazia, conforme mostrado no código a seguir.
class Program {
static void Main(string[] args) {
using (var context = new UniContextEntities()) {
Console.WriteLine("Adding new Student to the database");
Console.WriteLine();
try {
context.Students.Add(new Student() {
FirstMidName = "",
LastName = "Upston"
});
context.SaveChanges();
} catch (DbEntityValidationException dbValidationEx) {
foreach (DbEntityValidationResult entityErr in
dbValidationEx.EntityValidationErrors) {
foreach (DbValidationError error in entityErr.ValidationErrors) {
Console.WriteLine("Error: {0}",error.ErrorMessage);
}
}
}
Console.ReadKey();
}
}
}
Quando o exemplo acima for compilado e executado, você receberá a seguinte mensagem de erro na janela do console.
Adding new Student to the database
Error: FirstMidName is required
Recomendamos que você execute o exemplo acima passo a passo para melhor compreensão.
O Entity Framework fornece a capacidade de rastrear as alterações feitas nas entidades e em suas relações, para que as atualizações corretas sejam feitas no banco de dados quando o método SaveChanges de contexto é chamado. Este é um recurso chave do Entity Framework.
O Controle de Mudanças rastreia as mudanças enquanto adiciona novo (s) registro (s) à coleção de entidades, modificando ou removendo entidades existentes.
Então, todas as alterações são mantidas pelo nível DbContext.
Essas alterações de trilha serão perdidas se não forem salvas antes de o objeto DbContext ser destruído.
A classe DbChangeTracker fornece todas as informações sobre as entidades atuais rastreadas pelo contexto.
Para rastrear qualquer entidade pelo contexto, ela deve ter a propriedade de chave primária.
No Entity Framework, o controle de alterações é habilitado por padrão. Você também pode desativar o controle de alterações configurando a propriedade AutoDetectChangesEnabled de DbContext como false. Se esta propriedade for definida como verdadeira, o Entity Framework mantém o estado das entidades.
using (var context = new UniContextEntities()) {
context.Configuration.AutoDetectChangesEnabled = true;
}
Vamos dar uma olhada no exemplo a seguir, no qual os alunos e suas matrículas são recuperados do banco de dados.
class Program {
static void Main(string[] args) {
using (var context = new UniContextEntities()) {
context.Configuration.AutoDetectChangesEnabled = true;
Console.WriteLine("Retrieve Student");
var student = (from s in context.Students where s.FirstMidName ==
"Ali" select s).FirstOrDefault<Student>();
string name = student.FirstMidName + " " + student.LastName;
Console.WriteLine("ID: {0}, Name: {1}", student.ID, name);
Console.WriteLine();
Console.WriteLine("Retrieve all related enrollments");
foreach (var enrollment in student.Enrollments) {
Console.WriteLine("Enrollment ID: {0}, Course ID: {1}",
enrollment.EnrollmentID, enrollment.CourseID);
}
Console.WriteLine();
Console.WriteLine("Context tracking changes of {0} entity.",
context.ChangeTracker.Entries().Count());
var entries = context.ChangeTracker.Entries();
foreach (var entry in entries) {
Console.WriteLine("Entity Name: {0}", entry.Entity.GetType().Name);
Console.WriteLine("Status: {0}", entry.State);
}
Console.ReadKey();
}
}
}
Quando o exemplo acima for compilado e executado, você receberá a seguinte saída.
Retrieve Student
ID: 1, Name: Ali Alexander
Retrieve all related enrollments
Enrollment ID: 1, Course ID: 1050
Enrollment ID: 2, Course ID: 4022
Enrollment ID: 3, Course ID: 4041
Context tracking changes of 4 entity.
Entity Name: Student
Status: Unchanged
Entity Name: Enrollment
Status: Unchanged
Entity Name: Enrollment
Status: Unchanged
Entity Name: Enrollment
Status: Unchanged
Você pode ver que todos os dados são recuperados apenas do banco de dados, por isso o status é inalterado para todas as entidades.
Vamos agora dar uma olhada em outro exemplo simples em que adicionaremos mais uma inscrição e excluiremos um aluno do banco de dados. A seguir está o código no qual a nova inscrição é adicionada e um aluno é excluído.
class Program {
static void Main(string[] args) {
using (var context = new UniContextEntities()) {
context.Configuration.AutoDetectChangesEnabled = true;
Enrollment enr = new Enrollment() {
StudentID = 1, CourseID = 3141
};
Console.WriteLine("Adding New Enrollment");
context.Enrollments.Add(enr);
Console.WriteLine("Delete Student");
var student = (from s in context.Students where s.ID ==
23 select s).SingleOrDefault<Student>();
context.Students.Remove(student);
Console.WriteLine("");
Console.WriteLine("Context tracking changes of {0} entity.",
context.ChangeTracker.Entries().Count());
var entries = context.ChangeTracker.Entries();
foreach (var entry in entries) {
Console.WriteLine("Entity Name: {0}", entry.Entity.GetType().Name);
Console.WriteLine("Status: {0}", entry.State);
}
Console.ReadKey();
}
}
}
Quando o exemplo acima for compilado e executado, você receberá a seguinte saída.
Adding New Enrollment
Delete Student
Context tracking changes of 2 entity.
Entity Name: Enrollment
Status: Added
Entity Name: Student
Status: Deleted
Agora você pode ver que o status da entidade de inscrição está definido como adicionado e o status da entidade do aluno foi excluído, porque uma nova inscrição foi adicionada e um aluno foi removido do banco de dados.
Recomendamos que você execute o exemplo acima passo a passo para melhor compreensão.
No Entity Framework, o Colored Entity trata principalmente da alteração da cor da entidade no designer, de modo que seja fácil para os desenvolvedores identificarem grupos de entidades relacionados no designer do Visual Studio. Esse recurso foi introduzido pela primeira vez no Entity Framework 5.0.
Esse recurso não tem nada a ver com aspectos de desempenho.
Quando você tem um projeto em grande escala e muitas entidades em um arquivo edmx, este recurso é muito útil para separar suas entidades em módulos diferentes.
Se você está trabalhando com o arquivo edmx e o abriu no designer, para alterar a cor, selecione uma entidade nas janelas de design. Em seguida, clique com o botão direito e selecione Propriedades.
Na janela Propriedades, selecione a propriedade Cor de preenchimento.
Especifique a cor usando um nome de cor válido, por exemplo, Verde ou RGB válido (255, 128, 128) ou você também pode selecionar no seletor de cores.
Para alterar a cor de várias entidades de uma vez, selecione várias entidades e altere a cor de preenchimento para todas elas usando a janela de propriedades.
Você também pode alterar o formato das propriedades selecionando qualquer uma das seguintes opções -
- Nome em Exibição
- Nome e tipo de exibição
Por padrão, a opção de nome de exibição é selecionada. Para alterar o formato da propriedade, clique com o botão direito na janela do designer.
Selecione Formato de propriedade escalar → Nome e tipo de exibição.
Agora você pode ver que o tipo também é exibido junto com o nome.
O Entity Framework fornece três abordagens para criar um modelo de entidade e cada uma tem seus próprios prós e contras.
- Código Primeiro
- Banco de dados primeiro
- Modelo primeiro
Neste capítulo, descreveremos brevemente a primeira abordagem do código. Alguns desenvolvedores preferem trabalhar com o Designer no Código, enquanto outros preferem apenas trabalhar com seu código. Para esses desenvolvedores, o Entity Framework tem um fluxo de trabalho de modelagem conhecido como Code First.
O fluxo de trabalho de modelagem do Code First visa um banco de dados que não existe e o Code First o criará.
Ele também pode ser usado se você tiver um banco de dados vazio e, em seguida, Code First adicionará novas tabelas a ele.
Code First permite que você defina seu modelo usando classes C # ou VB.Net.
A configuração adicional pode ser executada opcionalmente usando atributos em suas classes e propriedades ou usando uma API fluente.
Por que codificar primeiro?
O Code First é realmente composto de um conjunto de peças de um quebra-cabeça. Em primeiro lugar estão suas classes de domínio.
As classes de domínio não têm nada a ver com o Entity Framework. Eles são apenas os itens do domínio do seu negócio.
Entity Framework, então, tem um contexto que gerencia a interação entre essas classes e seu banco de dados.
O contexto não é específico para Code First. É um recurso do Entity Framework.
Code First inclui um construtor de modelo que inspeciona suas classes que o contexto está gerenciando e, em seguida, usa um conjunto de regras ou convenções para determinar como essas classes e os relacionamentos descrevem um modelo e como esse modelo deve ser mapeado para seu banco de dados.
Tudo isso acontece em tempo de execução. Você nunca verá este modelo, está apenas na memória.
O Code First também tem a capacidade de usar esse modelo para criar um banco de dados, se você quiser.
Ele também pode atualizar o banco de dados se o modelo mudar, usando um recurso chamado Code First Migrations.
Configuração de ambiente
Para começar a trabalhar com a abordagem EF Code First, você precisa das seguintes ferramentas instaladas em seu sistema.
- Visual Studio 2013 (.net framework 4.5.2) ou versão posterior.
- MS SQL Server 2012 ou posterior.
- Entity Framework via pacote NuGet.
Instale EF via pacote NuGet
Step 1 - Primeiro, crie o aplicativo de console em Arquivo → Novo → Projeto ...
Step 2 - Selecione Windows no painel esquerdo e Aplicativo de console no painel de modelo.
Step 3 - Digite EFCodeFirstDemo como o nome e selecione OK.
Step 4 - Clique com o botão direito em seu projeto no explorador de soluções e selecione Gerenciar Pacotes NuGet…
Isso abrirá o Gerenciador de pacotes NuGet e procurará EntityFramework. Isso pesquisará todos os pacotes relacionados ao Entity Framework.
Step 5- Selecione EntityFramework e clique em Instalar. Ou, no menu Ferramentas, clique em Gerenciador de Pacotes NuGet e em Console do Gerenciador de Pacotes. Na janela do Console do gerenciador de pacotes, digite o seguinte comando: Install-Package EntityFramework.
Quando a instalação for concluída, você verá a seguinte mensagem na janela de saída “Instalado com sucesso 'EntityFramework 6.1.2' para EFCodeFirstDemo”.
Após a instalação, EntityFramework.dll será incluído em seu projeto, conforme mostrado na imagem a seguir.
Agora você está pronto para começar a trabalhar na abordagem Code First.
Vamos definir um modelo muito simples usando classes. Estamos apenas definindo-os no arquivo Program.cs, mas em um aplicativo do mundo real você dividirá suas classes em arquivos separados e, potencialmente, em um projeto separado. A seguir está um modelo de dados que iremos criar usando a abordagem Code First.
Criar modelo
Adicione as três classes a seguir no arquivo Program.cs usando o código a seguir para a classe Aluno.
public class Student {
public int ID { get; set; }
public string LastName { get; set; }
public string FirstMidName { get; set; }
public DateTime EnrollmentDate { get; set; }
public virtual ICollection<Enrollment> Enrollments { get; set; }
}
A propriedade ID se tornará a coluna de chave primária da tabela do banco de dados que corresponde a esta classe.
A propriedade Enrollments é uma propriedade de navegação. As propriedades de navegação contêm outras entidades relacionadas a esta entidade.
Nesse caso, a propriedade Enrollments de uma entidade Aluno conterá todas as entidades Enrollment relacionadas a essa entidade.
As propriedades de navegação são normalmente definidas como virtuais para que possam tirar proveito de certas funcionalidades do Entity Framework, como o carregamento lento.
Se uma propriedade de navegação pode conter várias entidades (como em relacionamentos muitos para muitos ou um para muitos), seu tipo deve ser uma lista na qual as entradas podem ser adicionadas, excluídas e atualizadas, como ICollection.
A seguir está a implementação para a aula do Curso.
public class Course {
public int CourseID { get; set; }
public string Title { get; set; }
public int Credits { get; set; }
public virtual ICollection<Enrollment> Enrollments { get; set; }
}
A propriedade Enrollments é uma propriedade de navegação. Uma entidade de curso pode ser relacionada a qualquer número de entidades de inscrição.
A seguir está a implementação da classe Enrollment e enum.
public enum Grade {
A, B, C, D, F
}
public class Enrollment {
public int EnrollmentID { get; set; }
public int CourseID { get; set; }
public int StudentID { get; set; }
public Grade? Grade { get; set; }
public virtual Course Course { get; set; }
public virtual Student Student { get; set; }
}
A propriedade EnrollmentID será a chave primária.
A propriedade Grade é um enum. O ponto de interrogação após a declaração do tipo Grade indica que a propriedade Grade é anulável.
Uma nota nula é diferente de uma nota zero. Nulo significa que uma nota não é conhecida ou ainda não foi atribuída.
As propriedades StudentID e CourseID são chaves estrangeiras e as propriedades de navegação correspondentes são Student e Course.
Uma entidade de Inscrição está associada a um Aluno e a uma entidade de Curso, portanto a propriedade só pode conter uma única entidade de Aluno e de Curso.
Criar contexto de banco de dados
A classe principal que coordena a funcionalidade do Entity Framework para um determinado modelo de dados é a classe de contexto de banco de dados que permite consultar e salvar dados. Você pode criar esta classe derivando da classe DbContext e expondo um DbSet digitado
public class MyContext : DbContext {
public virtual DbSet<Course> Courses { get; set; }
public virtual DbSet<Enrollment> Enrollments { get; set; }
public virtual DbSet<Student> Students { get; set; }
}
A seguir está o código completo no arquivo Program.cs.
using System.ComponentModel.DataAnnotations.Schema;
using System.Data.Entity;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace EFCodeFirstDemo {
class Program {
static void Main(string[] args) {}
}
public enum Grade {
A, B, C, D, F
}
public class Enrollment {
public int EnrollmentID { get; set; }
public int CourseID { get; set; }
public int StudentID { get; set; }
public Grade? Grade { get; set; }
public virtual Course Course { get; set; }
public virtual Student Student { get; set; }
}
public class Student {
public int ID { get; set; }
public string LastName { get; set; }
public string FirstMidName { get; set; }
public DateTime EnrollmentDate { get; set; }
public virtual ICollection<Enrollment> Enrollments { get; set; }
}
public class Course {
public int CourseID { get; set; }
public string Title { get; set; }
public int Credits { get; set; }
public virtual ICollection<Enrollment> Enrollments { get; set; }
}
public class MyContext : DbContext {
public virtual DbSet<Course> Courses { get; set; }
public virtual DbSet<Enrollment> Enrollments { get; set; }
public virtual DbSet<Student> Students { get; set; }
}
}
O código acima é tudo de que precisamos para começar a armazenar e recuperar dados. Vamos adicionar alguns dados e depois recuperá-los. A seguir está o código no método principal.
static void Main(string[] args) {
using (var context = new MyContext()) {
// Create and save a new Students
Console.WriteLine("Adding new students");
var student = new Student {
FirstMidName = "Alain", LastName = "Bomer",
EnrollmentDate = DateTime.Parse(DateTime.Today.ToString())
};
context.Students.Add(student);
var student1 = new Student {
FirstMidName = "Mark", LastName = "Upston",
EnrollmentDate = DateTime.Parse(DateTime.Today.ToString())
};
context.Students.Add(student1);
context.SaveChanges();
// Display all Students from the database
var students = (from s in context.Students
orderby s.FirstMidName select s).ToList<Student>();
Console.WriteLine("Retrieve all Students from the database:");
foreach (var stdnt in students) {
string name = stdnt.FirstMidName + " " + stdnt.LastName;
Console.WriteLine("ID: {0}, Name: {1}", stdnt.ID, name);
}
Console.WriteLine("Press any key to exit...");
Console.ReadKey();
}
}
Quando o código acima for executado, você receberá a seguinte saída.
Adding new students
Retrieve all Students from the database:
ID: 1, Name: Alain Bomer
ID: 2, Name: Mark Upston
Press any key to exit...
Agora, a questão que vem à mente é: onde estão os dados e o banco de dados no qual adicionamos alguns dados e depois os recuperamos do banco de dados. Por convenção, DbContext criou um banco de dados para você.
Se uma instância local do SQL Express estiver disponível, a Code First criou o banco de dados nessa instância.
Se o SQL Express não estiver disponível, o Code First tentará usar o LocalDb.
O banco de dados é nomeado após o nome totalmente qualificado do contexto derivado.
Em nosso caso, a instância do SQL Express está disponível e o nome do banco de dados é EFCodeFirstDemo.MyContext, conforme mostrado na imagem a seguir.
Essas são apenas as convenções padrão e existem várias maneiras de alterar o banco de dados que o Code First usa.
Como você pode ver na imagem acima, foram criadas tabelas de Alunos, Cursos e Matrículas e cada tabela contém colunas com tipo de dados e comprimento apropriados.
Os nomes das colunas e o tipo de dados também correspondem às propriedades das respectivas classes de domínio.
Inicialização de banco de dados
No exemplo acima, vimos que o Code First cria um banco de dados automaticamente, mas se você quiser alterar o nome do banco de dados e do servidor, vamos ver como o Code First decide o nome do banco de dados e o servidor ao inicializar um banco de dados. Dê uma olhada no diagrama a seguir.
Você pode definir o construtor base da classe de contexto das seguintes maneiras.
- Sem parâmetro
- Nome do banco de dados
- Nome da string de conexão
Sem parâmetro
Se você especificar o construtor base da classe de contexto sem nenhum parâmetro, conforme mostrado no exemplo acima, a estrutura de entidade criará um banco de dados em seu servidor SQLEXPRESS local com um nome {Namespace}. {Nome da classe de contexto}.
No exemplo acima, o banco de dados que é criado automaticamente tem o nome EFCodeFirstDemo.MyContext. Se você olhar para o nome, verá que EFCodeFirstDemo é o namespace e MyContext é o nome da classe de contexto, conforme mostrado no código a seguir.
public class MyContext : DbContext {
public MyContext() : base() {}
public virtual DbSet<Course> Courses { get; set; }
public virtual DbSet<Enrollment> Enrollments { get; set; }
public virtual DbSet<Student> Students { get; set; }
}
Nome do banco de dados
Se você passar o nome do banco de dados como um parâmetro em um construtor base da classe de contexto, o Code First criará um banco de dados automaticamente novamente, mas desta vez o nome será aquele passado como parâmetro no construtor base no servidor de banco de dados SQLEXPRESS local .
No código a seguir, MyContextDB é especificado como parâmetro no construtor base. Se executar seu aplicativo, o banco de dados com o nome MyContextDB será criado em seu servidor SQL local.
public class MyContext : DbContext {
public MyContext() : base("MyContextDB") {}
public virtual DbSet<Course> Courses { get; set; }
public virtual DbSet<Enrollment> Enrollments { get; set; }
public virtual DbSet<Student> Students { get; set; }
}
Nome da string de conexão
Esta é uma maneira fácil de dizer ao DbContext para usar um servidor de banco de dados diferente do SQL Express ou LocalDb. Você pode escolher colocar uma string de conexão em seu arquivo app.config.
Se o nome da string de conexão corresponder ao nome do seu contexto (com ou sem qualificação de namespace), ele será encontrado por DbContext quando o construtor sem parâmetro for usado.
Se o nome da string de conexão for diferente do nome do seu contexto, então você pode dizer ao DbContext para usar esta conexão no modo Code First passando o nome da string de conexão para o construtor DbContext.
public class MyContext : DbContext {
public MyContext() : base("name = MyContextDB") {}
public virtual DbSet<Course> Courses { get; set; }
public virtual DbSet<Enrollment> Enrollments { get; set; }
public virtual DbSet<Student> Students { get; set; }
}
No código acima, o snippet da string de conexão da classe de contexto é especificado como um parâmetro no construtor base.
O nome da string de conexão deve começar com "name =", caso contrário, será considerado como um nome de banco de dados.
Este formulário torna explícito que você espera que a string de conexão seja encontrada em seu arquivo de configuração. Uma exceção será lançada se uma string de conexão com o nome fornecido não for encontrada.
<connectionStrings>
<add name = "MyContextDB"
connectionString = "Data Source =.;Initial Catalog = EFMyContextDB;Integrated Security = true"
providerName = "System.Data.SqlClient"/>
</connectionStrings>
O nome do banco de dados na string de conexão em app.config é EFMyContextDB. CodeFirst irá criar um novoEFMyContextDB banco de dados ou uso existente EFMyContextDB banco de dados no SQL Server local.
Classes de Domínio
Até agora, apenas deixamos o EF descobrir o modelo usando suas convenções padrão, mas haverá momentos em que nossas classes não seguirão as convenções e precisaremos ser capazes de realizar outras configurações. Mas você pode substituir essas convenções configurando suas classes de domínio para fornecer ao EF as informações de que precisa. Existem duas opções para configurar suas classes de domínio -
- Anotações de dados
- API fluente
Anotações de dados
DataAnnotations é usado para configurar suas classes que irão destacar as configurações mais comumente necessárias. DataAnnotations também são compreendidos por vários aplicativos .NET, como ASP.NET MVC, que permitem que esses aplicativos aproveitem as mesmas anotações para validações do lado do cliente.
A seguir estão as anotações de dados usadas na aula do aluno.
public class Enrollment {
[Key]
public int EnrollmentID { get; set; }
public int CourseID { get; set; }
public int StudentID { get; set; }
public Grade? Grade { get; set; }
[ForeignKey("CourseID")]
public virtual Course Course { get; set; }
[ForeignKey("ID")]
public virtual Student Student { get; set; }
}
API fluente
A maior parte da configuração do modelo pode ser feita usando anotações de dados simples. A API fluente é uma maneira avançada de especificar a configuração do modelo que cobre tudo o que as anotações de dados podem fazer, além de algumas configurações mais avançadas que não são possíveis com as anotações de dados. As anotações de dados e a API fluente podem ser usadas juntas.
Para acessar a API fluente, você substitui o método OnModelCreating em DbContext. Agora, vamos renomear o nome da coluna na tabela de alunos de FirstMidName para FirstName, conforme mostrado no código a seguir.
public class MyContext : DbContext {
protected override void OnModelCreating(DbModelBuilder modelBuilder) {
modelBuilder.Entity<Student>().Property(s ⇒ s.FirstMidName)
.HasColumnName("FirstName");
}
public virtual DbSet<Course> Courses { get; set; }
public virtual DbSet<Enrollment> Enrollments { get; set; }
public virtual DbSet<Student> Students { get; set; }
}
DataAnnotations é usado para configurar as classes que irão destacar as configurações mais comumente necessárias. DataAnnotations também são compreendidos por vários aplicativos .NET, como ASP.NET MVC que permite que esses aplicativos aproveitem as mesmas anotações para validações do lado do cliente. Os atributos DataAnnotation substituem as convenções CodeFirst padrão.
System.ComponentModel.DataAnnotations inclui os seguintes atributos que afetam a nulidade ou o tamanho da coluna.
- Key
- Timestamp
- ConcurrencyCheck
- Required
- MinLength
- MaxLength
- StringLength
System.ComponentModel.DataAnnotations.Schema o namespace inclui os seguintes atributos que afetam o esquema do banco de dados.
- Table
- Column
- Index
- ForeignKey
- NotMapped
- InverseProperty
Chave
O Entity Framework depende de cada entidade com um valor-chave que usa para rastrear entidades. Uma das convenções das quais o Code First depende é como ela implica qual propriedade é a chave em cada uma das classes do Code First.
A convenção consiste em procurar uma propriedade chamada “Id” ou uma que combine o nome da classe e “Id”, como “StudentId”.
A propriedade será mapeada para uma coluna de chave primária no banco de dados.
As aulas de Aluno, Curso e Matrícula seguem esta convenção.
Agora vamos supor que a classe Student use o nome StdntID em vez de ID. Quando o Code First não encontra uma propriedade que corresponda a esta convenção, ele lançará uma exceção devido ao requisito do Entity Framework de que você deve ter uma propriedade de chave. Você pode usar a anotação de chave para especificar qual propriedade deve ser usada como EntityKey.
Vamos dar uma olhada no seguinte código de uma classe Student que contém StdntID, mas não segue a convenção padrão do Code First. Portanto, para lidar com isso, um atributo Key é adicionado, o que o tornará uma chave primária.
public class Student {
[Key]
public int StdntID { get; set; }
public string LastName { get; set; }
public string FirstMidName { get; set; }
public DateTime EnrollmentDate { get; set; }
public virtual ICollection<Enrollment> Enrollments { get; set; }
}
Ao executar seu aplicativo e examinar seu banco de dados no SQL Server Explorer, você verá que a chave primária agora é StdntID na tabela Alunos.
O Entity Framework também oferece suporte a chaves compostas. Composite keystambém são chaves primárias que consistem em mais de uma propriedade. Por exemplo, você tem uma classe DrivingLicense cuja chave primária é uma combinação de LicenseNumber e IssuingCountry.
public class DrivingLicense {
[Key, Column(Order = 1)]
public int LicenseNumber { get; set; }
[Key, Column(Order = 2)]
public string IssuingCountry { get; set; }
public DateTime Issued { get; set; }
public DateTime Expires { get; set; }
}
Quando você tem chaves compostas, o Entity Framework exige que você defina uma ordem das propriedades da chave. Você pode fazer isso usando a anotação da coluna para especificar um pedido.
Timestamp
O Code First tratará as propriedades de carimbo de data / hora da mesma forma que as propriedades de ConcurrencyCheck, mas também garantirá que o campo do banco de dados que o código primeiro gera seja não anulável.
É mais comum usar campos rowversion ou timestamp para verificação de simultaneidade.
Em vez de usar a anotação ConcurrencyCheck, você pode usar a anotação TimeStamp mais específica, desde que o tipo da propriedade seja uma matriz de bytes.
Você só pode ter uma propriedade de carimbo de data / hora em uma determinada classe.
Vamos dar uma olhada em um exemplo simples adicionando a propriedade TimeStamp à classe Course -
public class Course {
public int CourseID { get; set; }
public string Title { get; set; }
public int Credits { get; set; }
[Timestamp]
public byte[] TStamp { get; set; }
public virtual ICollection<Enrollment> Enrollments { get; set; }
}
Como você pode ver no exemplo acima, o atributo Timestamp é aplicado à propriedade Byte [] da classe Course. Portanto, Code First criará uma coluna de carimbo de data / hora TStamp
na tabela Cursos.
ConcurrencyCheck
A anotação ConcurrencyCheck permite que você sinalize uma ou mais propriedades a serem usadas para verificação de simultaneidade no banco de dados quando um usuário edita ou exclui uma entidade. Se você está trabalhando com o EF Designer, isso se alinha com a configuração de ConcurrencyMode de uma propriedade como Fixo.
Vamos dar uma olhada em um exemplo simples de como o ConcurrencyCheck funciona adicionando-o à propriedade Title na classe Course.
public class Course {
public int CourseID { get; set; }
[ConcurrencyCheck]
public string Title { get; set; }
public int Credits { get; set; }
[Timestamp, DataType("timestamp")]
public byte[] TimeStamp { get; set; }
public virtual ICollection<Enrollment> Enrollments { get; set; }
}
Na classe Course acima, o atributo ConcurrencyCheck é aplicado à propriedade Title existente. Agora, o Code First incluirá a coluna Title no comando update para verificar a simultaneidade otimista, conforme mostrado no código a seguir.
exec sp_executesql N'UPDATE [dbo].[Courses]
SET [Title] = @0
WHERE (([CourseID] = @1) AND ([Title] = @2))
',N'@0 nvarchar(max) ,@1 int,@2 nvarchar(max) ',@0=N'Maths',@1=1,@2=N'Calculus'
go
Anotação necessária
A anotação Requerido informa ao EF que uma propriedade particular é necessária. Vamos dar uma olhada na seguinte classe Student, na qual o id obrigatório é adicionado à propriedade FirstMidName. O atributo obrigatório forçará o EF a garantir que a propriedade contenha dados.
public class Student {
[Key]
public int StdntID { get; set; }
[Required]
public string LastName { get; set; }
[Required]
public string FirstMidName { get; set; }
public DateTime EnrollmentDate { get; set; }
public virtual ICollection<Enrollment> Enrollments { get; set; }
}
Como visto no exemplo acima, o atributo Required é aplicado a FirstMidName e LastName. Portanto, Code First criará colunas NOT NULL FirstMidName e LastName na tabela Alunos, conforme mostrado na imagem a seguir.
Comprimento máximo
O atributo MaxLength permite especificar validações de propriedade adicionais. Pode ser aplicado a uma propriedade de tipo string ou array de uma classe de domínio. EF Code First definirá o tamanho de uma coluna conforme especificado no atributo MaxLength.
Vamos dar uma olhada na seguinte classe de Curso, na qual o atributo MaxLength (24) é aplicado à propriedade Title.
public class Course {
public int CourseID { get; set; }
[ConcurrencyCheck]
[MaxLength(24)]
public string Title { get; set; }
public int Credits { get; set; }
public virtual ICollection<Enrollment> Enrollments { get; set; }
}
Quando você executa o aplicativo acima, Code First criará um título de coluna nvarchar (24) na tabela CourseId, conforme mostrado na imagem a seguir.
Quando o usuário define o Título que contém mais de 24 caracteres, o EF lançará EntityValidationError.
MinLength
O atributo MinLength também permite especificar validações de propriedade adicionais, assim como você fez com MaxLength. O atributo MinLength também pode ser usado com o atributo MaxLength, conforme mostrado no código a seguir.
public class Course {
public int CourseID { get; set; }
[ConcurrencyCheck]
[MaxLength(24) , MinLength(5)]
public string Title { get; set; }
public int Credits { get; set; }
public virtual ICollection<Enrollment> Enrollments { get; set; }
}
EF lançará EntityValidationError, se você definir um valor da propriedade Title menor que o comprimento especificado no atributo MinLength ou maior que o comprimento especificado no atributo MaxLength.
StringLength
StringLength também permite que você especifique validações de propriedade adicionais, como MaxLength. A única diferença é que o atributo StringLength só pode ser aplicado a uma propriedade do tipo string das classes de Domínio.
public class Course {
public int CourseID { get; set; }
[StringLength (24)]
public string Title { get; set; }
public int Credits { get; set; }
public virtual ICollection<Enrollment> Enrollments { get; set; }
}
Entity Framework também valida o valor de uma propriedade para o atributo StringLength. Se o usuário definir o Título que contém mais de 24 caracteres, o EF lançará EntityValidationError.
Mesa
A convenção Default Code First cria um nome de tabela semelhante ao nome da classe. Se você está permitindo que o Code First crie o banco de dados e também deseja alterar o nome das tabelas que ele está criando. Então -
Você pode usar o Code First com um banco de dados existente. Mas nem sempre é o caso de os nomes das classes corresponderem aos nomes das tabelas em seu banco de dados.
O atributo de tabela substitui essa convenção padrão.
EF Code First criará uma tabela com um nome especificado no atributo Table para uma determinada classe de domínio.
Vamos dar uma olhada no exemplo a seguir, no qual a classe é chamada de Aluno e, por convenção, o Code First presume que isso será mapeado para uma tabela chamada Alunos. Se esse não for o caso, você pode especificar o nome da tabela com o atributo Table, conforme mostrado no código a seguir.
[Table("StudentsInfo")]
public class Student {
[Key]
public int StdntID { get; set; }
[Required]
public string LastName { get; set; }
[Required]
public string FirstMidName { get; set; }
public DateTime EnrollmentDate { get; set; }
public virtual ICollection<Enrollment> Enrollments { get; set; }
}
Agora você pode ver que o atributo Tabela especifica a tabela como StudentsInfo. Quando a tabela for gerada, você verá o nome da tabela StudentsInfo conforme mostrado na imagem a seguir.
Você não pode apenas especificar o nome da tabela, mas também pode especificar um esquema para a tabela usando o atributo Tabela, conforme mostrado no código a seguir.
[Table("StudentsInfo", Schema = "Admin")]
public class Student {
[Key]
public int StdntID { get; set; }
[Required]
public string LastName { get; set; }
[Required]
public string FirstMidName { get; set; }
public DateTime EnrollmentDate { get; set; }
public virtual ICollection<Enrollment> Enrollments { get; set; }
}
Você pode ver no exemplo acima, a tabela é especificada com o esquema de administração. Agora, o Code First criará a tabela StudentsInfo no esquema Admin, conforme mostrado na imagem a seguir.
Coluna
Também é o mesmo que o atributo Tabela, mas o atributo Tabela substitui o comportamento da tabela, enquanto o atributo Coluna substitui o comportamento da coluna. A convenção Default Code First cria um nome de coluna semelhante ao nome da propriedade. Se você está permitindo que o Code First crie o banco de dados e também deseja alterar o nome das colunas em suas tabelas. Então -
O atributo da coluna substitui a convenção padrão.
EF Code First criará uma coluna com um nome especificado no atributo Column para uma determinada propriedade.
Vamos dar uma olhada no exemplo a seguir, no qual a propriedade é chamada FirstMidName e, por convenção, Code First presume que isso será mapeado para uma coluna chamada FirstMidName.
Se não for esse o caso, você pode especificar o nome da coluna com o atributo Coluna, conforme mostrado no código a seguir.
public class Student {
public int ID { get; set; }
public string LastName { get; set; }
[Column("FirstName")]
public string FirstMidName { get; set; }
public DateTime EnrollmentDate { get; set; }
public virtual ICollection<Enrollment> Enrollments { get; set; }
}
Você pode ver que o atributo Column especifica a coluna como FirstName. Quando a tabela for gerada, você verá o nome da coluna FirstName, conforme mostrado na imagem a seguir.
Índice
O atributo Index foi introduzido no Entity Framework 6.1. Se você estiver usando uma versão anterior, as informações nesta seção não se aplicam.
Você pode criar um índice em uma ou mais colunas usando o IndexAttribute.
Adicionar o atributo a uma ou mais propriedades fará com que EF crie o índice correspondente no banco de dados ao criar o banco de dados.
Os índices tornam a recuperação de dados mais rápida e eficiente, na maioria dos casos. No entanto, sobrecarregar uma tabela ou exibição com índices pode afetar desagradavelmente o desempenho de outras operações, como inserções ou atualizações.
A indexação é o novo recurso no Entity Framework onde você pode melhorar o desempenho de seu aplicativo Code First reduzindo o tempo necessário para consultar dados do banco de dados.
Você pode adicionar índices ao seu banco de dados usando o atributo Index e substituir as configurações padrão Unique e Clustered para obter o índice mais adequado ao seu cenário.
Por padrão, o índice será denominado IX_ <nome da propriedade>
Vamos dar uma olhada no código a seguir, no qual o atributo Index é adicionado na classe do curso para créditos.
public class Course {
public int CourseID { get; set; }
public string Title { get; set; }
[Index]
public int Credits { get; set; }
public virtual ICollection<Enrollment> Enrollments { get; set; }
}
Você pode ver que o atributo Index é aplicado à propriedade Credits. Quando a tabela for gerada, você verá IX_Credits em Índices.
Por padrão, os índices não são únicos, mas você pode usar o IsUniqueparâmetro nomeado para especificar que um índice deve ser exclusivo. O exemplo a seguir apresenta um índice exclusivo, conforme mostrado no código a seguir.
public class Course {
public int CourseID { get; set; }
[Index(IsUnique = true)]
public string Title { get; set; }
[Index]
public int Credits { get; set; }
public virtual ICollection<Enrollment> Enrollments { get; set; }
}
ForeignKey
A convenção Code First cuidará dos relacionamentos mais comuns em seu modelo, mas há alguns casos em que ela precisa de ajuda. Por exemplo, alterar o nome da propriedade-chave na classe Aluno criou um problema com seu relacionamento com a classe Enrollment.
public class Enrollment {
public int EnrollmentID { get; set; }
public int CourseID { get; set; }
public int StudentID { get; set; }
public Grade? Grade { get; set; }
public virtual Course Course { get; set; }
public virtual Student Student { get; set; }
}
public class Student {
[Key]
public int StdntID { get; set; }
public string LastName { get; set; }
public string FirstMidName { get; set; }
public DateTime EnrollmentDate { get; set; }
public virtual ICollection<Enrollment> Enrollments { get; set; }
}
Ao gerar o banco de dados, Code First vê a propriedade StudentID na classe Enrollment e a reconhece, pela convenção de que corresponde a um nome de classe mais “ID”, como uma chave estrangeira para a classe Student. No entanto, não há nenhuma propriedade StudentID na classe Student, mas a propriedade StdntID é uma classe Student.
A solução para isso é criar uma propriedade de navegação no Enrollment e usar ForeignKey DataAnnotation para ajudar o Code First a entender como construir o relacionamento entre as duas classes, conforme mostrado no código a seguir.
public class Enrollment {
public int EnrollmentID { get; set; }
public int CourseID { get; set; }
public int StudentID { get; set; }
public Grade? Grade { get; set; }
public virtual Course Course { get; set; }
[ForeignKey("StudentID")]
public virtual Student Student { get; set; }
}
Você pode ver agora que o atributo ForeignKey é aplicado à propriedade de navegação.
NotMapped
Por convenções padrão do Code First, todas as propriedades de um tipo de dados com suporte e que incluem getters e setters são representadas no banco de dados. Mas nem sempre é o caso em seus aplicativos. O atributo NotMapped substitui essa convenção padrão. Por exemplo, você pode ter uma propriedade na classe Aluno, como NomePai, mas ela não precisa ser armazenada. Você pode aplicar o atributo NotMapped a uma propriedade FatherName da qual não deseja criar uma coluna no banco de dados, conforme mostrado no código a seguir.
public class Student {
[Key]
public int StdntID { get; set; }
public string LastName { get; set; }
public string FirstMidName { get; set; }
public DateTime EnrollmentDate { get; set; }
[NotMapped]
public int FatherName { get; set; }
public virtual ICollection<Enrollment> Enrollments { get; set; }
}
Você pode ver que o atributo NotMapped é aplicado à propriedade FatherName. Quando a tabela for gerada você verá que a coluna FatherName não será criada em um banco de dados, mas está presente na classe Aluno.
O Code First não criará uma coluna para uma propriedade, que não tem getters ou setters, conforme mostrado no exemplo a seguir das propriedades Address e Age da classe Student.
InverseProperty
InverseProperty é usado quando você tem vários relacionamentos entre classes. Na classe de Inscrição, você pode querer acompanhar quem se inscreveu em um Curso Atual e um Curso Anterior. Vamos adicionar duas propriedades de navegação para a classe Enrollment.
public class Enrollment {
public int EnrollmentID { get; set; }
public int CourseID { get; set; }
public int StudentID { get; set; }
public Grade? Grade { get; set; }
public virtual Course CurrCourse { get; set; }
public virtual Course PrevCourse { get; set; }
public virtual Student Student { get; set; }
}
Da mesma forma, você também precisará adicionar a classe Curso referenciada por essas propriedades. A classe Curso tem propriedades de navegação de volta para a classe Enrollment, que contém todas as matrículas atuais e anteriores.
public class Course {
public int CourseID { get; set; }
public string Title { get; set; }
[Index]
public int Credits { get; set; }
public virtual ICollection<Enrollment> CurrEnrollments { get; set; }
public virtual ICollection<Enrollment> PrevEnrollments { get; set; }
}
O Code First cria a coluna de chave estrangeira {Class Name} _ {Primary Key}, se a propriedade da chave estrangeira não estiver incluída em uma classe particular, conforme mostrado nas classes acima. Quando o banco de dados for gerado, você verá as seguintes chaves estrangeiras.
Como você pode ver, Code first não é capaz de combinar as propriedades nas duas classes por conta própria. A tabela de banco de dados para Enrollments deve ter uma chave estrangeira para CurrCourse e uma para PrevCourse, mas Code First criará quatro propriedades de chave estrangeira, ou seja,
- CurrCourse _CourseID
- PrevCourse _CourseID
- Course_CourseID e
- Course_CourseID1
Para corrigir esses problemas, você pode usar a anotação InverseProperty para especificar o alinhamento das propriedades.
public class Course {
public int CourseID { get; set; }
public string Title { get; set; }
[Index]
public int Credits { get; set; }
[InverseProperty("CurrCourse")]
public virtual ICollection<Enrollment> CurrEnrollments { get; set; }
[InverseProperty("PrevCourse")]
public virtual ICollection<Enrollment> PrevEnrollments { get; set; }
}
Como você pode ver, o atributo InverseProperty é aplicado na classe Curso acima, especificando a qual propriedade de referência da classe Enrollment ela pertence. Agora, o Code First irá gerar um banco de dados e criar apenas duas colunas de chave estrangeira na tabela Enrollments, conforme mostrado na imagem a seguir.
Recomendamos que você execute o exemplo acima passo a passo para melhor compreensão.
API Fluent é uma maneira avançada de especificar a configuração do modelo que cobre tudo o que as anotações de dados podem fazer, além de algumas configurações mais avançadas que não são possíveis com as anotações de dados. Anotações de dados e a API fluente podem ser usadas juntas, mas o Code First dá preferência a API Fluent> anotações de dados> convenções padrão.
A API Fluent é outra maneira de configurar suas classes de domínio.
A API Code First Fluent é mais comumente acessada substituindo o método OnModelCreating em seu DbContext derivado.
A API Fluent fornece mais funcionalidade para configuração do que DataAnnotations. A API Fluent oferece suporte aos seguintes tipos de mapeamento.
Neste capítulo, continuaremos com o exemplo simples que contém as classes Student, Course e Enrollment e uma classe de contexto com o nome MyContext conforme mostrado no código a seguir.
using System.Data.Entity;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace EFCodeFirstDemo {
class Program {
static void Main(string[] args) {}
}
public enum Grade {
A, B, C, D, F
}
public class Enrollment {
public int EnrollmentID { get; set; }
public int CourseID { get; set; }
public int StudentID { get; set; }
public Grade? Grade { get; set; }
public virtual Course Course { get; set; }
public virtual Student Student { get; set; }
}
public class Student {
public int ID { get; set; }
public string LastName { get; set; }
public string FirstMidName { get; set; }
public DateTime EnrollmentDate { get; set; }
public virtual ICollection<Enrollment> Enrollments { get; set; }
}
public class Course {
public int CourseID { get; set; }
public string Title { get; set; }
public int Credits { get; set; }
public virtual ICollection<Enrollment> Enrollments { get; set; }
}
public class MyContext : DbContext {
public virtual DbSet<Course> Courses { get; set; }
public virtual DbSet<Enrollment> Enrollments { get; set; }
public virtual DbSet<Student> Students { get; set; }
}
}
Para acessar a API Fluent, você precisa substituir o método OnModelCreating em DbContext. Vamos dar uma olhada em um exemplo simples no qual renomearemos o nome da coluna na tabela do aluno de FirstMidName para FirstName, conforme mostrado no código a seguir.
public class MyContext : DbContext {
protected override void OnModelCreating(DbModelBuilder modelBuilder) {
modelBuilder.Entity<Student>().Property(s ⇒ s.FirstMidName)
.HasColumnName("FirstName");}
public virtual DbSet<Course> Courses { get; set; }
public virtual DbSet<Enrollment> Enrollments { get; set; }
public virtual DbSet<Student> Students { get; set; }
}
DbModelBuilder é usado para mapear classes CLR para um esquema de banco de dados. É a classe principal e na qual você pode configurar todas as suas classes de domínio. Essa abordagem centrada no código para construir um Entity Data Model (EDM) é conhecida como Code First.
A API Fluent fornece vários métodos importantes para configurar entidades e suas propriedades para substituir várias convenções Code First. Abaixo estão alguns deles.
Sr. Não. | Nome e descrição do método |
---|---|
1 | ComplexType<TComplexType> Registra um tipo como um tipo complexo no modelo e retorna um objeto que pode ser usado para configurar o tipo complexo. Este método pode ser chamado várias vezes para o mesmo tipo para executar várias linhas de configuração. |
2 | Entity<TEntityType> Registra um tipo de entidade como parte do modelo e retorna um objeto que pode ser usado para configurar a entidade. Este método pode ser chamado várias vezes para a mesma entidade para executar várias linhas de configuração. |
3 | HasKey<TKey> Configura a (s) propriedade (s) de chave primária para este tipo de entidade. |
4 | HasMany<TTargetEntity> Configura muitos relacionamentos a partir deste tipo de entidade. |
5 | HasOptional<TTargetEntity> Configura um relacionamento opcional a partir deste tipo de entidade. As instâncias do tipo de entidade poderão ser salvas no banco de dados sem que esse relacionamento seja especificado. A chave estrangeira no banco de dados será anulável. |
6 | HasRequired<TTargetEntity> Configura um relacionamento necessário a partir deste tipo de entidade. As instâncias do tipo de entidade não poderão ser salvas no banco de dados, a menos que esse relacionamento seja especificado. A chave estrangeira no banco de dados não será anulável. |
7 | Ignore<TProperty> Exclui uma propriedade do modelo para que não seja mapeada para o banco de dados. (Herdado de StructuralTypeConfiguration <TStructuralType>) |
8 | Property<T> Configura uma propriedade de estrutura que é definida neste tipo. (Herdado de StructuralTypeConfiguration <TStructuralType>) |
9 | ToTable(String) Configura o nome da tabela para a qual este tipo de entidade está mapeado. |
A API Fluent permite configurar suas entidades ou suas propriedades, se você deseja alterar algo sobre como elas são mapeadas para o banco de dados ou como elas se relacionam umas com as outras. Há uma grande variedade de mapeamentos e modelagem que você pode impactar usando as configurações. A seguir estão os principais tipos de mapeamento que a API Fluent suporta -
- Mapeamento de Entidades
- Mapeamento de Propriedades
Mapeamento de Entidades
O mapeamento de entidades é apenas alguns mapeamentos simples que afetarão a compreensão do Entity Framework de como as classes são mapeadas para os bancos de dados. Discutimos tudo isso em anotações de dados e aqui veremos como conseguir as mesmas coisas usando a API Fluent.
Portanto, em vez de entrar nas classes de domínio para adicionar essas configurações, podemos fazer isso dentro do contexto.
A primeira coisa é substituir o método OnModelCreating, que fornece o modelBuilder para trabalhar.
Esquema Padrão
O esquema padrão é dbo quando o banco de dados é gerado. Você pode usar o método HasDefaultSchema em DbModelBuilder para especificar o esquema do banco de dados a ser usado para todas as tabelas, procedimentos armazenados, etc.
Vamos dar uma olhada no exemplo a seguir em que o esquema de administração é aplicado.
public class MyContext : DbContext {
public MyContext() : base("name = MyContextDB") {}
protected override void OnModelCreating(DbModelBuilder modelBuilder) {
//Configure default schema
modelBuilder.HasDefaultSchema("Admin");
}
public virtual DbSet<Course> Courses { get; set; }
public virtual DbSet<Enrollment> Enrollments { get; set; }
public virtual DbSet<Student> Students { get; set; }
}
Mapear Entidade para Tabela
Com a convenção padrão, o Code First criará as tabelas do banco de dados com o nome das propriedades DbSet na classe de contexto, como Cursos, Inscrições e Alunos. Mas se você quiser nomes de tabela diferentes, poderá substituir essa convenção e fornecer um nome de tabela diferente das propriedades DbSet, conforme mostrado no código a seguir.
protected override void OnModelCreating(DbModelBuilder modelBuilder) {
//Configure default schema
modelBuilder.HasDefaultSchema("Admin");
//Map entity to table
modelBuilder.Entity<Student>().ToTable("StudentData");
modelBuilder.Entity<Course>().ToTable("CourseDetail");
modelBuilder.Entity<Enrollment>().ToTable("EnrollmentInfo");
}
Quando o banco de dados for gerado, você verá o nome das tabelas conforme especificado no método OnModelCreating.
Divisão de entidade (mapear entidade para tabela múltipla)
A divisão de entidades permite combinar dados provenientes de várias tabelas em uma única classe e só pode ser usada com tabelas que têm uma relação um-para-um entre elas. Vamos dar uma olhada no exemplo a seguir, no qual as informações do aluno são mapeadas em duas tabelas.
protected override void OnModelCreating(DbModelBuilder modelBuilder) {
//Configure default schema
modelBuilder.HasDefaultSchema("Admin");
//Map entity to table
modelBuilder.Entity<Student>().Map(sd ⇒ {
sd.Properties(p ⇒ new { p.ID, p.FirstMidName, p.LastName });
sd.ToTable("StudentData");
})
.Map(si ⇒ {
si.Properties(p ⇒ new { p.ID, p.EnrollmentDate });
si.ToTable("StudentEnrollmentInfo");
});
modelBuilder.Entity<Course>().ToTable("CourseDetail");
modelBuilder.Entity<Enrollment>().ToTable("EnrollmentInfo");
}
No código acima, você pode ver que a entidade Student é dividida nas duas tabelas a seguir, mapeando algumas propriedades para a tabela StudentData e algumas propriedades para a tabela StudentEnrollmentInfo usando o método Map.
StudentData - Contém nome e sobrenome do aluno.
StudentEnrollmentInfo - Contém data de inscrição.
Quando o banco de dados é gerado, você vê as seguintes tabelas em seu banco de dados, conforme mostrado na imagem a seguir.
Mapeamento de Propriedades
O método Property é usado para configurar atributos para cada propriedade pertencente a uma entidade ou tipo complexo. O método Property é usado para obter um objeto de configuração para uma determinada propriedade. Você também pode mapear e configurar as propriedades de suas classes de domínio usando a API Fluent.
Configurando uma chave primária
A convenção padrão para chaves primárias é -
- Classe define uma propriedade cujo nome é “ID” ou “Id”
- Nome da classe seguido por “ID” ou “Id”
Se sua classe não segue as convenções padrão para a chave primária, conforme mostrado no seguinte código da classe Aluno -
public class Student {
public int StdntID { get; set; }
public string LastName { get; set; }
public string FirstMidName { get; set; }
public DateTime EnrollmentDate { get; set; }
public virtual ICollection<Enrollment> Enrollments { get; set; }
}
Então, para definir explicitamente uma propriedade como uma chave primária, você pode usar o método HasKey conforme mostrado no código a seguir -
protected override void OnModelCreating(DbModelBuilder modelBuilder) {
//Configure default schema
modelBuilder.HasDefaultSchema("Admin");
// Configure Primary Key
modelBuilder.Entity<Student>().HasKey<int>(s ⇒ s.StdntID);
}
Configurar coluna
No Entity Framework, por padrão, o Code First criará uma coluna para uma propriedade com o mesmo nome, ordem e tipo de dados. Mas você também pode substituir essa convenção, conforme mostrado no código a seguir.
protected override void OnModelCreating(DbModelBuilder modelBuilder) {
//Configure default schema
modelBuilder.HasDefaultSchema("Admin");
//Configure EnrollmentDate Column
modelBuilder.Entity<Student>().Property(p ⇒ p.EnrollmentDate)
.HasColumnName("EnDate")
.HasColumnType("DateTime")
.HasColumnOrder(2);
}
Configurar propriedade MaxLength
No exemplo a seguir, a propriedade Título do curso não deve ter mais de 24 caracteres. Quando o usuário especifica um valor com mais de 24 caracteres, o usuário obtém uma exceção DbEntityValidationException.
protected override void OnModelCreating(DbModelBuilder modelBuilder) {
//Configure default schema
modelBuilder.HasDefaultSchema("Admin");
modelBuilder.Entity<Course>().Property(p ⇒ p.Title).HasMaxLength(24);
}
Configurar propriedade nula ou não nula
No exemplo a seguir, a propriedade Course Title é necessária, portanto, o método IsRequired é usado para criar a coluna NotNull. Da mesma forma, Student EnrollmentDate é opcional, portanto, usaremos o método IsOptional para permitir um valor nulo nesta coluna, conforme mostrado no código a seguir.
protected override void OnModelCreating(DbModelBuilder modelBuilder) {
//Configure default schema
modelBuilder.HasDefaultSchema("Admin");
modelBuilder.Entity<Course>().Property(p ⇒ p.Title).IsRequired();
modelBuilder.Entity<Student>().Property(p ⇒ p.EnrollmentDate).IsOptional();
//modelBuilder.Entity<Student>().Property(s ⇒ s.FirstMidName)
//.HasColumnName("FirstName");
}
Configurando Relacionamentos
Um relacionamento, no contexto de bancos de dados, é uma situação que existe entre duas tabelas de banco de dados relacional, quando uma tabela possui uma chave estrangeira que faz referência à chave primária da outra tabela. Ao trabalhar com o Code First, você define seu modelo definindo suas classes CLR de domínio. Por padrão, o Entity Framework usa as convenções Code First para mapear suas classes para o esquema do banco de dados.
Se você usar as convenções de nomenclatura Code First, na maioria dos casos você pode contar com Code First para configurar relacionamentos entre suas tabelas com base nas chaves estrangeiras e propriedades de navegação.
Se eles não atenderem a essas convenções, também existem configurações que você pode usar para impactar os relacionamentos entre as classes e como esses relacionamentos são realizados no banco de dados quando você adiciona configurações no Code First.
Alguns deles estão disponíveis nas anotações de dados e você pode aplicar alguns ainda mais complicados com uma API Fluent.
Configurar relacionamento um para um
Ao definir um relacionamento um para um em seu modelo, você usa uma propriedade de navegação de referência em cada classe. No banco de dados, ambas as tabelas podem ter apenas um registro em cada lado do relacionamento. Cada valor de chave primária está relacionado a apenas um registro (ou nenhum registro) na tabela relacionada.
Um relacionamento um para um será criado se ambas as colunas relacionadas forem chaves primárias ou tiverem restrições exclusivas.
Em um relacionamento um-para-um, a chave primária atua adicionalmente como uma chave estrangeira e não há coluna de chave estrangeira separada para nenhuma das tabelas.
Esse tipo de relacionamento não é comum porque a maioria das informações relacionadas dessa forma estariam todas em uma tabela.
Vamos dar uma olhada no exemplo a seguir, onde adicionaremos outra classe em nosso modelo para criar um relacionamento um para um.
public class Student {
public int ID { get; set; }
public string LastName { get; set; }
public string FirstMidName { get; set; }
public DateTime EnrollmentDate { get; set; }
public virtual StudentLogIn StudentLogIn { get; set; }
public virtual ICollection<Enrollment> Enrollments { get; set; }
}
public class StudentLogIn {
[Key, ForeignKey("Student")]
public int ID { get; set; }
public string EmailID { get; set; }
public string Password { get; set; }
public virtual Student Student { get; set; }
}
Como você pode ver no código acima, os atributos Key e ForeignKey são usados para a propriedade ID na classe StudentLogIn, a fim de marcá-la como Chave Primária e também como Chave Estrangeira.
Para configurar um para zero ou um relacionamento entre Student e StudentLogIn usando a API Fluent, você precisa substituir o método OnModelCreating conforme mostrado no código a seguir.
protected override void OnModelCreating(DbModelBuilder modelBuilder) {
//Configure default schema
modelBuilder.HasDefaultSchema("Admin");
// Configure ID as PK for StudentLogIn
modelBuilder.Entity<StudentLogIn>()
.HasKey(s ⇒ s.ID);
// Configure ID as FK for StudentLogIn
modelBuilder.Entity<Student>()
.HasOptional(s ⇒ s.StudentLogIn) //StudentLogIn is optional
.WithRequired(t ⇒ t.Student); // Create inverse relationship
}
Na maioria dos casos, o Entity Framework pode inferir qual tipo é o dependente e qual é o principal em um relacionamento. No entanto, quando ambas as extremidades do relacionamento são necessárias ou ambos os lados são opcionais, o Entity Framework não pode identificar o dependente e o principal. Quando ambas as extremidades do relacionamento são necessárias, você pode usar HasRequired conforme mostrado no código a seguir.
protected override void OnModelCreating(DbModelBuilder modelBuilder) {
//Configure default schema
modelBuilder.HasDefaultSchema("Admin");
// Configure ID as PK for StudentLogIn
modelBuilder.Entity<StudentLogIn>()
.HasKey(s ⇒ s.ID);
// Configure ID as FK for StudentLogIn
modelBuilder.Entity<Student>()
.HasRequired(r ⇒ r.Student)
.WithOptional(s ⇒ s.StudentLogIn);
}
Quando o banco de dados for gerado, você verá que o relacionamento é criado conforme mostrado na imagem a seguir.
Configurar relacionamento um-para-muitos
A tabela de chave primária contém apenas um registro relacionado a nenhum, um ou muitos registros na tabela relacionada. Este é o tipo de relacionamento mais comumente usado.
Nesse tipo de relacionamento, uma linha na tabela A pode ter muitas linhas correspondentes na tabela B, mas uma linha na tabela B pode ter apenas uma linha correspondente na tabela A.
A chave estrangeira é definida na tabela que representa a extremidade múltipla do relacionamento.
Por exemplo, no diagrama acima, as tabelas Aluno e Matrícula têm relação um-para-muitos, cada aluno pode ter muitas matrículas, mas cada inscrição pertence a apenas um aluno.
Abaixo estão o Aluno e a Inscrição, que têm relação um-para-muitos, mas a chave estrangeira na tabela de Inscrição não segue as convenções padrão do Code First.
public class Enrollment {
public int EnrollmentID { get; set; }
public int CourseID { get; set; }
//StdntID is not following code first conventions name
public int StdntID { get; set; }
public Grade? Grade { get; set; }
public virtual Course Course { get; set; }
public virtual Student Student { get; set; }
}
public class Student {
public int ID { get; set; }
public string LastName { get; set; }
public string FirstMidName { get; set; }
public DateTime EnrollmentDate { get; set; }
public virtual StudentLogIn StudentLogIn { get; set; }
public virtual ICollection<Enrollment> Enrollments { get; set; }
}
Nesse caso, para configurar o relacionamento um para muitos usando a API Fluent, você precisa usar o método HasForeignKey conforme mostrado no código a seguir.
protected override void OnModelCreating(DbModelBuilder modelBuilder) {
//Configure default schema
modelBuilder.HasDefaultSchema("Admin");
//Configure FK for one-to-many relationship
modelBuilder.Entity<Enrollment>()
.HasRequired<Student>(s ⇒ s.Student)
.WithMany(t ⇒ t.Enrollments)
.HasForeignKey(u ⇒ u.StdntID);
}
Quando o banco de dados for gerado, você verá que o relacionamento é criado conforme mostrado na imagem a seguir.
No exemplo acima, o método HasRequired especifica que a propriedade de navegação do Aluno deve ser Nula. Portanto, você deve atribuir a entidade Aluno com Inscrição sempre que adicionar ou atualizar Inscrição. Para lidar com isso, precisamos usar o método HasOptional em vez do método HasRequired.
Configurar relacionamento muitos para muitos
Cada registro em ambas as tabelas pode se relacionar a qualquer número de registros (ou nenhum registro) na outra tabela.
Você pode criar tal relacionamento definindo uma terceira tabela, chamada de tabela de junção, cuja chave primária consiste nas chaves estrangeiras da tabela A e da tabela B.
Por exemplo, a tabela Aluno e a tabela Curso têm relacionamento muitos para muitos.
A seguir estão as classes Aluno e Curso nas quais Aluno e Curso têm relação de muitos para muitos, porque ambas as classes têm propriedades de navegação Alunos e Cursos que são coleções. Em outras palavras, uma entidade possui outra coleção de entidades.
public class Student {
public int ID { get; set; }
public string LastName { get; set; }
public string FirstMidName { get; set; }
public DateTime EnrollmentDate { get; set; }
public virtual ICollection<Course> Courses { get; set; }
public virtual ICollection<Enrollment> Enrollments { get; set; }
}
public class Course {
public int CourseID { get; set; }
public string Title { get; set; }
public int Credits { get; set; }
public virtual ICollection<Student> Students { get; set; }
public virtual ICollection<Enrollment> Enrollments { get; set; }
}
Para configurar o relacionamento muitos-para-muitos entre o Aluno e o Curso, você pode usar a API Fluent conforme mostrado no código a seguir.
protected override void OnModelCreating(DbModelBuilder modelBuilder) {
//Configure default schema
modelBuilder.HasDefaultSchema("Admin");
// Configure many-to-many relationship
modelBuilder.Entity<Student>()
.HasMany(s ⇒ s.Courses)
.WithMany(s ⇒ s.Students);
}
As convenções padrão do Code First são usadas para criar uma tabela de junção quando o banco de dados é gerado. Como resultado, a tabela StudentCourses é criada com as colunas Course_CourseID e Student_ID, conforme mostrado na imagem a seguir.
Se você deseja especificar o nome da tabela de junção e os nomes das colunas na tabela, você precisa fazer uma configuração adicional usando o método Map.
protected override void OnModelCreating(DbModelBuilder modelBuilder) {
//Configure default schema
modelBuilder.HasDefaultSchema("Admin");
// Configure many-to-many relationship
modelBuilder.Entity<Student>()
.HasMany(s ⇒ s.Courses)
.WithMany(s ⇒ s.Students)
.Map(m ⇒ {
m.ToTable("StudentCoursesTable");
m.MapLeftKey("StudentID");
m.MapRightKey("CourseID");
});
}
Você pode ver quando o banco de dados é gerado, os nomes da tabela e das colunas são criados conforme especificado no código acima.
Recomendamos que você execute o exemplo acima passo a passo para melhor compreensão.
No Entity Framework, o Seed foi introduzido no EF 4.1 e funciona com inicializadores de banco de dados. A ideia geral de umSeed Methodé inicializar os dados em um banco de dados que está sendo criado pelo Code First ou desenvolvido por Migrations. Esses dados geralmente são dados de teste, mas também podem ser dados de referência, como listas de Alunos, Cursos, etc. Quando os dados são inicializados, ele faz o seguinte -
- Verifica se o banco de dados de destino já existe ou não.
- Em caso afirmativo, o modelo Code First atual é comparado com o modelo armazenado em metadados no banco de dados.
- O banco de dados será eliminado se o modelo atual não corresponder ao modelo no banco de dados.
- O banco de dados é criado se ele foi eliminado ou não existia.
- Se o banco de dados foi criado, o método inicializador Seed é chamado.
O método Seed usa o objeto de contexto do banco de dados como um parâmetro de entrada, e o código no método usa esse objeto para adicionar novas entidades ao banco de dados. Para propagar dados em seu banco de dados, você precisa substituir o método Seed. Vamos dar uma olhada no exemplo a seguir, no qual alguns dos dados padrão são iniciados no banco de dados em uma classe interna.
private class UniDBInitializer<T> : DropCreateDatabaseAlways<MyContext> {
protected override void Seed(MyContext context) {
IList<Student> students = new List<Student>();
students.Add(new Student() {
FirstMidName = "Andrew",
LastName = "Peters",
EnrollmentDate = DateTime.Parse(DateTime.Today.ToString())
});
students.Add(new Student() {
FirstMidName = "Brice",
LastName = "Lambson",
EnrollmentDate = DateTime.Parse(DateTime.Today.ToString())
});
students.Add(new Student() {
FirstMidName = "Rowan",
LastName = "Miller",
EnrollmentDate = DateTime.Parse(DateTime.Today.ToString())
});
foreach (Student student in students)
context.Students.Add(student);
base.Seed(context);
}
}
No código acima, a tabela do aluno é inicializada. Você precisa definir esta classe de inicializador de banco de dados na classe de contexto, conforme mostrado no código a seguir.
public MyContext() : base("name=MyContextDB") {
Database.SetInitializer<MyContext>(new UniDBInitializer<MyContext>());
}
A seguir está a implementação completa da classe MyContext, que também contém a classe inicializadora DB.
public class MyContext : DbContext {
public MyContext() : base("name=MyContextDB") {
Database.SetInitializer<MyContext>(new UniDBInitializer<MyContext>());
}
public virtual DbSet<Course> Courses { get; set; }
public virtual DbSet<Enrollment> Enrollments { get; set; }
public virtual DbSet<Student> Students { get; set; }
private class UniDBInitializer<T> : DropCreateDatabaseAlways<MyContext> {
protected override void Seed(MyContext context) {
IList<Student> students = new List<Student>();
students.Add(new Student() {
FirstMidName = "Andrew",
LastName = "Peters",
EnrollmentDate = DateTime.Parse(DateTime.Today.ToString())
});
students.Add(new Student() {
FirstMidName = "Brice",
LastName = "Lambson",
EnrollmentDate = DateTime.Parse(DateTime.Today.ToString())
});
students.Add(new Student() {
FirstMidName = "Rowan",
LastName = "Miller",
EnrollmentDate = DateTime.Parse(DateTime.Today.ToString())
});
foreach (Student student in students)
context.Students.Add(student);
base.Seed(context);
}
}
}
Quando o exemplo acima é compilado e executado, você pode ver os dados em um banco de dados conforme mostrado na imagem a seguir.
Recomendamos que você execute o exemplo acima passo a passo para melhor compreensão.
O Entity Framework 4.3 inclui um novo recurso Code First Migrations que permite que você evolua incrementalmente o esquema do banco de dados conforme seu modelo muda ao longo do tempo. Para a maioria dos desenvolvedores, esta é uma grande melhoria sobre as opções do inicializador de banco de dados das versões 4.1 e 4.2 que exigiam que você atualizasse manualmente o banco de dados ou eliminasse e recriasse quando seu modelo mudasse.
Antes do Entity Framework 4.3, se você já tinha dados (além dos dados iniciais) ou procedimentos armazenados, gatilhos, etc. existentes em seu banco de dados, essas estratégias costumavam descartar o banco de dados inteiro e recriá-lo, de modo que você perderia os dados e outros bancos de dados objetos.
Com a migração, ele atualizará automaticamente o esquema do banco de dados, quando seu modelo for alterado sem perder nenhum dado existente ou outros objetos de banco de dados.
Ele usa um novo inicializador de banco de dados chamado MigrateDatabaseToLatestVersion.
Existem dois tipos de migração -
- Migração Automatizada
- Migração baseada em código
Migração Automatizada
A migração automatizada foi introduzida pela primeira vez na estrutura de entidades 4.3. Na migração automatizada, você não precisa processar a migração do banco de dados manualmente no arquivo de código. Por exemplo, para cada alteração, você também precisará alterar suas classes de domínio. Mas com a migração automatizada, você só precisa executar um comando no Console do gerenciador de pacotes para fazer isso.
Vamos dar uma olhada no seguinte processo passo a passo de migração automatizada.
Quando você usa a abordagem Code First, não tem um banco de dados para seu aplicativo.
Neste exemplo, começaremos com nossas 3 classes básicas, como Aluno, Curso e Inscrição, conforme mostrado no código a seguir.
public class Enrollment {
public int EnrollmentID { get; set; }
public int CourseID { get; set; }
public int StudentID { get; set; }
public Grade? Grade { get; set; }
public virtual Course Course { get; set; }
public virtual Student Student { get; set; }
}
public class Student {
public int ID { get; set; }
public string LastName { get; set; }
public string FirstMidName { get; set; }
public DateTime EnrollmentDate { get; set; }
public virtual ICollection<Enrollment> Enrollments { get; set; }
}
public class Course {
public int CourseID { get; set; }
public string Title { get; set; }
[Index]
public int Credits { get; set; }
public virtual ICollection<Enrollment> Enrollments { get; set; }
}
A seguir está a classe de contexto.
public class MyContext : DbContext {
public MyContext() : base("MyContextDB") {}
public virtual DbSet<Course> Courses { get; set; }
public virtual DbSet<Enrollment> Enrollments { get; set; }
public virtual DbSet<Student> Students { get; set; }
}
Antes de executar o aplicativo, você precisa habilitar a migração automatizada.
Step 1 - Abra o Console do gerenciador de pacotes em Ferramentas → Gerenciador de pacotes NuGet → Console do gerenciador de pacotes.
Step 2 - Para ativar a migração automatizada, execute o seguinte comando no Console do gerenciador de pacotes.
PM> enable-migrations -EnableAutomaticMigrations:$true
Step 3 - Depois que o comando é executado com êxito, ele cria uma classe interna de configuração lacrada na pasta Migration do seu projeto, conforme mostrado no código a seguir.
namespace EFCodeFirstDemo.Migrations {
using System;
using System.Data.Entity;
using System.Data.Entity.Migrations;
using System.Linq;
internal sealed class Configuration : DbMigrationsConfiguration<EFCodeFirstDemo.MyContext> {
public Configuration() {
AutomaticMigrationsEnabled = true;
ContextKey = "EFCodeFirstDemo.MyContext";
}
protected override void Seed(EFCodeFirstDemo.MyContext context) {
// This method will be called after migrating to the latest version.
// You can use the DbSet<T>.AddOrUpdate() helper extension method
// to avoid creating duplicate seed data. E.g.
// context.People.AddOrUpdate(
// p ⇒ p.FullName,
// new Person { FullName = "Andrew Peters" },
// new Person { FullName = "Brice Lambson" },
// new Person { FullName = "Rowan Miller" }
// );
}
}
}
Step 4 - Defina o inicializador de banco de dados na classe de contexto com a nova estratégia de inicialização de banco de dados MigrateDatabaseToLatestVersion.
public class MyContext : DbContext {
public MyContext() : base("MyContextDB") {
Database.SetInitializer(new MigrateDatabaseToLatestVersion<MyContext,
EFCodeFirstDemo.Migrations.Configuration>("MyContextDB"));
}
public virtual DbSet<Course> Courses { get; set; }
public virtual DbSet<Enrollment> Enrollments { get; set; }
public virtual DbSet<Student> Students { get; set; }
}
Step 5- Você configurou a migração automatizada. Quando você executa seu aplicativo, ele automaticamente cuidará da migração, quando você alterar o modelo.
Step 6- Como você pode ver, uma tabela de sistema __MigrationHistory também é criada em seu banco de dados com outras tabelas. Em __MigrationHistory, a migração automatizada mantém o histórico de alterações do banco de dados.
Step 7- Quando você adiciona outra classe de entidade como sua classe de domínio e executa seu aplicativo, a tabela é criada em seu banco de dados. Vamos adicionar a seguinte classe StudentLogIn.
public class StudentLogIn {
[Key, ForeignKey("Student")]
public int ID { get; set; }
public string EmailID { get; set; }
public string Password { get; set; }
public virtual Student Student { get; set; }
}
Step 8 - Não se esqueça de adicionar o DBSet para a classe mencionada acima em sua classe de contexto, conforme mostrado no código a seguir.
public virtual DbSet<StudentLogIn> StudentsLogIn { get; set; }
Step 9 - Execute seu aplicativo novamente e você verá que a tabela StudentsLogIn foi adicionada ao seu banco de dados.
As etapas acima mencionadas para migrações automatizadas funcionarão apenas para sua entidade. Por exemplo, para adicionar outra classe de entidade ou remover a classe de entidade existente, ela será migrada com êxito. Mas se você adicionar ou remover qualquer propriedade de sua classe de entidade, isso lançará uma exceção.
Step 10 - Para lidar com a migração da propriedade, você precisa definir AutomaticMigrationDataLossAllowed = true no construtor da classe de configuração.
public Configuration() {
AutomaticMigrationsEnabled = true;
AutomaticMigrationDataLossAllowed = true;
ContextKey = "EFCodeFirstDemo.MyContext";
}
Migração baseada em código
Quando você desenvolve um novo aplicativo, seu modelo de dados muda com frequência e, cada vez que o modelo muda, fica fora de sincronia com o banco de dados. Você configurou o Entity Framework para descartar e recriar automaticamente o banco de dados sempre que alterar o modelo de dados. A migração baseada em código é útil quando você deseja mais controle sobre a migração.
Ao adicionar, remover ou alterar classes de entidade ou alterar sua classe DbContext, na próxima vez que você executar o aplicativo, ele excluirá automaticamente o banco de dados existente, criará um novo que corresponda ao modelo e o propagará com dados de teste.
O recurso Migrações do Code First resolve esse problema permitindo que o Code First atualize o esquema do banco de dados em vez de descartar e recriar o banco de dados. Para implantar o aplicativo, você terá que habilitar Migrações.
Aqui está a regra básica para migrar alterações no banco de dados -
- Habilitar migrações
- Adicionar Migração
- Atualizar o banco de dados
Vamos dar uma olhada no seguinte processo passo a passo de migração de base de código.
Quando você usa a abordagem de código em primeiro lugar, não tem um banco de dados para seu aplicativo.
Neste exemplo, começaremos novamente com nossas 3 classes básicas, como Aluno, Curso e Inscrição, conforme mostrado no código a seguir.
public class Enrollment {
public int EnrollmentID { get; set; }
public int CourseID { get; set; }
public int StudentID { get; set; }
public Grade? Grade { get; set; }
public virtual Course Course { get; set; }
public virtual Student Student { get; set; }
}
public class Student {
public int ID { get; set; }
public string LastName { get; set; }
public string FirstMidName { get; set; }
public DateTime EnrollmentDate { get; set; }
public virtual ICollection<Enrollment> Enrollments { get; set; }
}
public class Course {
public int CourseID { get; set; }
public string Title { get; set; }
[Index]
public int Credits { get; set; }
public virtual ICollection<Enrollment> Enrollments { get; set; }
}
A seguir está a classe de contexto.
public class MyContext : DbContext {
public MyContext() : base("MyContextDB") {
Database.SetInitializer(new MigrateDatabaseToLatestVersion<
MyContext, EFCodeFirstDemo.Migrations.Configuration>("MyContextDB"));
}
public virtual DbSet<Course> Courses { get; set; }
public virtual DbSet<Enrollment> Enrollments { get; set; }
public virtual DbSet<Student> Students { get; set; }
}
Step 1 - Antes de executar o aplicativo, você precisa habilitar a migração.
Step 2 - Abra o Console do gerenciador de pacotes em Ferramentas → Gerenciador de pacotes NuGet → Console do gerenciador de pacotes.
Step 3 - A migração já está habilitada, agora adicione a migração em seu aplicativo executando o seguinte comando.
PM> add-migration "UniDB Schema"
Step 4 - Quando o comando for executado com sucesso, você verá que um novo arquivo foi criado na pasta Migration com o nome do parâmetro que você passou para o comando com um prefixo de carimbo de data / hora, conforme mostrado na imagem a seguir.
Step 5 - Você pode criar ou atualizar o banco de dados usando o comando “update-database”.
PM> Update-Database -Verbose
O sinalizador "-Verbose" especifica a exibição das Instruções SQL aplicadas ao banco de dados de destino no console.
Step 6 - Vamos adicionar mais uma propriedade 'Age' na classe do aluno e então executar a instrução de atualização.
public class Student {
public int ID { get; set; }
public string LastName { get; set; }
public string FirstMidName { get; set; }
public int Age { get; set; }
public DateTime EnrollmentDate { get; set; }
public virtual ICollection<Enrollment> Enrollments { get; set; }
}
Ao executar PM → Update-Database –Verbose, quando o comando for executado com sucesso, você verá que a nova coluna Age foi adicionada ao seu banco de dados.
Recomendamos que você execute o exemplo acima passo a passo para melhor compreensão.
Neste capítulo, aprenderemos como migrar alterações para o banco de dados quando houver várias classes DbContext no aplicativo.
- Multiple DbContext foi introduzido pela primeira vez no Entity Framework 6.0.
- Várias classes de contexto podem pertencer a um único banco de dados ou a dois bancos de dados diferentes.
Em nosso exemplo, definiremos duas classes de Contexto para o mesmo banco de dados. No código a seguir, existem duas classes DbContext para Aluno e Professor.
public class Student {
public int ID { get; set; }
public string LastName { get; set; }
public string FirstMidName { get; set; }
public DateTime EnrollmentDate { get; set; }
}
public class MyStudentContext : DbContext {
public MyStudentContext() : base("UniContextDB") {}
public virtual DbSet<Student> Students { get; set; }
}
public class Teacher {
public int ID { get; set; }
public string LastName { get; set; }
public string FirstMidName { get; set; }
public DateTime HireDate { get; set; }
}
public class MyTeacherContext : DbContext {
public MyTeacherContext() : base("UniContextDB") {}
public virtual DbSet<Teacher> Teachers { get; set; }
}
Como você pode ver no código acima, existem dois modelos chamados “Aluno” e “Professor”. Cada um está associado a uma classe de contexto correspondente específica, ou seja, o Aluno está associado ao MyStudentContext e o Professor está associado ao MyTeacherContext.
Esta é a regra básica para migrar alterações no banco de dados, quando há várias classes de Contexto dentro do mesmo projeto.
enable-migrations -ContextTypeName <DbContext-Name-with-Namespaces> MigrationsDirectory: <Migrations-Directory-Name>
Add-Migration -configuration <DbContext-Migrations-Configuration-Class-withNamespaces> <Migrations-Name>
Update-Database -configuration <DbContext-Migrations-Configuration-Class-withNamespaces> -Verbose
Vamos habilitar a migração para MyStudentContext executando o seguinte comando no Console do gerenciador de pacotes.
PM→ enable-migrations -ContextTypeName:EFCodeFirstDemo.MyStudentContext
Uma vez executado, adicionaremos o modelo no histórico de migração e para isso, temos que disparar o comando add-migration no mesmo console.
PM→ add-migration -configuration EFCodeFirstDemo.Migrations.Configuration Initial
Vamos agora adicionar alguns dados nas tabelas de alunos e professores no banco de dados.
static void Main(string[] args) {
using (var context = new MyStudentContext()) {
//// Create and save a new Students
Console.WriteLine("Adding new students");
var student = new Student {
FirstMidName = "Alain",
LastName = "Bomer",
EnrollmentDate = DateTime.Parse(DateTime.Today.ToString())
//Age = 24
};
context.Students.Add(student);
var student1 = new Student {
FirstMidName = "Mark",
LastName = "Upston",
EnrollmentDate = DateTime.Parse(DateTime.Today.ToString())
//Age = 30
};
context.Students.Add(student1);
context.SaveChanges();
// Display all Students from the database
var students = (from s in context.Students orderby s.FirstMidName
select s).ToList<Student>();
Console.WriteLine("Retrieve all Students from the database:");
foreach (var stdnt in students) {
string name = stdnt.FirstMidName + " " + stdnt.LastName;
Console.WriteLine("ID: {0}, Name: {1}", stdnt.ID, name);
}
Console.WriteLine("Press any key to exit...");
Console.ReadKey();
}
using (var context = new MyTeacherContext()) {
//// Create and save a new Teachers
Console.WriteLine("Adding new teachers");
var student = new Teacher {
FirstMidName = "Alain",
LastName = "Bomer",
HireDate = DateTime.Parse(DateTime.Today.ToString())
//Age = 24
};
context.Teachers.Add(student);
var student1 = new Teacher {
FirstMidName = "Mark",
LastName = "Upston",
HireDate = DateTime.Parse(DateTime.Today.ToString())
//Age = 30
};
context.Teachers.Add(student1);
context.SaveChanges();
// Display all Teachers from the database
var teachers = (from t in context.Teachers orderby t.FirstMidName
select t).ToList<Teacher>();
Console.WriteLine("Retrieve all teachers from the database:");
foreach (var teacher in teachers) {
string name = teacher.FirstMidName + " " + teacher.LastName;
Console.WriteLine("ID: {0}, Name: {1}", teacher.ID, name);
}
Console.WriteLine("Press any key to exit...");
Console.ReadKey();
}
}
Quando o código acima for executado, você verá que duas tabelas diferentes são criadas para dois modelos diferentes, conforme mostrado na imagem a seguir.
Recomendamos que você execute o exemplo acima passo a passo para melhor compreensão.
Antes do Entity Framework 6, o Entity Framework não reconhecia entidades ou tipos complexos que estavam aninhados em outras entidades ou tipos complexos. Quando o Entity Framework gerou o modelo, os tipos aninhados simplesmente desapareceram.
Vamos dar uma olhada em um exemplo simples no qual temos nosso modelo básico com três entidades Aluno, Curso e Inscrição.
Vamos adicionar uma propriedade Identity, que é do tipo Person. Person é outra entidade, contém as propriedades BirthDate e FatherName.
Em termos de Entity Framework, por não ter identidade e ser parte de uma entidade, é um tipo complexo do Entity Framework e, na verdade, temos suporte para tipos complexos desde a primeira versão do Entity Framework.
O tipo Person não está aninhado conforme mostrado no código a seguir.
public class Student {
public int ID { get; set; }
public string LastName { get; set; }
public string FirstMidName { get; set; }
public DateTime EnrollmentDate { get; set; }
public Person Identity { get; set; }
public virtual ICollection<Enrollment> Enrollments { get; set; }
}
public class Person {
public Person(string fatherName, DateTime birthDate) {
FatherName = fatherName;
BirthDate = birthDate;
}
public string FatherName { get; set; }
public DateTime BirthDate { get; set; }
}
O Entity Framework saberá como persistir os tipos Person quando for usado em versões anteriores.
Usando o Entity Framework Power Tool, veremos como o Entity Framework interpreta o modelo. Clique com o botão direito do mouse no arquivo Program.cs e selecione Entity Framework → View Entity Data Model (somente leitura)
Agora você verá que a propriedade Identity está definida na classe do aluno.
Se esta classe Person não for usada por nenhuma outra entidade, podemos aninhá-la dentro da classe Student, mas esta versão anterior do Entity Framework não reconhece tipos aninhados.
Na versão mais antiga, você gera o modelo novamente, não apenas o tipo não é reconhecido, mas como não está lá, a propriedade também não está, então o Entity Framework não persistirá o tipo Person.
public class Student {
public int ID { get; set; }
public string LastName { get; set; }
public string FirstMidName { get; set; }
public DateTime EnrollmentDate { get; set; }
public Person Identity { get; set; }
public virtual ICollection<Enrollment> Enrollments { get; set; }
public class Person {
public Person(string fatherName, DateTime birthDate) {
FatherName = fatherName;
BirthDate = birthDate;
}
public string FatherName { get; set; }
public DateTime BirthDate { get; set; }
}
}
Com o Entity Framework 6, entidades aninhadas e tipos complexos são reconhecidos. No código acima, você pode ver que Person está aninhado na classe Student.
Ao usar a ferramenta poderosa do Entity Framework para mostrar como o Entity Framework interpreta o modelo desta vez, há a verdadeira propriedade Identity e o tipo complexo Person. Portanto, o Entity Framework manterá esses dados.
Agora você pode ver que Identity é um tipo de entidade aninhada, que não era compatível antes do Entity Framework 6.
Recomendamos que você execute o exemplo acima passo a passo para melhor compreensão.