You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何向AutoMapper Profile传参?映射NdtType遇异常求助

问题描述

父表LineJoint与子表NdtStatus是一对多关联,子表记录可通过JointId和NdtId唯一确定。调用仓储方法GetLineJointsForNdtSelectionAsync时,需要通过这两个ID过滤数据——JointId可在解析器中获取,NdtId需从仓储方法传入。

尝试用以下代码传递参数:

.ProjectTo<Models.Output.Piping.LineJoints.LineJointForNdtSelection>(
    _mapper.ConfigurationProvider,
    new { ndtId = query.NdtId })

但传入解析器的NdtId是空GUID。之后改用(Guid)context.Items["ndtId"]在Profile类中获取参数,抛出异常:

Unable to create a map expression from . (Ces.Caspian.Domain.Entities.Piping.LineJoints.LineJoint) to String.NdtType (System.String) Mapping types: LineJoint -> LineJointForNdtSelection Ces.Caspian.Domain.Entities.Piping.LineJoints.LineJoint -> Ces.Caspian.Models.Output.Piping.LineJoints.LineJointForNdtSelection Type Map configuration: LineJoint -> LineJointForNdtSelection Ces.Caspian.Domain.Entities.Piping.LineJoints.LineJoint -> Ces.Caspian.Models.Output.Piping.LineJoints.LineJointForNdtSelection Destination Member: NdtType.

父表实体

public class LineJoint : EntityBase
{
    public const int JointNoIndexMaxLength = 5;
    public const int JointNoMaxLength = 20;

    public LineJoint()
    {
    }

    public LineJoint(Guid? userId) : base(userId)
    {
    }

    public Guid? ContractorId { get; set; }
    public Guid? LineSheetId { get; set; }
    public string? JointNo { get; set; }
    public Guid? ScheduleId { get; set; }
    public Guid? ConnectionTypeId { get; set; }
    public Guid? EndStatusId { get; set; }
    public Guid? EndTypeId { get; set; }
    public Guid? JointTypeId { get; set; }
    public Guid? LocationId { get; set; }
    public Guid? WeldTypeId { get; set; }
    public int Revision { get; set; }
    public Guid? TestPackageId { get; set; }

    // Navigation

    public Domain.Entities.Project.Contractors.Contractor? Contractor { get; set; }
    public Domain.Entities.Piping.LineSheets.LineSheet? LineSheet { get; set; }
    public Domain.Entities.Piping.ConnectionTypes.ConnectionType? ConnectionType { get; set; }
    public Domain.Entities.Piping.EndTypes.EndType? EndType { get; set; }
    public Domain.Entities.Piping.EndStatuses.EndStatus? EndStatus { get; set; }
    public Domain.Entities.Piping.JointTypes.JointType? JointType { get; set; }
    public Domain.Entities.Piping.Locations.Location? Location { get; set; }
    public Domain.Entities.Piping.WeldTypes.WeldType? WeldType { get; set; }
    public Domain.Entities.TestPackage.TestPackages.TestPackage? TestPackage { get; set; }
    public Domain.Entities.Piping.Schedules.Schedule? Schedule { get; set; }
    public List<Domain.Entities.Piping.NdtStatuses.NdtStatus>? NdtStatuses { get; set; }
}

子表实体

public class NdtStatus : Entities.EntityBase
{
    public const int NDTNoMaxLength = 50;

    public NdtStatus()
    {
    }

    public NdtStatus(Guid? userId) : base(userId)
    {
    }

    public Guid? LineJointId { get; set; }
    public Guid? NdtId { get; set; }
    public bool Required { get; set; }
    public bool Exempted { get; set; }
    public string? NdtNo { get; set; }
    public Guid? NdtTypeId { get; set; }

    // Navigation

    public Domain.Entities.Piping.LineJoints.LineJoint? LineJoint { get; set; }
    public Domain.Entities.Piping.Ndts.Ndt? Ndt { get; set; }
    public Domain.Entities.Piping.NdtTypes.NdtType? NdtType { get; set; }
}

输出模型

public class LineJointForNdtSelection : OutputModelEssentials
{
    // From parent table

    public Guid JointId { get; set; }
    public string? Contractor { get; set; }
    public string? SheetNo { get; set; }
    public string? JointNo { get; set; }
    public string? ConnectionType { get; set; } 
    public string? EndStatus { get; set; } 
    public string? EndType { get; set; }
    public string? JointType { get; set; } 
    public string? Location { get; set; } 
    public string? PackageNo { get; set; }

    // From child table

    public string? Ndt { get; set; }
    public string? NdtType { get; set; }
    public bool Required { get; set; }
    public bool Exempted { get; set; }
    public string? NdtNo { get; set; }
    public string? RequestNo { get; set; }
    public string? ReportNo { get; set; }
    public string? Result { get; set; }
}

原AutoMapper Profile类

public class LineJointForNdtSelection : AutoMapper.Profile
{
    public LineJointForNdtSelection()
    {
        CreateMap<
            Domain.Entities.Piping.LineJoints.LineJoint,
            Models.Output.Piping.LineJoints.LineJointForNdtSelection>()

        // From parent table

            .ForMember(x => x.JointId, x => x.MapFrom(x => x.Id))
            .ForMember(x => x.Contractor, x => x.MapFrom(x => x.Contractor.Name))
            .ForMember(x => x.SheetNo, x => x.MapFrom(x => x.LineSheet.SheetNo))
            .ForMember(x => x.ConnectionType, x => x.MapFrom(x => x.ConnectionType.Name))
            .ForMember(x => x.EndStatus, x => x.MapFrom(x => x.EndStatus.Name))
            .ForMember(x => x.EndType, x => x.MapFrom(x => x.EndType.Name))
            .ForMember(x => x.JointType, x => x.MapFrom(x => x.JointType.Name))
            .ForMember(x => x.Location, x => x.MapFrom(x => x.Location.Name))
            .ForMember(x => x.PackageNo, x => x.MapFrom(x => x.TestPackage.TestPackageNo))

        // From child table

            .ForMember(x => x.NdtType, opt => opt.MapFrom((src, dest, destMember, context)
                => new Profiles.Piping.NdtStatuses.Resolvers.NdtTypeResolver((Guid)context.Items["ndtId"])
                    .Resolve(src, dest, destMember, context)));
        ;
    }
}

原自定义解析器

public class NdtTypeResolver : IValueResolver<
    Ces.Caspian.Domain.Entities.Piping.LineJoints.LineJoint,
    Ces.Caspian.Models.Output.Piping.LineJoints.LineJointForNdtSelection,
    string?>
{
    private Guid? _ndtId;

    public NdtTypeResolver()
    {

    }

    public NdtTypeResolver(Guid? ndtId = null)
    {
        _ndtId = ndtId;
    }

    public string? Resolve(
        Ces.Caspian.Domain.Entities.Piping.LineJoints.LineJoint source,
        Ces.Caspian.Models.Output.Piping.LineJoints.LineJointForNdtSelection destination,
        string? destMember,
        ResolutionContext context)
    {
        return source.NdtStatuses?.FirstOrDefault(c
            => c.LineJointId == source.Id && c.NdtId == _ndtId)?.NdtType?.Name ?? string.Empty;
    }
}

原仓储方法

public async Task<List<Models.Output.Piping.LineJoints.LineJointForNdtSelection>> GetLineJointsForNdtSelectionAsync(
    Models.Input.Queries.Piping.LineJoints.GetLineJointsForNdtSelection query)
{
    var result = await _dbContext.LineJoints
        .Where(x => x.LineSheet.LineNumberId == query.LineNumberId)
        .ProjectTo<Models.Output.Piping.LineJoints.LineJointForNdtSelection>(
            _mapper.ConfigurationProvider,
            new { ndtId = query.NdtId })
        .AsNoTracking()
        .ToListAsync()
        ;

    return result;
}
解决方案

问题核心是手动实例化解析器并调用Resolve方法的写法无法被AutoMapper的表达式树解析(ProjectTo依赖LINQ表达式转换为SQL),同时参数传递逻辑存在冲突。以下是正确实现步骤:

1. 调整自定义解析器,直接从上下文读取参数

移除构造函数传参逻辑,在Resolve方法中从context.Items获取NdtId:

public class NdtTypeResolver : IValueResolver<
    Ces.Caspian.Domain.Entities.Piping.LineJoints.LineJoint,
    Ces.Caspian.Models.Output.Piping.LineJoints.LineJointForNdtSelection,
    string?>
{
    public string? Resolve(
        Ces.Caspian.Domain.Entities.Piping.LineJoints.LineJoint source,
        Ces.Caspian.Models.Output.Piping.LineJoints.LineJointForNdtSelection destination,
        string? destMember,
        ResolutionContext context)
    {
        // 校验参数存在性与类型
        if (!context.Items.TryGetValue("ndtId", out var ndtIdObj) || ndtIdObj is not Guid ndtId)
            return string.Empty;

        return source.NdtStatuses?.FirstOrDefault(c
            => c.LineJointId == source.Id && c.NdtId == ndtId)?.NdtType?.Name ?? string.Empty;
    }
}

2. 修改AutoMapper Profile,正确注册解析器

  • 避免Profile类名与输出模型重名,改为LineJointForNdtSelectionProfile
  • 使用ResolveUsing指定解析器类型,让AutoMapper自动管理上下文传递:
public class LineJointForNdtSelectionProfile : AutoMapper.Profile
{
    public LineJointForNdtSelectionProfile()
    {
        CreateMap<
            Domain.Entities.Piping.LineJoints.LineJoint,
            Models.Output.Piping.LineJoints.LineJointForNdtSelection>()

            // 父表字段映射保留不变
            .ForMember(x => x.JointId, x => x.MapFrom(x => x.Id))
            .ForMember(x => x.Contractor, x => x.MapFrom(x => x.Contractor.Name))
            .ForMember(x => x.SheetNo, x => x.MapFrom(x => x.LineSheet.SheetNo))
            .ForMember(x => x.ConnectionType, x => x.MapFrom(x => x.ConnectionType.Name))
            .ForMember(x => x.EndStatus, x => x.MapFrom(x => x.EndStatus.Name))
            .ForMember(x => x.EndType, x => x.MapFrom(x => x.EndType.Name))
            .ForMember(x => x.JointType, x => x.MapFrom(x => x.JointType.Name))
            .ForMember(x => x.Location, x => x.MapFrom(x => x.Location.Name))
            .ForMember(x => x.PackageNo, x => x.MapFrom(x => x.TestPackage.TestPackageNo))

            // 子表字段映射:指定解析器类型
            .ForMember(x => x.NdtType, opt => opt.ResolveUsing<NdtTypeResolver>());
    }
}

3. 仓储方法中完善参数传递与校验

保持ProjectTo的参数传递逻辑,同时增加参数非空校验:

public async Task<List<Models.Output.Piping.LineJoints.LineJointForNdtSelection>> GetLineJointsForNdtSelectionAsync(
    Models.Input.Queries.Piping.LineJoints.GetLineJointsForNdtSelection query)
{
    // 提前校验参数,避免空GUID传入
    if (query.NdtId == Guid.Empty)
        throw new ArgumentException("NdtId 不能为空", nameof(query.NdtId));

    var result = await _dbContext.LineJoints
        .Include(x => x.NdtStatuses) // 若EF需要显式加载关联数据则添加,ProjectTo可自动处理时可省略
        .Where(x => x.LineSheet.LineNumberId == query.LineNumberId)
        .ProjectTo<Models.Output.Piping.LineJoints.LineJointForNdtSelection>(
            _mapper.ConfigurationProvider,
            new { ndtId = query.NdtId })
        .AsNoTracking()
        .ToListAsync();

    return result;
}

4. 其他子表字段的映射扩展

如果需要映射Required、Exempted等其他子表字段,可复用上下文参数过滤逻辑:

.ForMember(x => x.Required, opt => opt.MapFrom((src, dest, _, context) => 
    src.NdtStatuses?.FirstOrDefault(c => c.LineJointId == src.Id && c.NdtId == (Guid)context.Items["ndtId"])?.Required ?? false))
关键说明
  • ProjectTo依赖LINQ表达式树转换,禁止手动调用解析器的Resolve方法,必须让AutoMapper管理解析器生命周期与上下文传递。
  • Profile类名不要与输出模型同名,否则会导致AutoMapper配置冲突。
  • 从context.Items读取参数时必须做类型校验,避免空值或类型转换异常。

内容的提问来源于stack exchange,提问作者Hadi Mazareei

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.15 22:47:32