从 AutoMapper 迁移到 Mapperly
介绍
AutoMapper 库不再免费用于商业用途。更多详细信息,您可以参考此公告文章。
ABP 框架同时提供了 AutoMapper 和 Mapperly 集成。如果您当前的项目正在使用 AutoMapper 并且您没有商业许可证,您可以按照以下步骤切换到 Mapperly。
迁移步骤
请在 IDE(Visual Studio、VS Code 或 JetBrains Rider)中打开您的项目,然后执行以下全局搜索和替换操作:
- 在所有
*.csproj文件中,将Volo.Abp.AutoMapper替换为Volo.Abp.Mapperly。 - 在所有
*.cs文件中,将using Volo.Abp.AutoMapper;替换为using Volo.Abp.Mapperly;。 - 在所有
*.cs文件中,将AbpAutoMapperModule替换为AbpMapperlyModule。 - 在所有
*.cs文件中,将AddAutoMapperObjectMapper替换为AddMapperlyObjectMapper。 - 移除所有配置
Configure<AbpAutoMapperOptions>的代码段。 - 检查任何现有的 AutoMapper
Profile类,并确保所有新创建的 Mapperly 映射类都已正确注册。(您可以参考下面的示例作为指导)
示例:
这是一个 AutoMapper 的 Profile 类:
public class ExampleAutoMapper : Profile
{
public AbpIdentityApplicationModuleAutoMapperProfile()
{
CreateMap<IdentityUser, IdentityUserDto>()
.MapExtraProperties()
.Ignore(x => x.IsLockedOut)
.Ignore(x => x.SupportTwoFactor)
.Ignore(x => x.RoleNames);
CreateMap<IdentityUserClaim, IdentityUserClaimDto>();
CreateMap<OrganizationUnit, OrganizationUnitDto>()
.MapExtraProperties();
CreateMap<OrganizationUnitRole, OrganizationUnitRoleDto>()
.ReverseMap();
CreateMap<IdentityRole, OrganizationUnitRoleDto>()
.ForMember(dest => dest.RoleId, src => src.MapFrom(r => r.Id));
CreateMap<IdentityUser, IdentityUserExportDto>()
.ForMember(dest => dest.Active, src => src.MapFrom(r => r.IsActive ? "Yes" : "No"))
.ForMember(dest => dest.EmailConfirmed, src => src.MapFrom(r => r.EmailConfirmed ? "Yes" : "No"))
.ForMember(dest => dest.TwoFactorEnabled, src => src.MapFrom(r => r.TwoFactorEnabled ? "Yes" : "No"))
.ForMember(dest => dest.AccountLookout, src => src.MapFrom(r => r.LockoutEnd != null && r.LockoutEnd > DateTime.UtcNow ? "Yes" : "No"))
.Ignore(x => x.Roles);
}
}
以及对应的 Mapperly 映射类:
[Mapper(RequiredMappingStrategy = RequiredMappingStrategy.Target)]
[MapExtraProperties]
public partial class IdentityUserToIdentityUserDtoMapper : MapperBase<IdentityUser, IdentityUserDto>
{
[MapperIgnoreTarget(nameof(IdentityUserDto.IsLockedOut))]
[MapperIgnoreTarget(nameof(IdentityUserDto.SupportTwoFactor))]
[MapperIgnoreTarget(nameof(IdentityUserDto.RoleNames))]
public override partial IdentityUserDto Map(IdentityUser source);
[MapperIgnoreTarget(nameof(IdentityUserDto.IsLockedOut))]
[MapperIgnoreTarget(nameof(IdentityUserDto.SupportTwoFactor))]
[MapperIgnoreTarget(nameof(IdentityUserDto.RoleNames))]
public override partial void Map(IdentityUser source, IdentityUserDto destination);
}
[Mapper(RequiredMappingStrategy = RequiredMappingStrategy.Target)]
public partial class IdentityUserClaimToIdentityUserClaimDtoMapper : MapperBase<IdentityUserClaim, IdentityUserClaimDto>
{
public override partial IdentityUserClaimDto Map(IdentityUserClaim source);
public override partial void Map(IdentityUserClaim source, IdentityUserClaimDto destination);
}
[Mapper(RequiredMappingStrategy = RequiredMappingStrategy.Target)]
[MapExtraProperties]
public partial class OrganizationUnitToOrganizationUnitDtoMapper : MapperBase<OrganizationUnit, OrganizationUnitDto>
{
public override partial OrganizationUnitDto Map(OrganizationUnit source);
public override partial void Map(OrganizationUnit source, OrganizationUnitDto destination);
}
[Mapper(RequiredMappingStrategy = RequiredMappingStrategy.Target)]
public partial class OrganizationUnitRoleToOrganizationUnitRoleDtoMapper : TwoWayMapperBase<OrganizationUnitRole, OrganizationUnitRoleDto>
{
public override partial OrganizationUnitRoleDto Map(OrganizationUnitRole source);
public override partial void Map(OrganizationUnitRole source, OrganizationUnitRoleDto destination);
public override partial OrganizationUnitRole ReverseMap(OrganizationUnitRoleDto destination);
public override partial void ReverseMap(OrganizationUnitRoleDto destination, OrganizationUnitRole source);
}
[Mapper(RequiredMappingStrategy = RequiredMappingStrategy.Target)]
[MapExtraProperties]
public partial class OrganizationUnitToOrganizationUnitWithDetailsDtoMapper : MapperBase<OrganizationUnit, OrganizationUnitWithDetailsDto>
{
[MapperIgnoreTarget(nameof(OrganizationUnitWithDetailsDto.Roles))]
[MapperIgnoreTarget(nameof(OrganizationUnitWithDetailsDto.UserCount))]
public override partial OrganizationUnitWithDetailsDto Map(OrganizationUnit source);
[MapperIgnoreTarget(nameof(OrganizationUnitWithDetailsDto.Roles))]
[MapperIgnoreTarget(nameof(OrganizationUnitWithDetailsDto.UserCount))]
public override partial void Map(OrganizationUnit source, OrganizationUnitWithDetailsDto destination);
}
[Mapper(RequiredMappingStrategy = RequiredMappingStrategy.Target)]
public partial class IdentityRoleToOrganizationUnitRoleDtoMapper : MapperBase<IdentityRole, OrganizationUnitRoleDto>
{
[MapProperty(nameof(IdentityRole.Id), nameof(OrganizationUnitRoleDto.RoleId))]
public override partial OrganizationUnitRoleDto Map(IdentityRole source);
[MapProperty(nameof(IdentityRole.Id), nameof(OrganizationUnitRoleDto.RoleId))]
public override partial void Map(IdentityRole source, OrganizationUnitRoleDto destination);
}
[Mapper(RequiredMappingStrategy = RequiredMappingStrategy.Target)]
public partial class IdentityUserToIdentityUserExportDtoMapper : MapperBase<IdentityUser, IdentityUserExportDto>
{
[MapperIgnoreTarget(nameof(IdentityUserExportDto.Roles))]
public override partial IdentityUserExportDto Map(IdentityUser source);
[MapperIgnoreTarget(nameof(IdentityUserExportDto.Roles))]
public override partial void Map(IdentityUser source, IdentityUserExportDto destination);
public override void AfterMap(IdentityUser source, IdentityUserExportDto destination)
{
destination.Active = source.IsActive ? "Yes" : "No";
destination.EmailConfirmed = source.EmailConfirmed ? "Yes" : "No";
destination.TwoFactorEnabled = source.TwoFactorEnabled ? "Yes" : "No";
destination.AccountLookout = source.LockoutEnd != null && source.LockoutEnd > DateTime.UtcNow ? "Yes" : "No";
}
}
Mapperly 映射类
要使用 Mapperly,您需要为每种源类型和目标类型创建专用的映射类。
- 使用
[Mapper]特性将类指定为 Mapperly 映射器。RequiredMappingStrategy默认设置为Target。 - 将 AutoMapper 的
Ignore()方法替换为[MapperIgnoreTarget]特性。 - 将
MapExtraProperties()方法替换为[MapExtraProperties]特性。 - 使用
TwoWayMapperBase类作为 AutoMapper 的ReverseMap()功能的替代方案。 - 实现
AfterMap()方法以在映射完成后执行逻辑。
映射类中的依赖注入
所有 Mapperly 映射类都会自动注册到依赖注入 (DI) 容器中。要在映射器类中使用服务,只需将其添加到构造函数中;Mapperly 会自动注入它。
示例:
[Mapper(RequiredMappingStrategy = RequiredMappingStrategy.Target)]
public partial class IdentityUserToIdentityUserDtoMapper : MapperBase<IdentityUser, IdentityUserDto>
{
public IdentityUserToIdentityUserDtoMapper(MyService myService)
{
_myService = myService;
}
public override partial IdentityUserDto Map(IdentityUser source);
public override partial void Map(IdentityUser source, IdentityUserDto destination);
public override void AfterMap(IdentityUser source, IdentityUserDto destination)
{
destination.MyProperty = _myService.GetMyProperty(source.MyProperty);
}
}
用于从 AutoMapper 迁移到 Mapperly 的 AI 提示
如果您拥有像 Cursor 这样的 AI 工具,您可以使用以下提示自动将您的 AutoMapper 映射迁移到 Mapperly:
AI 可能会生成一些不正确的代码。请仔细检查代码。
请帮助我将 AutoMapper Profile 类迁移到 Mapperly。我当前的工作空间/上下文中需要转换的 AutoMapper Profile 文件。
**转换要求:**
1. **将 AutoMapper Profile 转换为 Mapperly 映射器**:将每个 `CreateMap` 转换为单独的 Mapperly 映射器类
2. **重命名文件**:从 `XXXAutoMapperProfile.cs` 改为 `XXXMappers.cs`
3. **使用正确的 Mapperly 特性**:
- 为每个映射器类使用 `[Mapper(RequiredMappingStrategy = RequiredMappingStrategy.Target)]`
- 为需要映射额外属性的类使用 `[MapExtraProperties]`
- 为需要忽略的属性使用 `[MapperIgnoreTarget]`
- 为自定义属性映射使用 `[MapProperty]`
4. **继承适当的基类**:
- 单向映射使用 `MapperBase<TSource, TDestination>`
- 反向映射使用 `TwoWayMapperBase<TSource, TDestination>`
5. **处理复杂映射**:对于复杂的转换,使用 `AfterMap` 方法
**注意:** 下面的代码包含两个部分 - 都是供您理解转换模式的参考示例:
1. **AutoMapper Profile 示例** - 显示原始的 AutoMapper 代码结构
2. **Mapperly 映射器示例** - 显示预期的转换后的 Mapperly 代码结构
请转换您当前上下文/工作空间中实际存在的 AutoMapper Profile 文件,遵循与这些示例相同的转换模式。
**参考示例:**
**1. AutoMapper Profile (原始代码):**
using System;
using AutoMapper;
using System.Linq;
using Volo.Abp.AutoMapper;
namespace Volo.Abp.Identity;
public class ExampleAutoMapperProfile : Profile
{
public ExampleAutoMapperProfile()
{
CreateMap<IdentityUser, IdentityUserDto>()
.MapExtraProperties()
.Ignore(x => x.IsLockedOut)
.Ignore(x => x.SupportTwoFactor)
.Ignore(x => x.RoleNames);
CreateMap<IdentityUserClaim, IdentityUserClaimDto>();
CreateMap<OrganizationUnit, OrganizationUnitDto>()
.MapExtraProperties();
CreateMap<OrganizationUnitRole, OrganizationUnitRoleDto>()
.ReverseMap();
CreateMap<IdentityRole, OrganizationUnitRoleDto>()
.ForMember(dest => dest.RoleId, src => src.MapFrom(r => r.Id));
CreateMap<IdentityUser, IdentityUserExportDto>()
.ForMember(dest => dest.Active, src => src.MapFrom(r => r.IsActive ? "Yes" : "No"))
.ForMember(dest => dest.EmailConfirmed, src => src.MapFrom(r => r.EmailConfirmed ? "Yes" : "No"))
.ForMember(dest => dest.TwoFactorEnabled, src => src.MapFrom(r => r.TwoFactorEnabled ? "Yes" : "No"))
.ForMember(dest => dest.AccountLookout, src => src.MapFrom(r => r.LockoutEnd != null && r.LockoutEnd > DateTime.UtcNow ? "Yes" : "No"))
.Ignore(x => x.Roles);
}
}
---
**2. Mapperly 映射器 (转换后的代码):**
using System;
using System.Linq;
using Riok.Mapperly.Abstractions;
using Volo.Abp.Mapperly;
namespace Volo.Abp.Identity;
[Mapper(RequiredMappingStrategy = RequiredMappingStrategy.Target)]
[MapExtraProperties]
public partial class IdentityUserToIdentityUserDtoMapper : MapperBase<IdentityUser, IdentityUserDto>
{
[MapperIgnoreTarget(nameof(IdentityUserDto.IsLockedOut))]
[MapperIgnoreTarget(nameof(IdentityUserDto.SupportTwoFactor))]
[MapperIgnoreTarget(nameof(IdentityUserDto.RoleNames))]
public override partial IdentityUserDto Map(IdentityUser source);
[MapperIgnoreTarget(nameof(IdentityUserDto.IsLockedOut))]
[MapperIgnoreTarget(nameof(IdentityUserDto.SupportTwoFactor))]
[MapperIgnoreTarget(nameof(IdentityUserDto.RoleNames))]
public override partial void Map(IdentityUser source, IdentityUserDto destination);
}
[Mapper(RequiredMappingStrategy = RequiredMappingStrategy.Target)]
public partial class IdentityUserClaimToIdentityUserClaimDtoMapper : MapperBase<IdentityUserClaim, IdentityUserClaimDto>
{
public override partial IdentityUserClaimDto Map(IdentityUserClaim source);
public override partial void Map(IdentityUserClaim source, IdentityUserClaimDto destination);
}
[Mapper(RequiredMappingStrategy = RequiredMappingStrategy.Target)]
[MapExtraProperties]
public partial class OrganizationUnitToOrganizationUnitDtoMapper : MapperBase<OrganizationUnit, OrganizationUnitDto>
{
public override partial OrganizationUnitDto Map(OrganizationUnit source);
public override partial void Map(OrganizationUnit source, OrganizationUnitDto destination);
}
[Mapper(RequiredMappingStrategy = RequiredMappingStrategy.Target)]
public partial class OrganizationUnitRoleToOrganizationUnitRoleDtoMapper : TwoWayMapperBase<OrganizationUnitRole, OrganizationUnitRoleDto>
{
public override partial OrganizationUnitRoleDto Map(OrganizationUnitRole source);
public override partial void Map(OrganizationUnitRole source, OrganizationUnitRoleDto destination);
public override partial OrganizationUnitRole ReverseMap(OrganizationUnitRoleDto destination);
public override partial void ReverseMap(OrganizationUnitRoleDto destination, OrganizationUnitRole source);
}
[Mapper(RequiredMappingStrategy = RequiredMappingStrategy.Target)]
[MapExtraProperties]
public partial class OrganizationUnitToOrganizationUnitWithDetailsDtoMapper : MapperBase<OrganizationUnit, OrganizationUnitWithDetailsDto>
{
[MapperIgnoreTarget(nameof(OrganizationUnitWithDetailsDto.Roles))]
[MapperIgnoreTarget(nameof(OrganizationUnitWithDetailsDto.UserCount))]
public override partial OrganizationUnitWithDetailsDto Map(OrganizationUnit source);
[MapperIgnoreTarget(nameof(OrganizationUnitWithDetailsDto.Roles))]
[MapperIgnoreTarget(nameof(OrganizationUnitWithDetailsDto.UserCount))]
public override partial void Map(OrganizationUnit source, OrganizationUnitWithDetailsDto destination);
}
[Mapper(RequiredMappingStrategy = RequiredMappingStrategy.Target)]
public partial class IdentityRoleToOrganizationUnitRoleDtoMapper : MapperBase<IdentityRole, OrganizationUnitRoleDto>
{
[MapProperty(nameof(IdentityRole.Id), nameof(OrganizationUnitRoleDto.RoleId))]
public override partial OrganizationUnitRoleDto Map(IdentityRole source);
[MapProperty(nameof(IdentityRole.Id), nameof(OrganizationUnitRoleDto.RoleId))]
public override partial void Map(IdentityRole source, OrganizationUnitRoleDto destination);
}
[Mapper(RequiredMappingStrategy = RequiredMappingStrategy.Target)]
public partial class IdentityUserToIdentityUserExportDtoMapper : MapperBase<IdentityUser, IdentityUserExportDto>
{
[MapperIgnoreTarget(nameof(IdentityUserExportDto.Roles))]
public override partial IdentityUserExportDto Map(IdentityUser source);
[MapperIgnoreTarget(nameof(IdentityUserExportDto.Roles))]
public override partial void Map(IdentityUser source, IdentityUserExportDto destination);
public override void AfterMap(IdentityUser source, IdentityUserExportDto destination)
{
destination.Active = source.IsActive ? "Yes" : "No";
destination.EmailConfirmed = source.EmailConfirmed ? "Yes" : "No";
destination.TwoFactorEnabled = source.TwoFactorEnabled ? "Yes" : "No";
destination.AccountLookout = source.LockoutEnd != null && source.LockoutEnd > DateTime.UtcNow ? "Yes" : "No";
}
}
Mapperly 文档
请参考 Mapperly 文档 了解更多详细信息。
关键点:
设置默认映射提供程序
当您的项目包含同时使用 AutoMapper 和 Mapperly 的模块时,您可能需要显式设置默认的 IAutoObjectMappingProvider,以确保应用程序行为一致。
如果您的应用程序使用 AutoMapper:
public override void ConfigureServices(ServiceConfigurationContext context)
{
context.Services.AddAutoMapperObjectMapper();
}
如果您的应用程序使用 Mapperly:
public override void ConfigureServices(ServiceConfigurationContext context)
{
context.Services.AddMapperlyObjectMapper();
}
为什么需要设置默认映射提供程序?
当您的项目包含同时使用 AutoMapper 和 Mapperly 的模块时,AbpAutoMapperModule 和 AbpMapperlyModule 都会被加载。根据您的项目模块结构,它们的依赖加载顺序可能会有所不同,最后加载的模块将覆盖 IAutoObjectMappingProvider 实现。这可能导致意外的行为。显式设置默认提供程序可以确保整个应用程序的映射行为可预测。
抠丁客


