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

如何在EF Core中使用AutoMapper正确映射多对多关系?

问题描述

我定义了以下两个类:

public class ADto 
{
    public int Id { get; set; }
    public List<BDto>? BSet { get; set; }
}

public class A 
{
    public int Id { get; set; }
    public virtual ICollection<B>? BSet { get; set; }
}

我使用AutoMapper将传入的ADto转换为A后执行创建操作,AutoMapper映射配置如下:

CreateMap<A, ADto>();
CreateMap<B, BDto>();

业务代码:

public async Task<OperationResultDto> CreateAsync(ADto aDto, CancellationToken cancellationToken)
{
    // Create & Map the record
    A a = new();
    mapper.Map(aDto, a);
        
    // Create the object in database
    await repository.CreateAsync(a, cancellationToken);
}   

执行时出现错误:

SqlException: Cannot insert explicit value for identity column in table 'base_qamarks' when IDENTITY_INSERT is set to OFF.

目前我通过在AutoMapper中忽略BSet映射,再手动赋值的方式解决问题:

CreateMap<ADto, A>()
      .ForMember(dest => dest.BSet, opt => opt.Ignore());

手动赋值代码:

public async Task<OperationResultDto> CreateAsync(ADto aDto, CancellationToken cancellationToken)
{
    // Create & Map the record
    A a = new();
    mapper.Map(aDto, a);
    
    // Manually assign it
    foreach (BDto bDto in aDto.BSet)
    {
        B? b = await bRepository.GetEntityAsync(bDto.Id, cancellationToken);

        if (b != null)
        {
            a.BSet.Add(b);
        }
    }
            
    // Create the object in database
    await repository.CreateAsync(a, cancellationToken);
}   

该方法可行但存在冗余代码,请问如何通过AutoMapper实现多对多关系的正确映射?


解决方案

错误根源是:AutoMapper默认会把ADto.BSet中的BDto直接映射为全新的B实体,这些实体带有从BDto过来的已存在Id。EF Core会误判为要插入新的B记录,尝试给自增主键列赋值,从而触发报错。

以下是两种通过AutoMapper优化的方案:

方案一:映射时直接注入仓储处理关联

在ADto到A的映射配置中,自定义BSet的转换逻辑,注入仓储获取已存在的B实体:

// 配置ADto到A的映射,注入IBRepository
CreateMap<ADto, A>()
    .ForMember(dest => dest.BSet, opt => opt.MapFrom(async (src, dest, _, context) =>
    {
        var bRepo = context.GetService<IBRepository>();
        var cancellationToken = context.Items["CancellationToken"] as CancellationToken? ?? CancellationToken.None;
        
        var bEntities = new List<B>();
        if (src.BSet != null)
        {
            foreach (var bDto in src.BSet)
            {
                var b = await bRepo.GetEntityAsync(bDto.Id, cancellationToken);
                if (b != null)
                {
                    bEntities.Add(b);
                }
            }
        }
        return bEntities;
    }));

// 保留原有反向映射
CreateMap<A, ADto>();
CreateMap<B, BDto>();

调整业务代码,将CancellationToken传入AutoMapper上下文:

public async Task<OperationResultDto> CreateAsync(ADto aDto, CancellationToken cancellationToken)
{
    var a = mapper.Map<A>(aDto, opts => 
        opts.Items["CancellationToken"] = cancellationToken);
    
    await repository.CreateAsync(a, cancellationToken);
    // 返回对应的OperationResultDto
}

方案二:使用自定义转换器解耦

创建独立的类型转换器,封装BDto到B实体的关联逻辑:

public class BSetConverter : IAsyncTypeConverter<List<BDto>, ICollection<B>>
{
    private readonly IBRepository _bRepo;

    public BSetConverter(IBRepository bRepo)
    {
        _bRepo = bRepo;
    }

    public async Task<ICollection<B>> Convert(List<BDto> source, ICollection<B> destination, ResolutionContext context)
    {
        var cancellationToken = context.Items["CancellationToken"] as CancellationToken? ?? CancellationToken.None;
        var bEntities = new List<B>();
        if (source != null)
        {
            foreach (var bDto in source)
            {
                var b = await _bRepo.GetEntityAsync(bDto.Id, cancellationToken);
                if (b != null)
                {
                    bEntities.Add(b);
                }
            }
        }
        return bEntities;
    }
}

在AutoMapper配置中注册转换器:

CreateMap<ADto, A>()
    .ForMember(dest => dest.BSet, opt => opt.ConvertUsing<BSetConverter>());

// 保留原有映射
CreateMap<A, ADto>();
CreateMap<B, BDto>();

业务代码同样需要传递CancellationToken到AutoMapper上下文,和方案一的业务代码一致。

两种方案都能自动完成多对多关系的实体关联,省去手动遍历的冗余代码,同时避免EF Core误插入已存在的B实体。


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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.18 20:35:22