...
Bloco de código | ||||||||
---|---|---|---|---|---|---|---|---|
| ||||||||
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); } } |
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:
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
{
[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:
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 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:
Bloco de código | ||||||||
---|---|---|---|---|---|---|---|---|
| ||||||||
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