Entity Framework - primeiro exemplo

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 criaremos 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.

  • Neste caso, a propriedade Enrollments de uma entidade Aluno conterá todas as entidades Enrollment que estão 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 para a 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 do banco de dados que permite consultar e salvar dados. Você pode criar esta classe derivando da classe DbContext e expondo um DbSet digitado para cada classe em nosso modelo. A seguir está a implementação na classe MyContext, que é derivada da classe DbContext.

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 pergunta 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, entã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 optar por colocar uma string de conexão no 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, você pode dizer ao DbContext para usar essa 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 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, 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 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 Fluent

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 Fluent

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 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; }
}