...
Bloco de código | ||||||||
---|---|---|---|---|---|---|---|---|
| ||||||||
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()Assembly); } } |
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.
...
Bloco de código | ||||||||
---|---|---|---|---|---|---|---|---|
| ||||||||
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;
}
} |
...
Bloco de código | ||||||||
---|---|---|---|---|---|---|---|---|
| ||||||||
public interface ICountryRepository : IRepository<Country> { } |
Essa interface está dentro do projeto de Domain para que ele não precise conhecer o projeto de Arquitetura.
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 EfCoreRepositoryBasepadrão AppEfCoreRepositoryBase com suporte a métodos para realizar operações de CRUD e queries mais complexas.
...
Bloco de código | ||||||||
---|---|---|---|---|---|---|---|---|
| ||||||||
public class CustomCountryRepository : EfCoreRepositoryBase<ArchitectureDbContextAppEfCoreRepositoryBase<ArchitectureDbContext, Country, int>, ICountryRepository { public ProfessionalRepository(IDbContextProvider<ArchitectureDbContext> dbContextProvider) : base(dbContextProvider) { } public override Professional Get(decimal id) { return base.Get(id); } } |
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 (PKs: ProfessionalId, Code):
Bloco de código | ||||||||
---|---|---|---|---|---|---|---|---|
| ||||||||
using System;
using System.Collections.Generic;
using Tnf.Architecture.Dto.Registration;
using Tnf.AutoMapper;
using Tnf.Domain.Entities;
namespace Tnf.Architecture.EntityFrameworkCore.Entities
{
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; }
}
} |
Configuração do contexto:
Bloco de código | ||||||||
---|---|---|---|---|---|---|---|---|
| ||||||||
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 (SYS009_PROFESSIONAL) com um relacionamento de N para N (SYS010_PROFESSIONAL_SPECIALTIES) com a entidade Specialty (SYS011_SPECIALTIES) que representa as especialidades de um profissional.
Como o repositório por default exige uma chave primaria, nossa entidade comporta apenas uma chave. Dessa forma devemos ignora-la na hora de representar a tabela em nosso contexto utilizando a 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.
Obs: 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).
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:
Bloco de código | ||||||||
---|---|---|---|---|---|---|---|---|
| ||||||||
using System; using System.Collections.Generic; using Tnf.App.Dto.Request; using Tnf.Architecture.Common.ValueObjects; using Tnf.Architecture.Domain.Registration; using Tnf.Domain.Repositories; namespace Tnf.Architecture.Domain.Interfaces.Repositories { public interface IProfessionalRepository : IRepository { Professional GetProfessional(IRequestDto<ComposeKey<Guid, decimal>> requestDto); ComposeKey<Guid, decimal> CreateProfessional(Professional entity); void UpdateProfessional(Professional entity); bool DeleteProfessional(ComposeKey<Guid, decimal> keys); void AddOrRemoveSpecialties(ComposeKey<Guid, decimal> keys, IList<Specialty> specialties); bool ExistsProfessional(ComposeKey<Guid, decimal> keys); } } |
Bloco de código | ||||||||
---|---|---|---|---|---|---|---|---|
| ||||||||
using Microsoft.EntityFrameworkCore; using System; using System.Collections.Generic; using System.Linq; using System.Transactions; using Tnf.App.AutoMapper; using Tnf.App.Dto.Request; using Tnf.App.EntityFrameworkCore; using Tnf.App.EntityFrameworkCore.Repositories; using Tnf.Architecture.Common.ValueObjects; using Tnf.Architecture.Domain.Interfaces.Repositories; using Tnf.Architecture.Domain.Registration; using Tnf.Architecture.Dto.Registration; using Tnf.Architecture.EntityFrameworkCore.Contexts; using Tnf.Architecture.EntityFrameworkCore.Entities; using Tnf.Domain.Uow; using Tnf.EntityFrameworkCore; namespace Tnf.Architecture.EntityFrameworkCore.Repositories { public class ProfessionalRepository : AppEfCoreRepositoryBase<LegacyDbContext, ProfessionalPoco>, IProfessionalRepository { private readonly IUnitOfWorkManager _unitOfWorkManager; public ProfessionalRepository(IDbContextProvider<LegacyDbContext> dbContextProvider, IUnitOfWorkManager unitOfWorkManager) : base(dbContextProvider) { _unitOfWorkManager = unitOfWorkManager; } public bool DeleteProfessional(ComposeKey<Guid, decimal> keys) { var dbEntity = Context.Professionals .Include(i => i.ProfessionalSpecialties) .SingleOrDefault(s => s.ProfessionalId == keys.SecundaryKey && s.Code == keys.PrimaryKey); if (dbEntity == null) return false; dbEntity.ProfessionalSpecialties.ForEach(w => Context.ProfessionalSpecialties.Remove(w)); Context.Professionals.Remove(dbEntity); return true; } private ProfessionalPoco GetProfessionalPoco(IRequestDto<ComposeKey<Guid, decimal>> requestDto) { var dbEntity = Context.Professionals .IncludeByRequestDto(requestDto) .Where(w => w.ProfessionalId == requestDto.GetId().SecundaryKey && w.Code == requestDto.GetId().PrimaryKey) .SelectFieldsByRequestDto(requestDto) .SingleOrDefault(); return dbEntity; } public Professional GetProfessional(IRequestDto<ComposeKey<Guid, decimal>> requestDto) { var dbEntity = GetProfessionalPoco(requestDto); if (dbEntity == null) return null; var dto = dbEntity.MapTo<Professional>(); return dto; } public ComposeKey<Guid, decimal> CreateProfessional(Professional entity) { var dbEntity = entity.MapTo<ProfessionalPoco>(); dbEntity.ProfessionalId = GetNextKeyProfessional(); Context.Professionals.Add(dbEntity); Context.SaveChanges(); return new ComposeKey<Guid, decimal>(dbEntity.Code, dbEntity.ProfessionalId); } private decimal GetNextKeyProfessional() { using (var uow = _unitOfWorkManager.Begin(TransactionScopeOption.RequiresNew)) { var lastKey = Context.Professionals.Select(s => s.ProfessionalId).DefaultIfEmpty(0).Max(); return (lastKey + 1); } } public void UpdateProfessional(Professional entity) { var mappedEntity = GetProfessionalPoco(new RequestDto<ComposeKey<Guid, decimal>>(new ComposeKey<Guid, decimal>(entity.Code, entity.ProfessionalId))); entity.MapTo(mappedEntity); Context.Professionals.Update(mappedEntity); Context.SaveChanges(); } public void AddOrRemoveSpecialties(ComposeKey<Guid, decimal> keys, IList<Specialty> specialties) { var request = new RequestDto<ComposeKey<Guid, decimal>>(keys) { Expand = new ProfessionalDto().Expandables[0] }; var dbProfessional = GetProfessionalPoco(request); if (dbProfessional == null) return; var idsToAdd = specialties.Select(s => s.Id).ToArray(); if (dbProfessional.ProfessionalSpecialties == null) dbProfessional.ProfessionalSpecialties = new List<ProfessionalSpecialtiesPoco>(); dbProfessional.ProfessionalSpecialties.RemoveAll(w => !idsToAdd.Contains(w.SpecialtyId)); foreach (var specialty in specialties) { var dbProfessionalSpecialties = dbProfessional.ProfessionalSpecialties .FirstOrDefault(s => s.SpecialtyId == specialty.Id); if (dbProfessionalSpecialties == null) { dbProfessional.ProfessionalSpecialties.Add(new ProfessionalSpecialtiesPoco { ProfessionalId = dbProfessional.ProfessionalId, Code = dbProfessional.Code, SpecialtyId = specialty.Id }); } } } public bool ExistsProfessional(ComposeKey<Guid, decimal> keys) { var dbEntity = Context.Professionals .SingleOrDefault(s => s.ProfessionalId == keys.SecundaryKey && s.Code == keys.PrimaryKey); 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