Árvore de páginas

Você está vendo a versão antiga da página. Ver a versão atual.

Comparar com o atual Ver Histórico da Página

« Anterior Versão 12 Próxima »

Criando o projeto

Para começarmos a trabalhar com Entity Framework Core temos que criar um projeto com suporte a .NET Core.

Para isso através do Visual Studio vamos criar nosso projeto acessando: File -> New -> Project -> .NET Standard -> Class Library.

Com o projeto criado vamos adicionar o seguinte pacote Tnf.App.EntityFrameworkCore disponível em https://www.myget.org/F/tnf/api/v3/index.json

Podemos agora começar a criar nosso contexto e entidades de nosso banco de dados.

Para isso vamos criar o módulo que fará a configuração do uso do Entity Framework Core.

EntityFrameworkCoreModule.cs
using Tnf.Reflection.Extensions;
using Tnf.App.EntityFrameworkCore;
using Tnf.Architecture.Domain;
using Tnf.Modules;
 
[DependsOn(
	typeof(DomainModule),
    typeof(TnfAppEntityFrameworkCoreModule))]
public class EntityFrameworkModule : TnfModule
{
	public override void Initialize()
	{
		IocManager.RegisterAssemblyByConvention(typeof(EntityFrameworkModule).GetAssembly());
	}
}

Podemos perceber em nosso módulo definido acima no atributo "DependsOn" fazendo referencia ao TnfAppEntityFrameworkCoreModule. Essa dependência comporta toda a estrutura necessária para a utilização do Entity Framework Core.

Definindo uma entidade

Agora podemos definir nossa entidade que representará a tabela:

Country.cs
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
using Tnf.Architecture.Dto;
using Tnf.AutoMapper;
using Tnf.Domain.Entities;
 
[AutoMap(typeof(CountryDto))]
[Table("Countries")]
public class Country : Entity
{
	public const int MaxNameLength = 256;
	[Required]
	[MaxLength(MaxNameLength)]
	public string Name { get; set; }
	public Country()
	{
	}
	public Country(int id, string name)
	{
		Id = id;
		Name = name;
	}
}

A definição de nossa tabela pode ser feita usando atributos como no exemplo acima ou utilizando a FluentAPI do proprio Entity Framework Core para definir colunas e tipos de dados.

Note que temos uma herança para a classe Entity do TNF. Por padrão essa herança define que a entidade tenha uma propriedade Id (int) como chave primária.

Para alterar o tipo da chave primaria a ser usada informe através do parâmetro genérico qual tipo será usado. Exemplo: public class Country : Entity<Guid>

O atributo presente no exemplo AutoMapAttribute é um wrapper do TNF para uso do framework AutoMapper que fará o mapeamento automático da nossa tabela para o seu Dto.

Definindo um contexto

Para trabalhar usando contextos do Entity Framework Core devemos definir a classe que fará o acesso e configuração de nossas entidades.

Para isso o próximo passo é definir o DbContext fazendo a herança do TnfDbContext:

SampleDbContext
using Microsoft.EntityFrameworkCore;
using Tnf.Architecture.EntityFrameworkCore.Entities;
using Tnf.EntityFrameworkCore;
 
public class SampleDbContext : TnfDbContext
{
	public DbSet<Country> Countries { get; set; }

	public ArchitectureDbContext(DbContextOptions<SampleDbContext> options)
		: base(options)
	{
	}
}

Com nosso contexto criado podemos agora utilizar o Entity Framework Core utilizando o contexto criado.

Criando um Repositório

Para trabalhar com repositórios no TNF você precisa definir um TnfDbContext necessariamente. Quando o repositório for criado ele utilizará o TnfDbContext definido para criá-lo.

Vamos criar um repositório para a entidade criada anteriormente em nosso exemplo:

ICountryRepository.cs
public interface ICountryRepository : IRepository<Country>
{
}

Como instalamos o pacote Tnf.App.EntityFrameworkCore e configuramos a dependência do módulo TnfAppEntityFrameworkCoreModule, ao utilizar o tipo ICountryRepository para a entidade "Country", nosso mecanismo de injeção de dependência está injetando o repositório padrão EfCoreRepositoryBase com suporte a métodos para realizar operações de CRUD e queries mais complexas.

Caso você precise de uma especialização para seu repositório com métodos que você queira definir, você pode fazer override dos métodos do repositório ou implementar de acordo com a sua necessidade:

CustomCountryRepository.cs
public class CustomCountryRepository : EfCoreRepositoryBase<ArchitectureDbContext, Country, int>, ICountryRepository
{
	public ProfessionalRepository(IDbContextProvider<ArchitectureDbContext> dbContextProvider) 
		: base(dbContextProvider)
	{
	}
	
	public override Professional Get(decimal id)
	{
    	return base.Get(id);
	}
}

Mapeando uma Estrutura Legada

Em muitos cenários estamos reescrevendo uma funcionalidade de um sistema legado e nos deparamos com chaves primarias compostas. Nesses casos precisamos especializar nosso repositório.

Abaixo temos um exemplo de uma tabela "Professional" representada pela classe ProfessionalPoco:

ProfessionalPoco.cs
using System;
using System.Collections.Generic;
using Tnf.Architecture.Dto.Registration;
using Tnf.AutoMapper;
using Tnf.Domain.Entities;

namespace Tnf.Architecture.EntityFrameworkCore.Entities
{
    [AutoMap(typeof(ProfessionalDto))]
    public class ProfessionalPoco : Entity
    {
        public ProfessionalPoco()
        {
        }
        public decimal ProfessionalId { get; set; }
        public string Name { get; set; }
        public Guid Code { get; set; }
        public string Address { get; set; }
        public string AddressNumber { get; set; }
        public string AddressComplement { get; set; }
        public string ZipCode { get; set; }
        public string Phone { get; set; }
        public string Email { get; set; }
        public List<ProfessionalSpecialtiesPoco> ProfessionalSpecialties { get; set; }
    }
}

Tendo a definição de nossa entidade vamos agora configura-la em nosso contexto:

LegacyDbContext.cs
using Microsoft.EntityFrameworkCore;
using Tnf.EntityFrameworkCore;
using Tnf.Architecture.EntityFrameworkCore.Entities;

namespace Tnf.Architecture.EntityFrameworkCore
{
    public class LegacyDbContext : TnfDbContext
    {
        public DbSet<ProfessionalPoco> Professionals { get; set; }
        public DbSet<ProfessionalSpecialtiesPoco> ProfessionalSpecialties { get; set; }
        public DbSet<SpecialtyPoco> Specialties { get; set; }

        public LegacyDbContext(DbContextOptions<LegacyDbContext> options) : base(options)
        {
        }

        protected override void OnModelCreating(ModelBuilder modelBuilder)
        {
            modelBuilder.Entity<ProfessionalPoco>(m =>
            {
                // PKs
                m.Ignore(i => i.Id);
                m.HasKey(i => new { i.ProfessionalId, i.Code });

                // Configure PKs auto generated
                m.Property(i => i.Code).ValueGeneratedOnAdd().HasColumnName("SYS009_PROFESSIONAL_CODE");

                // Hack pois o método ValueGeneratedOnAdd do EF Core ainda não aceita propriedades decimal
                m.Property(i => i.ProfessionalId).HasDefaultValueSql("SELECT ISNULL(MAX(SYS009_PROFESSIONAL_ID), 1) FROM SYS009_PROFESSIONAL").HasColumnName("SYS009_PROFESSIONAL_ID");

                m.Property(p => p.Name).HasColumnName("SYS009_NAME").HasMaxLength(50).IsRequired();
                m.Property(p => p.Address).HasColumnName("SYS009_ADDRESS").HasMaxLength(50).IsRequired();
                m.Property(p => p.AddressComplement).HasColumnName("SYS009_ADDRESS_COMPLEMENT").HasMaxLength(100).IsRequired();
                m.Property(p => p.AddressNumber).HasColumnName("SYS009_ADDRESS_NUMBER").HasMaxLength(9).IsRequired();
                m.Property(p => p.Email).HasColumnName("SYS009_EMAIL").HasMaxLength(50).IsRequired();
                m.Property(p => p.Email).HasColumnName("SYS009_EMAIL").HasMaxLength(50).IsRequired();
                m.Property(p => p.Phone).HasColumnName("SYS009_PHONE").HasMaxLength(50).IsRequired();
                m.Property(p => p.ZipCode).HasColumnName("SYS009_ZIP_CODE").HasMaxLength(15).IsRequired();

                m.ToTable("SYS009_PROFESSIONAL");
            });

            modelBuilder.Entity<SpecialtyPoco>(m =>
            {
                // PKs
                m.HasKey(i => i.Id);
                // Configure PKs auto generated
                m.Property(i => i.Id).ValueGeneratedOnAdd().HasColumnName("SYS011_SPECIALTIES_ID");
                m.Property(p => p.Description).HasColumnName("SYS011_SPECIALTIES_DESCRIPTION").HasMaxLength(100).IsRequired();
                m.ToTable("SYS011_SPECIALTIES");
            });

            modelBuilder.Entity<ProfessionalSpecialtiesPoco>(m =>
            {
                // PKs
                m.Ignore(i => i.Id);
                m.HasKey(i => new { i.ProfessionalId, i.Code, i.SpecialtyId });
                
                m.Property(i => i.ProfessionalId).HasColumnName("SYS009_PROFESSIONAL_ID");
                m.Property(i => i.Code).HasColumnName("SYS009_PROFESSIONAL_CODE");
                m.Property(i => i.SpecialtyId).HasColumnName("SYS011_SPECIALTIES_ID");
                m.HasOne(o => o.Professional)
                    .WithMany(w => w.ProfessionalSpecialties)
                    .HasPrincipalKey(k => new { k.ProfessionalId, k.Code })
                    .HasForeignKey(k => new { k.ProfessionalId, k.Code });

                m.HasOne(o => o.Specialty)
                    .WithMany(w => w.ProfessionalSpecialties)
                    .HasPrincipalKey(k => k.Id)
                    .HasForeignKey(k => k.SpecialtyId);

                m.ToTable("SYS010_PROFESSIONAL_SPECIALTIES");
            });
        }
    }
}

Note que no contexto acima temos a configuração de nossa entidade Professional com um relacionamento de N para N com a entidade Specialty que representa as especialidades de um profissional.

Como o repositório por default exige uma chave primaria essa é ignorada por ser do tipo Int pela configuração m.Ignore(i => i.Id). 

Definimos nossa chave composta através do método de extensão chamado HasKey passando os múltiplos valores que irão constitui-la.

Como em muitos casos temos que criar chaves auto incremento podemos utilizar o método chamado ValueGeneratedOnAdd que irá configurar a chave como Identity. Existem alguns tipos de dados ainda no Entity Framework Core que não estão funcionando com valores auto incremento como decimal. Para esses valores podemos contornar o problema usando o método HasDefaultValueSql, onde é passada uma instrução SQL que irá recuperar o valor do campo na hora de usar o seu auto incremento (Esse problema já está previsto para ser resolvido em futuras releases do Entity Framework Core).

Note que além das chaves auto incremento configuramos também nomes das colunas, relacionamentos N para N e nomes das tabelas.

Abaixo temos a reescrita de nosso repositório com métodos que atendem o cenários com chaves primarias compostas:

ProfessionalRepository.cs
using Tnf.Architecture.Domain.Interfaces.Repositories;
using Tnf.EntityFrameworkCore.Repositories;
using Tnf.EntityFrameworkCore;
using Tnf.Architecture.EntityFrameworkCore.Entities;
using Tnf.Architecture.Dto;
using System.Collections.Generic;
using System.Linq;
using Tnf.AutoMapper;
using Tnf.Architecture.Dto.Registration;
using Microsoft.EntityFrameworkCore;
using System;

namespace Tnf.Architecture.EntityFrameworkCore.Repositories
{
    public class ProfessionalRepository : EfCoreRepositoryBase<LegacyDbContext, ProfessionalPoco>, IProfessionalRepository
    {
        public ProfessionalRepository(IDbContextProvider<LegacyDbContext> dbContextProvider)
            : base(dbContextProvider)
        {
        }

        public bool DeleteProfessional(ProfessionalKeysDto keys)
        {
            var dbEntity = Context.Professionals
                .Include(i => i.ProfessionalSpecialties)
                .SingleOrDefault(s => s.ProfessionalId == keys.ProfessionalId && s.Code == keys.Code);

            if (dbEntity != null)
            {
                dbEntity.ProfessionalSpecialties.ForEach(w => Context.ProfessionalSpecialties.Remove(w));
                Context.Professionals.Remove(dbEntity);
            }

            return dbEntity != null;
        }

        private ProfessionalPoco GetProfessionalPoco(ProfessionalKeysDto keys)
        {
            var dbEntity = Context.Professionals
                .Include(i => i.ProfessionalSpecialties)
                    .ThenInclude(i => i.Specialty)
                .SingleOrDefault(w => w.ProfessionalId == keys.ProfessionalId && w.Code == keys.Code);

            return dbEntity;
        }

        public ProfessionalDto GetProfessional(ProfessionalKeysDto keys)
        {
            var dbEntity = GetProfessionalPoco(keys);
            return dbEntity != null ? dbEntity.MapTo<ProfessionalDto>() : null;
        }

        public ProfessionalDto CreateProfessional(ProfessionalDto entity)
        {
            var dbEntity = entity.MapTo<ProfessionalPoco>();
            Context.Professionals.Add(dbEntity);
            Context.SaveChanges();
            return dbEntity.MapTo<ProfessionalDto>();
        }

        public ProfessionalDto UpdateProfessional(ProfessionalDto entity)
        {
            var mappedEntity = GetProfessionalPoco(new ProfessionalKeysDto(entity.ProfessionalId, entity.Code));
            entity.MapTo(mappedEntity);
            Context.Professionals.Update(mappedEntity);
            Context.SaveChanges();
            return mappedEntity.MapTo<ProfessionalDto>();
        }

        public PagingResponseDto<ProfessionalDto> GetAllProfessionals(GetAllProfessionalsDto request)
        {
            var response = new PagingResponseDto<ProfessionalDto>();
            var dbBaseQuery = Context.Professionals
                .Include(i => i.ProfessionalSpecialties)
                .Include("ProfessionalSpecialties.Specialty")
                .Where(w => request.Name == null || w.Name.Contains(request.Name));

            var dbQuery = dbBaseQuery
                .Skip(request.Offset)
                .Take(request.PageSize)
                .ToArray();

            response.Total = base.Count();
            response.Data = dbQuery.MapTo<List<ProfessionalDto>>();
            return response;
        }

        public void AddOrRemoveSpecialties(ProfessionalKeysDto keys, List<SpecialtyDto> dto)
        {
            var dbProfessional = GetProfessionalPoco(keys);
            if (dbProfessional != null)
            {
                var idsToAdd = dto.Select(s => s.Id).ToArray();
                if (dbProfessional.ProfessionalSpecialties == null)
                    dbProfessional.ProfessionalSpecialties = new List<ProfessionalSpecialtiesPoco>();
                dbProfessional.ProfessionalSpecialties.RemoveAll(w => !idsToAdd.Contains(w.SpecialtyId));
                dto.ForEach(w =>
                {
                    var dbProfessionalSpecialties = dbProfessional.ProfessionalSpecialties
                        .FirstOrDefault(s => s.SpecialtyId == w.Id);
                    if (dbProfessionalSpecialties == null)
                    {
                        dbProfessional.ProfessionalSpecialties.Add(new ProfessionalSpecialtiesPoco()
                        {
                            ProfessionalId = dbProfessional.ProfessionalId,
                            Code = dbProfessional.Code,
                            SpecialtyId = w.Id
                        });
                    }
                });
            }
        }

        public bool ExistsProfessional(ProfessionalKeysDto keys)
        {
            var dbEntity = Context.Professionals
                .SingleOrDefault(s => s.ProfessionalId == keys.ProfessionalId && s.Code == keys.Code);
            return dbEntity != null;
        }
    }
}

Este exemplo com umas estrutura legada esta disponível em nosso github: https://github.com/totvsnetcore/tnf-samples/tree/master/TnfSample-Architecture

  • Sem rótulos