Entity Framework 6 Code First

Resolvi escrever esse post sobre code first com o Entity Framework pois faz um tempo que trabalho com o EF, porém nunca dei a atenção devida a ele. Em meus projetos pessoais ou estudos, sempre dei prioridade ao NHibernate, porém, como praticamente em todos os projetos que tenho trabalhado utilizam o EF, resolvi falar um pouco sobre ele.

O Entity Framework Code First é uma maneira de você se preocupar primeiramente com o seu domínio, modelando as suas classes, etc. e depois delegar ao EF que monte o banco de dados para você. Para isso você poderá seguir por dois caminhos. O primeiro é o Data Annotation, onde dentro da sua classe, você irá anotar a sua classe e suas propriedades através de Annotations indicando nomes de tabela, nomes das colunas, índices, FK, PK, etc. Eu particularmente não gosto muito de trabalhar desta maneira pois eu acho que ele deixa a classe menos legível. A partir dai vamos para a segunda opção, que é utilizarmos o fluent api para realizar nossos mapeamentos (quando necessário). Vamos ver um exemplo da diferença entre utilizar dataannotation e fluent api.

- DataAnnotations

    [Table("Map")]
    public class Map
    {
        [Key]
        [DatabaseGenerated(DatabaseGeneratedOption.Identity)]
        public Guid Id { get; set; }

        [Required]
        [MaxLength(50)]
        [Column("Name", TypeName="varchar")]
        public string Name { get; set; }

        [Column("Created", TypeName = "datetime2")]
        public DateTime Created { get; set; }
    }

- Fluent API

    public class Map
    {
        public Guid Id { get; set; }
        public string Name { get; set; }
        public DateTime Created { get; set; }
    }

    public class MapConfiguration : EntityTypeConfiguration<Map>
    {
        public MapConfiguration()
        {
            ToTable("Map");

            HasKey(m => m.Id);

            Property(m => m.Id)
                .HasDatabaseGeneratedOption(DatabaseGeneratedOption.Identity);

            Property(m => m.Name)
                .HasColumnName("Name")
                .HasColumnType("varchar")
                .HasMaxLength(50)
                .IsRequired();

            Property(m => m.Created)
                .HasColumnName("Created")
                .HasColumnType("datetime2")
                .IsRequired();
        }
    }

Note que o EF também possui algumas convenções, onde se seguirmos um jeito pré determinado, não precisaremos configurar nada. Se deixarmos a nossa classe da seguinte maneira:

    public class Map
    {
        public Guid Id { get; set; }
        public string Name { get; set; }
        public DateTime Created { get; set; }
    }

E criarmos o nosso banco, será criada a tabela de nossa classe seguindo algumas convenções.

tabela gerada sem nenhuma configuração
tabela gerada sem nenhuma configuração

Note que ao nomearmos como Id uma propriedade, ele a identifica como sendo uma chave primária. Ao não definirmos o nome da coluna, ele assumirá que o nome da coluna é o mesmo nome da sua variável. Ele também dará os tipos padrões para os campos, como para uma string, ele será do tipo nvarchar(max), para DateTime, será do tipo datetime, etc. O nome da tabela, será o nome da classe pluralizada (cuidado ao deixar assim com suas classes escritas em português). Claro que você pode mudar essas configurações. Essa é exatamente a ideia por trás do CoC, seguir convenções e configurar apenas as singularidades. Digamos que você ache realmente ruim qualquer string em seu modelo ser representada como um nvarchar(max) em sua base. Basta você dizer para o EF que você quer que suas strings sejam representadas como varchar(150) e que DateTime seja representado como datetime2 no lugar. Basta sobrescrever o método OnModelCreating de seu contexto.

    protected override void OnModelCreating(DbModelBuilder modelBuilder)
    {
        modelBuilder.Properties<string>()
            .Configure(p => p.HasColumnType("varchar"));

        modelBuilder.Properties<string>()
            .Configure(p => p.HasMaxLength(150));

        modelBuilder.Properties<DateTime>()
            .Configure(c => c.HasColumnType("datetime2"));
    }

Vale lembrar que a Microsoft disponibiliza para todos o código fonte tanto do EntityFramework como do asp.net mvc, etc. Você pode visualizá-los (e contribuir) acessando sua página no github.

Exemplo Prático

Vamos criar um projeto mostrando como mapear suas classes utilizando convenções, herança, relacionamentos, etc. Vamos criar um modelo onde existe uma pessoa, com suas informações básicas, endereço e duas classes que herdam de pessoa. Neste caso as chamei de Professor e Estudante. Note que não há muito sentido nas classes professor e aluno, porém as coloquei para mostrar como mapear suas classes quando há herança envolvida. Vamos ao nosso domínio então:

    public class Person
    {
        public Guid Id { get; set; }
        public string Name { get; set; }
        public string Surname { get; set; }
        public string Nick { get; set; }
        public string Phone { get; set; }
        public string Cellphone { get; set; }
        public string Gender { get; set; }
        public string MaritalStatus { get; set; }
        public DateTime? Birth { get; set; }
        public DateTime Created { get; set; }
        public string NationalityId { get; set; }
        public Guid? AdressId { get; set; }
        public virtual Country Nationality { get; set; }
        public virtual Adress Adress { get; set; }
    }

    public class Student : Person
    {
        public string Registration { get; set; }
    }

    public class Teacher : Person
    {
        public DateTime HireDate { get; set; }
    }

    public class Adress
    {
        public Guid Id { get; set; }
        public string Street { get; set; }
        public string Complement { get; set; }
        public string Number { get; set; }
        public string District { get; set; }
        public string ZipCode { get; set; }
        public Guid? CityId { get; set; }
        public virtual City City { get; set; }
    }

    public class City
    {
        public Guid Id { get; set; }
        public string Name { get; set; }
        public string Latitude { get; set; }
        public string Longitude { get; set; }
        public string County { get; set; }
        public Guid StateId { get; set; }
        public virtual State State { get; set; }
    }

    public class State
    {
        public Guid Id { get; set; }
        public string UF { get; set; }
        public string Name { get; set; }
        public string CountryIso { get; set; }
        public virtual Country Country { get; set; }
    }

    public class Country
    {
        public string Iso { get; set; }
        public string Iso3 { get; set; }
        public string Name { get; set; }
    }

Agora, vamos mapear o nosso domínio. Precisaremos do EF para isso, logo podemos utilizar o Nuget para isso. Vá em Package Manager Console e digite:

Install-Package EntityFramework

Note que ao escrever este post, a última versão disponível no nuget é a 6.1.3

    public class AdressConfiguration : EntityTypeConfiguration<Adress>
    {
        public AdressConfiguration()
        {
            ToTable("Adresses");

            HasKey(a => a.Id);

            Property(a => a.Id)
                .HasDatabaseGeneratedOption(DatabaseGeneratedOption.Identity);

            Property(a => a.Street)
                .IsRequired();
            
            Property(a => a.Number)
                .HasMaxLength(15);

            Property(a => a.ZipCode)
                .HasMaxLength(15);

            HasOptional(a => a.City)
                .WithMany()
                .HasForeignKey(a => a.CityId)
                .WillCascadeOnDelete(false);
        }
    }

    public class CityConfiguration : EntityTypeConfiguration<City>
    {
        public CityConfiguration()
        {
            ToTable("Cities");

            HasKey(c => c.Id);

            Property(c => c.Id)
                .HasDatabaseGeneratedOption(DatabaseGeneratedOption.Identity);

            Property(c => c.Name)
                .HasMaxLength(200)
                .IsRequired();

            Property(c => c.County)
                .HasMaxLength(100);

            HasRequired(c => c.State)
                .WithMany()
                .HasForeignKey(c => c.StateId)
                .WillCascadeOnDelete(false);
        }
    }

    public class CountryConfiguration : EntityTypeConfiguration<Country>
    {
        public CountryConfiguration()
        {
            ToTable("Countries");

            HasKey(c => c.Iso);

            Property(c => c.Iso)
                .HasColumnType("char")
                .HasMaxLength(2);

            Property(c => c.Iso3)
                .HasColumnType("char")
                .HasMaxLength(3);

            Property(c => c.Name)
                .HasMaxLength(300)
                .IsRequired();
        }
    }

    public class PersonConfiguration : EntityTypeConfiguration<Person>
    {
        public PersonConfiguration()
        {
            ToTable("People");

            HasKey(p => p.Id);

            Property(p => p.Id)
                .HasDatabaseGeneratedOption(DatabaseGeneratedOption.Identity);

            Property(p => p.Name)
                .IsRequired();

            Property(p => p.Email)
                .HasColumnAnnotation(IndexAnnotation.AnnotationName,
                    new IndexAnnotation(
                        new IndexAttribute("UniqueEmail")
                        { IsUnique = true }));

            Property(p => p.NationalityId)
                .HasColumnType("char")
                .HasMaxLength(2);

            HasOptional(p => p.Nationality)
                .WithMany()
                .HasForeignKey(p => p.NationalityId)
                .WillCascadeOnDelete(false);

            HasOptional(p => p.Adress)
                .WithMany()
                .HasForeignKey(p => p.AdressId)
                .WillCascadeOnDelete(false);
        }
    }

    public class StateConfiguration : EntityTypeConfiguration<State>
    {
        public StateConfiguration()
        {
            ToTable("States");

            HasKey(s => s.Id);

            Property(s => s.Id)
                .HasDatabaseGeneratedOption(DatabaseGeneratedOption.Identity);

            Property(s => s.UF)
                .HasColumnType("char")
                .HasMaxLength(2)
                .IsRequired();

            Property(s => s.Name)
                .HasMaxLength(200)
                .IsRequired();

            Property(s => s.CountryIso)
                .HasColumnType("char")
                .HasMaxLength(2)
                .IsRequired();

            HasRequired(s => s.Country)
                .WithMany()
                .HasForeignKey(s => s.CountryIso)
                .WillCascadeOnDelete(false);
        }
    }

    public class StudentConfiguration : EntityTypeConfiguration<Student>
    {
        public StudentConfiguration()
        {
            ToTable("Students");

            Property(s => s.Registration)
                .HasColumnAnnotation(IndexAnnotation.AnnotationName,
                    new IndexAnnotation(
                        new IndexAttribute("UniqueRegistration")
                        { IsUnique = true }));
        }
    }

    public class TeacherConfiguration : EntityTypeConfiguration<Teacher>
    {
        public TeacherConfiguration()
        {
            ToTable("Teachers");

        }
    }

Agora nos falta apenas criar o nosso Contexto:

    public class CodeFirstExampleContext : DbContext
    {
        public DbSet<Adress> Adresses { get; set; }
        public DbSet<City> Cities { get; set; }
        public DbSet<Country> Countries { get; set; }
        public DbSet<Person> People { get; set; }
        public DbSet<State> States { get; set; }
        public DbSet<Student> Students { get; set; }
        public DbSet<Teacher> Teachers { get; set; }

        protected override void OnModelCreating(DbModelBuilder modelBuilder)
        {
            modelBuilder.Properties<string>()
                .Configure(p => p.HasColumnType("varchar"));

            modelBuilder.Properties<string>()
                .Configure(p => p.HasMaxLength(150));

            modelBuilder.Properties<DateTime>()
                .Configure(c => c.HasColumnType("datetime2"));

            modelBuilder.Configurations.Add(new AdressConfiguration());
            modelBuilder.Configurations.Add(new CityConfiguration());
            modelBuilder.Configurations.Add(new CountryConfiguration());
            modelBuilder.Configurations.Add(new PersonConfiguration());
            modelBuilder.Configurations.Add(new StateConfiguration());
            modelBuilder.Configurations.Add(new StudentConfiguration());
            modelBuilder.Configurations.Add(new TeacherConfiguration());
        }
    }

Agora podemos criar nosso banco e brincar um pouco com ele. Para isso vou criar um Console Application.

    static void Main(string[] args)
    {    
         var context = new CodeFirstExampleContext();
         context.Database.CreateIfNotExists();
    }

E podemos brincar um pouquinho também:

    static void Main(string[] args)
    {
        var context = new CodeFirstExampleContext();
        context.Database.Delete();
        context.Database.CreateIfNotExists();

        var brazil = new Country
        {
            Iso = "BR",
            Name = "Brasil"
        };

        var saoPaulo = new State
        {
            Country = brazil,
            CountryIso = brazil.Iso,
            UF = "SP",
            Name = "São Paulo"
        };

        var saoCaetano = new City
        {
            State = saoPaulo,
            StateId = saoPaulo.Id,
            Latitude = "",
            Longitude = "",
            Name = "São Caetano"
        };

        var person = new Person
        {
            Email = "novapessoa@email.com",
            Name = "Novo"
        };

        var teacher = new Teacher
        {
            Name = "Professor",
            Email = "professor@escola.com.br",
            HireDate = DateTime.Now,
            Adress = new Adress
            {
                City = saoCaetano,
                Complement = "Apto 222",
                District = "Perdizes",
                Street = "Rua abc",
                Number = "222",
                ZipCode = "05555552"
            }
        };

        var student = new Student
        {
            Name = "Student",
            Registration = "1234567"
        };

        context.Countries.Add(brazil);
        context.States.Add(saoPaulo);
        context.Cities.Add(saoCaetano);
        context.People.Add(person);
        context.Teachers.Add(teacher);
        context.Students.Add(student);

        context.SaveChanges();
            
        foreach (var p in context.People)
        {
            Console.WriteLine("Name: " + p.Name);
            Console.WriteLine("Possui Endereço: " + (p.AdressId.HasValue != false));
            Console.WriteLine();
        }

        foreach (var p in context.Students)
        {
            Console.WriteLine("Name: " + p.Name);
            Console.WriteLine("Matrícula: " + p.Registration);
            Console.WriteLine("Possui Endereço: " + (p.AdressId.HasValue != false));
            Console.WriteLine();
        }

        foreach (var p in context.Teachers)
        {
            Console.WriteLine("Name: " + p.Name);
            Console.WriteLine("Admitido em: " + p.HireDate.ToString("dd/MM/yyyy"));
            Console.WriteLine("Possui Endereço: " + (p.AdressId.HasValue != false));
            Console.WriteLine();
        }

        Console.ReadKey();
    }

Pronto! Se você rodar o console app você terá o seu banco criado em sua instância local do sql server.
Caso queira baixar o projeto, ele está hospedado na minha página do github: https://github.com/ceb10n/CodeFirstExample

see yah 😉

Anúncios

2 comentários sobre “Entity Framework 6 Code First

Deixe um comentário

Preencha os seus dados abaixo ou clique em um ícone para log in:

Logotipo do WordPress.com

Você está comentando utilizando sua conta WordPress.com. Sair / Alterar )

Imagem do Twitter

Você está comentando utilizando sua conta Twitter. Sair / Alterar )

Foto do Facebook

Você está comentando utilizando sua conta Facebook. Sair / Alterar )

Foto do Google+

Você está comentando utilizando sua conta Google+. Sair / Alterar )

Conectando a %s