如何使用 Automapper 映射两个嵌套列表

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

实体

public class PurchaseReq
{
    ...
    public string PrNumber { get; set; } // primary key
    public List<PurchaseReqItem> PurchaseReqItems { get; init; } = [];
    public List<PurchaseReqCharge> PurchaseReqCharges { get; init; } = [];
}

public class PurchaseReqItem
{
    ...
    public int Id { get; init; } // primary key
    public string PrNumber { get; set; } // foreign key
    public PurchaseReq PurchaseReq { get; init; } //navigation property
}

public class PurchaseReqCharge
{
    ...
    public int Id { get; init; } // primary key
    public string PrNumber { get; set; } // foreign key
    public PurchaseReq PurchaseReq { get; init; } //navigation property
}

Dtos

public class PurchaseReqDto
{
    ...
    public string PrNumber { get; set; }
    public List<PurchaseReqItemDto> PurchaseReqItems { get; init; } = [];
    public List<PurchaseReqChargeDto> PurchaseReqCharges { get; init; } = [];
}

public class PurchaseReqItemDto
{
    ...
    public int Id { get; init; }
    public string PrNumber { get; set; }
    public PurchaseReqDto PurchaseReq { get; init; }
}

public class PurchaseReqChargeDto
{
    ...
    public int Id { get; init; }
    public string PrNumber { get; set; }
    public PurchaseReqDto PurchaseReq { get; init; }
}

映射配置文件

public class PurchaseReqProfile : Profile
{
    public PurchaseReqProfile()
    {
        CreateMap<PurchaseReq, PurchaseReqDto>();
        
        CreateMap<PurchaseReqDto, PurchaseReq>()
            .ForMember(d => d.PurchaseReqItems, o => o.MapFrom(src => src.PurchaseReqItems))
            .ForMember(d => d.PurchaseReqCharges, o => o.MapFrom(src => src.PurchaseReqCharges));
    }
}

public class PurchaseReqItemProfile : Profile
{
    public PurchaseReqItemProfile()
    {
        CreateMap<PurchaseReqItem, PurchaseReqItemDto>();
        
        CreateMap<PurchaseReqItemDto, PurchaseReqItem>()
            .ForMember(d => d.TaxRate, o => o.Ignore());
    }
}

public class PurchaseReqChargeProfile : Profile
{
    public PurchaseReqChargeProfile()
    {
        CreateMap<PurchaseReqCharge, PurchaseReqChargeDto>();
        
        CreateMap<PurchaseReqChargeDto, PurchaseReqCharge>()
            .ForMember(d => d.OtherCharge, o => o.Ignore());
    }
}

AutoMapper 添加到 Program.cs 中的服务 自动映射器版本 13.0.1

builder.Services.AddAutoMapper(AppDomain.CurrentDomain.GetAssemblies());

PurchaseReqRepo 获取的数据

public async Task<PurchaseReq> GetPrForUpdateAsync(string prNumber)
    {
        ArgumentNullException.ThrowIfNull(prNumber);

        return await context.PurchaseReqs
            .Include(pr => pr.PurchaseReqItems)
            .Include(pr => pr.PurchaseReqCharges)
            .SingleOrDefaultAsync(pr => pr.PrNumber == prNumber);
    }

更新API

[HttpPut]
[CanUserUpdate(RolesConstant.PurchaseReq)]
public async Task<IActionResult> UpdatePurchaseReq([FromBody] PurchaseReqDto purchaseReqDto)
{
    if (!short.TryParse(User.Claims.FirstOrDefault(c => c.Type == "sub")?.Value,out var userId))
        return Unauthorized();
        
    var purchaseReq = await purchaseReqRepo.GetPrForUpdateAsync(purchaseReqDto.PrNumber);
    if (purchaseReq == null)
        return NotFound();
        
    if (purchaseReq.IsSubmitted)
        return BadRequest(string.Format($"PR ({purchaseReq.PrNumber}) was already submitted for approval and can't be updated."));
        
    mapper.Map(purchaseReqDto, purchaseReq, options => 
        options.BeforeMap((prDto, pr) =>
        {
            pr.RemoveUnMatchedItems(prDto.PurchaseReqItems);
            pr.RemoveUnMatchedCharges(prDto.PurchaseReqCharges);
        }));
        
    purchaseReq.UpdateBy(userId);
        
    await purchaseReqRepo.SaveChangesAsync();

    return NoContent();
}

将purchaseReqDto映射到purchaseReq时,我收到AutoMapper错误,System.InvalidOperationException:无法跟踪实体类型“PurchaseReqCharge”的实例,因为已跟踪具有键值“{Id:3}”的另一个实例。附加现有实体时,请确保仅附加一个具有给定键值的实体实例。

我不知道我在这里做错了什么。

c# asp.net-core entity-framework-core automapper
1个回答
0
投票

我不知道我在这里做错了什么

EF 使用 tracking 执行数据修改操作,当您获取数据进行更新时,它会被跟踪,但是 AutoMapper 在映射期间将使用新元素创建全新的集合,因此当您尝试保存更新的实体时,它将包含“重复”项目(从 EF 更改跟踪器的角度来看)。

您可以尝试查看

AutoMapper.Collection
图书馆。例如,作为映射器级别的快速修复,类似以下内容应该有效:

builder.Services.AddAutoMapper(cfg => cfg.AddCollectionMappers(), typeof(PurchaseReq));

对于所有 Dto -> 实体映射:

CreateMap<PurchaseReqItemDto, PurchaseReqItem>()
    .EqualityComparison((dto, item) => item.Id == dto.Id);

但是请考虑检查文档 -

Automapper.Collection.EntityFrameworkCore
有一套用于管理此类情况的特定方法。

总的来说,我认为创建与您的实体(包括关系周期)完全匹配的 DTO 并不是一个好主意。

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