当一个 DTO 使用 ID 属性和有点不同的关系模型扩展另一个 DTO 时,如何避免 DTO 之间的代码重复

问题描述 投票:0回答:1

我有一个域实体

KycInformation
,我使用两个 DTO 来处理不同的场景:

  • KycInformationDto
    用于创建操作(无需 ID)
  • KycInformationDetailDto
    用于获取/更新操作(包括 ID)

这些 DTO 之间的唯一区别是:

  1. KycInformationDetailDto
    包含
    Id
    属性
  2. Documents
    集合使用
    KycDocumentDetailDto
    代替
    KycDocumentDto

减少代码重复同时保持创建和获取/更新 DTO 之间的分离的最佳方法是什么?我正在使用 AutoMapper 在这些类型之间进行映射。

public class KycInformation : BaseEntity
{
    public required KycStatus Status { get; set; }
    public required RiskRating RiskRating { get; set; }
    public required AmlStatus AmlStatus { get; set; }
    public required SanctionsCheckStatus SanctionsCheckStatus { get; set; }
    public string? ComplianceNotes { get; set; }
    public string? ParentCompany { get; set; }

    public required Guid LegalEntityId { get; set; }
    public required LegalEntity LegalEntity { get; set; }

    public ICollection<KycDocument> Documents { get; set; } = new List<KycDocument>();

    public void AddDocument(KycDocument document) => Documents.Add(document);

    public void ClearDocuments() => Documents.Clear();
}

public record KycInformationDto
{
    public required KycStatus Status { get; init; }
    public required RiskRating RiskRating { get; init; }
    public required AmlStatus AmlStatus { get; init; }
    public required SanctionsCheckStatus SanctionsCheckStatus { get; init; }
    public string? ComplianceNotes { get; init; }
    public string? ParentCompany { get; init; }

    public ICollection<KycDocumentDto> Documents { get; init; } = new List<KycDocumentDto>();

    private class Mapping : Profile
    {
        public Mapping()
        {
            CreateMap<KycInformation, KycInformationDto>();
            CreateMap<KycInformationDto, KycInformation>();
        }
    }
}

public record KycInformationDetailDto : KycInformationDto
{
    public required Guid Id { get; init; }

    public new ICollection<KycDocumentDetailDto> Documents { get; init; } = new List<KycDocumentDetailDto>();

    private class Mapping : Profile
    {
        public Mapping()
        {
            CreateMap<KycInformation, KycInformationDetailDto>();
            CreateMap<KycInformationDetailDto, KycInformation>();
        }
    }
}

public record KycDocumentDto
{
    public required string DocumentType { get; init; }
    public required string DocumentName { get; init; }
    public string? DocumentDescription { get; init; }
    public required string DocumentUrl { get; init; }

    private class Mapping : Profile
    {
        public Mapping()
        {
            CreateMap<KycDocument, KycDocumentDto>();
            CreateMap<KycDocumentDto, KycDocument>();
        }
    }
}

public record KycDocumentDetailDto : KycDocumentDto
{
    public required Guid Id { get; init; }

    private class Mapping : Profile
    {
        public Mapping()
        {
            CreateMap<KycDocument, KycDocumentDetailDto>();
            CreateMap<KycDocumentDetailDto, KycDocument>();
        }
    }
}
c# .net asp.net-core automapper
1个回答
0
投票

在我看来,使用组合方法将是正确的选择,它将帮助您避免或减少重复,因此您不需要创建单独的 DTO 来创建和更新。并且您使用的是 EF Core,它将自行管理 id。当您调用

SaveChanges
时,EF Core 将处理 ID 分配,因此您不需要创建单独的 DTO 来排除
Id
。使用
public Guid? Id => IdComponent.Id;
因此您不需要创建单独的类型。

我已经准备好了示例演示:

KycInformationController.cs:

using KycInformationApi.DTOs;
using KycInformationApi.Services;
using Microsoft.AspNetCore.Mvc;

namespace KycInformationApi.Controllers
{
    [ApiController]
    [Route("api/[controller]")]
    public class KycInformationController : ControllerBase
    {
        private readonly KycInformationService _service;

        public KycInformationController(KycInformationService service)
        {
            _service = service;
        }

        [HttpPost]
        [ProducesResponseType(typeof(KycInformationDto), StatusCodes.Status201Created)]
        public async Task<ActionResult<KycInformationDto>> CreateKyc([FromBody] KycInformationDto dto)
        {
            var createdKyc = await _service.CreateKycAsync(dto);
            return CreatedAtAction(nameof(GetKycById), new { id = createdKyc.Id }, createdKyc);
        }

        [HttpGet("{id}")]
        [ProducesResponseType(typeof(KycInformationDto), StatusCodes.Status200OK)]
        [ProducesResponseType(StatusCodes.Status404NotFound)]
        public async Task<ActionResult<KycInformationDto>> GetKycById(Guid id)
        {
            var kyc = await _service.GetKycByIdAsync(id);
            if (kyc == null)
            {
                return NotFound();
            }
            return Ok(kyc);
        }

    }

}

IdComponent.cs:

namespace KycInformationApi.DTOs
{
    public record IdComponent
    {
        public Guid? Id { get; set; }
    }


    public record KycInformationDto
    {
        public IdComponent IdComponent { get; init; } = new IdComponent();
        public required KycStatus Status { get; init; }
        public required RiskRating RiskRating { get; init; }
        public required AmlStatus AmlStatus { get; init; }
        public required SanctionsCheckStatus SanctionsCheckStatus { get; init; }
        public string? ComplianceNotes { get; init; }
        public string? ParentCompany { get; init; }
        public ICollection<KycDocumentDto> Documents { get; init; } = new List<KycDocumentDto>();

        public Guid? Id => IdComponent.Id;
    }

    public record KycDocumentDto
    {
        public required string DocumentType { get; init; }
        public required string DocumentName { get; init; }
        public string? DocumentDescription { get; init; }
        public required string DocumentUrl { get; init; }
    }

}

KycInformation.cs:

namespace KycInformationApi.Models
{
    public class KycInformation
    {
        public Guid Id { get; set; } = Guid.NewGuid();
        public required KycStatus Status { get; set; }
        public required RiskRating RiskRating { get; set; }
        public required AmlStatus AmlStatus { get; set; }
        public required SanctionsCheckStatus SanctionsCheckStatus { get; set; }
        public string? ComplianceNotes { get; set; }
        public string? ParentCompany { get; set; }
        public ICollection<KycDocument> Documents { get; set; } = new List<KycDocument>();
    }

    public class KycDocument
    {
        public Guid Id { get; set; } = Guid.NewGuid();
        public required string DocumentType { get; set; }
        public required string DocumentName { get; set; }
        public string? DocumentDescription { get; set; }
        public required string DocumentUrl { get; set; }
    }

}

KycInformationRepository.cs:

using KycInformationApi.Models;
using Microsoft.EntityFrameworkCore;

namespace KycInformationApi.Repositories
{
    public class KycInformationRepository
    {
        private readonly ApplicationDbContext _context;

        public KycInformationRepository(ApplicationDbContext context)
        {
            _context = context;
        }

        public async Task AddAsync(KycInformation kycInformation)
        {
            _context.KycInformations.Add(kycInformation);
            await _context.SaveChangesAsync();
        }

        public async Task<KycInformation?> GetByIdAsync(Guid id)
        {
            return await _context.KycInformations
                .Include(k => k.Documents)
                .FirstOrDefaultAsync(k => k.Id == id);
        }
    }

}

KycInformationService.cs:

using AutoMapper;
using KycInformationApi.DTOs;
using KycInformationApi.Models;
using KycInformationApi.Repositories;

namespace KycInformationApi.Services
{
    public class KycInformationService
    {
        private readonly KycInformationRepository _repository;
        private readonly IMapper _mapper;

        public KycInformationService(KycInformationRepository repository, IMapper mapper)
        {
            _repository = repository;
            _mapper = mapper;
        }

        public async Task<KycInformationDto> CreateKycAsync(KycInformationDto dto)
        {
            var kycEntity = _mapper.Map<KycInformation>(dto);
            await _repository.AddAsync(kycEntity);

            // Retrieve saved entity with ID and map back to DTO
            return _mapper.Map<KycInformationDto>(kycEntity);
        }

        public async Task<KycInformationDto?> GetKycByIdAsync(Guid id)
        {
            var kycEntity = await _repository.GetByIdAsync(id);
            return kycEntity == null ? null : _mapper.Map<KycInformationDto>(kycEntity);
        }
    }

}

ApplicationDbContext.cs:

using KycInformationApi.Models;
using Microsoft.EntityFrameworkCore;

namespace KycInformationApi
{
    public class ApplicationDbContext : DbContext
    {
        public ApplicationDbContext(DbContextOptions<ApplicationDbContext> options) : base(options) { }

        public DbSet<KycInformation> KycInformations { get; set; } = null!;
        public DbSet<KycDocument> KycDocuments { get; set; } = null!;

        protected override void OnModelCreating(ModelBuilder modelBuilder)
        {
            modelBuilder.Entity<KycInformation>().HasKey(k => k.Id);
            modelBuilder.Entity<KycDocument>().HasKey(d => d.Id);
        }
    }

}

KycMappingProfile.cs:

using AutoMapper;
using KycInformationApi.DTOs;
using KycInformationApi.Models;

namespace KycInformationApi
{
    public class KycMappingProfile : Profile
    {
        public KycMappingProfile()
        {
            // Map between KycInformation and KycInformationDto, including reverse mapping
            CreateMap<KycInformation, KycInformationDto>()
                .ForPath(dest => dest.IdComponent.Id, opt => opt.MapFrom(src => src.Id))
                .ForMember(dest => dest.Documents, opt => opt.MapFrom(src => src.Documents))
                .ReverseMap(); // This will handle mapping in both directions

            // Map between KycDocument and KycDocumentDto, including reverse mapping
            CreateMap<KycDocument, KycDocumentDto>().ReverseMap();
        }
    }

}

程序.cs:

using KycInformationApi.Repositories;
using KycInformationApi.Services;
using KycInformationApi;
using Microsoft.EntityFrameworkCore;
using KycInformationApi.DTOs;

var builder = WebApplication.CreateBuilder(args);

// Add services to the container.

builder.Services.AddControllers()
     .AddJsonOptions(options =>
     {
         options.JsonSerializerOptions.Converters.Add(new System.Text.Json.Serialization.JsonStringEnumConverter());
     });
// Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();

// Register services
builder.Services.AddDbContext<ApplicationDbContext>(options =>
    options.UseInMemoryDatabase("KycDb"));
builder.Services.AddAutoMapper(typeof(KycMappingProfile));
builder.Services.AddScoped<KycInformationRepository>();
builder.Services.AddScoped<KycInformationService>();

var app = builder.Build();

// Configure the HTTP request pipeline.
if (app.Environment.IsDevelopment())
{
    app.UseSwagger();
    app.UseSwaggerUI();
}

app.UseHttpsRedirection();

app.UseAuthorization();

app.MapControllers();

// Seed data
using (var scope = app.Services.CreateScope())
{
    var service = scope.ServiceProvider.GetRequiredService<KycInformationService>();

    // Example usage: Creating and retrieving KYC info
    var newKyc = new KycInformationDto
    {
        Status = KycStatus.Active,
        RiskRating = RiskRating.Medium,
        AmlStatus = AmlStatus.Clear,
        SanctionsCheckStatus = SanctionsCheckStatus.None,
        ComplianceNotes = "Initial compliance review",
        ParentCompany = "Example Corp",
        Documents = new List<KycDocumentDto>
        {
            new KycDocumentDto
            {
                DocumentType = "ID",
                DocumentName = "Passport",
                DocumentUrl = "http://example.com/doc.pdf"
            }
        }
    };

  
    var createdKyc = await service.CreateKycAsync(newKyc);

    var logger = scope.ServiceProvider.GetRequiredService<ILogger<Program>>();
    logger.LogInformation("Created KYC ID: {Id}", createdKyc.Id);
}



app.Run();

因此,在上面的代码中,KycInformationService 处理 DTO 和实体之间的映射。KycInformationRepository 添加了处理 KycInformation.KycMappingProfile 的创建和按 id 获取的逻辑,我们配置了 AutoMapper 来处理启用了反向映射的 DTO 和模型之间的映射。

enter image description here

enter image description here

© www.soinside.com 2019 - 2024. All rights reserved.