网站首页 网站源码
website
站点相关全部源代码,隐藏了一些关于服务器的信息

自动映射类型实现方案指南

本文档提供了几种优雅的自动映射类型实现方案,解决了原始 ServiceMapper 不支持有参数构造函数的问题。

问题分析

原始的 ServiceMapper 使用 Activator.CreateInstance(x) 来创建实例,这种方式:

  • 只支持无参构造函数
  • 无法处理需要依赖注入的类型
  • 实例化过程不够优雅

解决方案

方案一:基于静态方法的映射配置

适用场景: C# 11+ 项目,不需要依赖注入

public class ProductViewModel : IHaveStaticMapping
{
    public string Name { get; set; }
    public decimal Price { get; set; }

    public static void CreateMappings(MapperConfigurationExpression cfg)
    {
        cfg.CreateMap<ProductEntity, ProductViewModel>();
        cfg.CreateMap<ProductViewModel, ProductEntity>();
    }
}

优点:

  • 无需实例化对象
  • 编译时类型安全
  • 性能最优

缺点:

  • 需要 C# 11+ 支持
  • 无法使用依赖注入

方案二:基于特性的映射配置

适用场景: 简单映射关系,声明式配置

[AutoMap(typeof(UserEntity), typeof(UserViewModel))]
[CustomMap(typeof(UserEntity), typeof(UserDetailViewModel), nameof(ConfigureUserDetailMapping))]
public class UserViewModel : IMapFrom<UserEntity>
{
    public string Name { get; set; }
    public string Email { get; set; }

    public static void ConfigureUserDetailMapping(MapperConfigurationExpression cfg)
    {
        cfg.CreateMap<UserEntity, UserDetailViewModel>()
            .ForMember(dest => dest.DisplayName, opt => opt.MapFrom(src => $"{src.FirstName} {src.LastName}"));
    }
}

优点:

  • 声明式配置,一目了然
  • 支持简单和复杂映射
  • 无需实例化

缺点:

  • 特性参数有限制
  • 复杂逻辑需要额外方法

方案三:映射配置文件(推荐)

适用场景: 现代项目,需要依赖注入,复杂映射逻辑

public class OrderMappingProfile : MappingProfile
{
    private readonly IDateTimeService _dateTimeService;

    public OrderMappingProfile(IDateTimeService dateTimeService)
    {
        _dateTimeService = dateTimeService;
        CreateMappings();
    }

    // 备用无参构造函数
    public OrderMappingProfile() : this(new DefaultDateTimeService()) { }

    private void CreateMappings()
    {
        CreateMap<OrderEntity, OrderViewModel>()
            .ForMember(dest => dest.OrderDate, 
                opt => opt.MapFrom(src => _dateTimeService.ToLocalTime(src.CreatedAt)));
    }
}

优点:

  • 完全支持依赖注入
  • 灵活的映射配置
  • 易于测试和维护
  • 符合 SOLID 原则

缺点:

  • 需要更多代码

方案四:智能实例创建工厂

适用场景: 兼容现有代码,渐进式升级

// 在 ImprovedServiceMapper 中使用
private object? CreateInstanceWithFactory(Type type)
{
    // 1. 尝试无参构造函数
    try { return Activator.CreateInstance(type); }
    catch { /* 继续尝试其他方法 */ }

    // 2. 尝试最少参数的构造函数
    var constructors = type.GetConstructors().OrderBy(c => c.GetParameters().Length);
    foreach (var constructor in constructors)
    {
        try
        {
            var args = constructor.GetParameters()
                .Select(p => GetDefaultValue(p.ParameterType))
                .ToArray();
            return Activator.CreateInstance(type, args);
        }
        catch { /* 尝试下一个构造函数 */ }
    }

    // 3. 使用 FormatterServices(不调用构造函数)
    return FormatterServices.GetUninitializedObject(type);
}

使用建议

新项目推荐方案

  1. 首选: 映射配置文件 + 依赖注入
  2. 备选: 静态接口映射(如果不需要依赖注入)

现有项目升级路径

  1. 第一步: 使用 ImprovedServiceMapper 替换现有的 ServiceMapper
  2. 第二步: 逐步将复杂映射迁移到映射配置文件
  3. 第三步: 引入依赖注入支持

依赖注入配置

// 在 Startup.cs 或 Program.cs 中
services.AddAutoMapper(); // 使用扩展方法自动注册

// 或手动注册
services.AddSingleton<DependencyInjectionServiceMapper>();
services.AddSingleton<IMapper>(provider => 
    provider.GetRequiredService<DependencyInjectionServiceMapper>().Mapper);

// 注册映射配置文件
services.AddTransient<OrderMappingProfile>();
services.AddTransient<UserMappingProfile>();

性能对比

方案初始化性能运行时性能内存占用
静态接口最优最优最少
特性配置优秀优秀
配置文件良好优秀中等
智能工厂一般优秀中等

最佳实践

  1. 类型安全: 优先使用编译时类型检查的方案
  2. 职责分离: 复杂映射逻辑单独封装
  3. 测试友好: 映射配置应该易于单元测试
  4. 文档化: 复杂映射规则要有清晰的注释
  5. 渐进式: 可以混合使用多种方案,逐步迁移

总结

推荐使用映射配置文件方案,它提供了最好的灵活性和可维护性。对于简单场景,静态接口映射也是不错的选择。智能工厂方案可以作为过渡方案,帮助现有代码平滑升级。

loading