郑心坦 發表於 2025-8-28 16:31:00

如何使用PocoEmit.Mapper替代AutoMapper

<p>PocoEmit使用比较简单对于大部分转化是不需要手动配置<br>
可以替代AutoMapper的大部分工作,实现精简代码,提高性能</p>
<h1 id="一获取mapper实例">一、获取Mapper实例</h1>
<h2 id="1-获取mapper默认实例">1. 获取Mapper默认实例</h2>
<blockquote>
<p>Default是一个静态延迟初始化的默认实例</p>
</blockquote>
<pre><code class="language-csharp">IMapper mapper =PocoEmit.Mapper.Default;
</code></pre>
<h2 id="2-构造mapper新实例">2. 构造Mapper新实例</h2>
<blockquote>
<ul>
<li>除了测试,不建议每次转化调用Create</li>
<li>Create的实例建议定义为静态字段或属性</li>
<li>或者注入IOC容器内重复使用</li>
</ul>
</blockquote>
<pre><code class="language-csharp">IMapper mapper =PocoEmit.Mapper.Create();
</code></pre>
<h1 id="二简单类型转化无需配置">二、简单类型转化无需配置</h1>
<blockquote>
<p>什么样的类型转化对PocoEmit来说是简单的呢</p>
</blockquote>
<h2 id="1-基础类型枚举互转">1. 基础类型、枚举互转</h2>
<blockquote>
<p>基础类型、枚举互转无需配置</p>
</blockquote>
<pre><code class="language-csharp">int intValue =PocoEmit.Mapper.Default.Convert&lt;string, long&gt;("123");
long longValue =PocoEmit.Mapper.Default.Convert&lt;int, long&gt;(123);
strting stringValue =PocoEmit.Mapper.Default.Convert&lt;int, string&gt;(123);
</code></pre>
<pre><code class="language-csharp">public enum MyColor
{
    None = 0,
    Red = 1,
    Green = 2,
    Blue = 3,
}
ConsoleColor color = ConsoleColor.DarkBlue;
// Red
MyColor redColor = PocoEmit.Mapper.Default.Convert&lt;ConsoleColor, MyColor&gt;(ConsoleColor.Red);
string colorName =PocoEmit.Mapper.Default.Convert&lt;ConsoleColor, string&gt;(ConsoleColor.Red);
// 1
long colorValue =PocoEmit.Mapper.Default.Convert&lt;MyColor, long&gt;(MyColor.Red);
/ None
MyColor noneColor = PocoEmit.Mapper.Default.Convert&lt;ConsoleColor, MyColor&gt;(ConsoleColor.DarkBlue);
</code></pre>
<h2 id="2-可空类型转化">2. 可空类型转化</h2>
<blockquote>
<p>可空类型转化无需配置</p>
</blockquote>
<pre><code class="language-csharp">int intValue =PocoEmit.Mapper.Default.Convert&lt;string?, long&gt;("123");
long? longValue =PocoEmit.Mapper.Default.Convert&lt;int, long?&gt;(123);
strting stringValue =PocoEmit.Mapper.Default.Convert&lt;int?, string&gt;(123);
User user = PocoEmit.Mapper.Default.Convert&lt;User?, User&gt;(null);
</code></pre>
<h2 id="3-构造函数和属性互转">3. 构造函数和属性互转</h2>
<blockquote>
<p>构造函数和属性互转无需配置</p>
</blockquote>
<pre><code class="language-csharp">class MyId(int id)
{
    public int Id { get; } = id;
}
class MyId2(int? id)
{
    public int? Id { get; } = id;
}
</code></pre>
<h3 id="31-构造函数转化">3.1 构造函数转化</h3>
<pre><code class="language-csharp">var myId = PocoEmit.Mapper.Default.Convert&lt;int, MyId&gt;(1);
var myId2 = PocoEmit.Mapper.Default.Convert&lt;int?, MyId2&gt;(2);
var myId3 = PocoEmit.Mapper.Default.Convert&lt;int?, MyId&gt;(3);
var myId4 = PocoEmit.Mapper.Default.Convert&lt;int, MyId2&gt;(4);
</code></pre>
<h3 id="32-属性转化">3.2 属性转化</h3>
<pre><code class="language-csharp">var id = PocoEmit.Mapper.Default.Convert&lt;MyId, int&gt;(new MyId(1));
var id2 = PocoEmit.Mapper.Default.Convert&lt;MyId2, int?&gt;(new MyId2(2));
var id3 = PocoEmit.Mapper.Default.Convert&lt;MyId, int?&gt;(new MyId(3));
var id4 = PocoEmit.Mapper.Default.Convert&lt;MyId2, int&gt;(new MyId2(4));
</code></pre>
<h2 id="4-复合类型同名属性互转">4. 复合类型同名属性互转</h2>
<blockquote>
<p>复合类型同名属性互转无需配置</p>
</blockquote>
<pre><code class="language-csharp">var dto =PocoEmit.Mapper.Default.Convert&lt;User, UserDTO&gt;(new User { Id = 1, Name = "Jxj1" });
var user =PocoEmit.Mapper.Default.Convert&lt;UserDTO, User&gt;(new UserDTO { Id = 2, Name = "Jxj2" });
var user3 =PocoEmit.Mapper.Default.Convert&lt;UserDTO2, User&gt;(new UserDTO { Id = "3", Name = "张三" });
</code></pre>
<h1 id="三一键开启集合类型转化配置">三、一键开启集合类型转化配置</h1>
<blockquote>
<ul>
<li>通过UseCollection扩展方法给PocoEmit.Mapper增加集合功能</li>
<li>扩展后PocoEmit.Mapper支持集合(含数组、列表及字典)的转化和复制</li>
<li>支持实体类型包含集合成员的转化和复制</li>
</ul>
</blockquote>
<h2 id="1-启用集合配置">1. 启用集合配置</h2>
<h3 id="12-开启全局集合配置">1.2 开启全局集合配置</h3>
<blockquote>
<ul>
<li>对所有Mapper启用集合</li>
<li>应在使用所有Mapper实例之前配置,对已经完成初始化的Mapper实例无效</li>
</ul>
</blockquote>
<pre><code class="language-csharp">CollectionContainer.GlobalUseCollection();
</code></pre>
<h3 id="11-对单个mapper启用集合">1.1 对单个Mapper启用集合</h3>
<pre><code class="language-csharp">PocoEmit.Mapper.UseCollection();
</code></pre>
<h3 id="2启用集合后集合互转无需配置">2.启用集合后集合互转无需配置</h3>
<pre><code class="language-csharp">User[] source = ;
UserDTO[] result = PocoEmit.Mapper.Default.Convert&lt;User[], UserDTO[]&gt;(source);
</code></pre>
<pre><code class="language-csharp">IEnumerable&lt;User&gt; source = ;
UserDTO[] result = PocoEmit.Mapper.Default.Convert&lt;IEnumerable&lt;User&gt;, UserDTO[]&gt;(source);
</code></pre>
<pre><code class="language-csharp">User[] source = ;
IEnumerable&lt;UserDTO&gt; result = PocoEmit.Mapper.Default.Convert&lt;User[], IEnumerable&lt;UserDTO&gt;&gt;(source);
</code></pre>
<pre><code class="language-csharp">Dictionary&lt;int, User&gt; source = new() { { 1, new User { Id = 1, Name = "Jxj" } } };
UserDTO[] result = PocoEmit.Mapper.Default.Convert&lt;Dictionary&lt;int, User&gt;, UserDTO[]&gt;(source);
</code></pre>
<pre><code class="language-csharp">Dictionary&lt;int, User&gt; source = new() { { 1, new User { Id = 1, Name = "Jxj" } } };
Dictionary&lt;int, UserDTO&gt; result = PocoEmit.Mapper.Default.Convert&lt;Dictionary&lt;int, User&gt;, Dictionary&lt;int, UserDTO&gt;&gt;(source);
</code></pre>
<h3 id="3启用集合后集合成员互转无需配置">3.启用集合后集合成员互转无需配置</h3>
<pre><code class="language-csharp">var source = new UserArray { Name = "VIP", Users = };
UserDTOArray result = PocoEmit.Mapper.Default.Convert&lt;UserArray, UserDTOArray&gt;(source);
</code></pre>
<h2 id="四自定义配置">四、自定义配置</h2>
<h3 id="1-mapper全局配置">1. Mapper全局配置</h3>
<blockquote>
<ul>
<li>全局配置对所有Mapper适用</li>
<li>全局配置应在使用所有Mapper实例之前配置,对已经完成初始化的Mapper实例无效</li>
</ul>
</blockquote>
<h4 id="11-全局配置映射规则">1.1 全局配置映射规则</h4>
<pre><code class="language-csharp">PocoEmit.Mapper.GlobalConfigure(mapper =&gt; {
    mapper.ConfigureMap&lt;User, UserDTO&gt;()
    .Source
    .Ignore(nameof(User.Name));
});
</code></pre>
<h4 id="12-全局配置内部缓存字典大小">1.2 全局配置内部缓存字典大小</h4>
<blockquote>
<ul>
<li>配置适当大小可以减少内存占用和扩容</li>
</ul>
</blockquote>
<pre><code class="language-csharp">PocoEmit.Mapper.GlobalOptions(options =&gt; {
    // 转化器数量
    options.ConverterCapacity = 100;
});
</code></pre>
<h3 id="2-配置单个mapper">2. 配置单个Mapper</h3>
<pre><code class="language-csharp">PocoEmit.Mapper.Default.ConfigureMap&lt;User, UserDTO&gt;()
    .Source
    .Ignore(nameof(User.Name));
</code></pre>
<h3 id="3-哪些需要配置">3. 哪些需要配置</h3>
<h4 id="31-属性名前后缀">3.1 属性名前、后缀</h4>
<blockquote>
<ul>
<li>AddPrefix设置前缀</li>
<li>AddSuffix设置后缀</li>
<li>ClearPrefix清空前缀</li>
</ul>
</blockquote>
<pre><code class="language-csharp">public class UserCustomDTO(string userName)
{
    public int? UId { get; set; }
    public string UName { get; } = userName;
}
</code></pre>
<h5 id="311-源类型设置前缀">3.1.1 源类型设置前缀</h5>
<pre><code class="language-csharp">IMapper mapper = Mapper.Create();
mapper.ConfigureMap&lt;UserCustomDTO, User&gt;()
    .Source
    .AddPrefix("U");
var source = new UserCustomDTO("Jxj2") { UId = 222 };
var converter = mapper.GetConverter&lt;UserCustomDTO, User&gt;();
User result = converter.Convert(source);
</code></pre>
<h5 id="312-目标类型设置前缀">3.1.2 目标类型设置前缀</h5>
<pre><code class="language-csharp">IMapper mapper = Mapper.Create();
mapper.ConfigureMap&lt;User, UserCustomDTO&gt;()
    .Dest
    .AddPrefix("U");
var source = new User { Id = 222, Name = "Jxj2" };
var result = mapper.Convert&lt;User, UserCustomDTO&gt;(source);
</code></pre>
<h5 id="313-默认前缀">3.1.3 默认前缀</h5>
<blockquote>
<ul>
<li>ConfigureMap会默认把源类型名作为目标类型前缀</li>
<li>ConfigureMap会默认把目标类型名作为源类型前缀</li>
<li>如果默认前缀干扰到正常匹配,可以调用ClearPrefix清空前缀</li>
</ul>
</blockquote>
<pre><code class="language-csharp">public class AutoUserDTO
{
    public string UserId { get; set; }
    public string UserName { get; set; }
}
IMapper mapper = Mapper.Create();
mapper.ConfigureMap&lt;AutoUserDTO, User&gt;();
var source = new AutoUserDTO{ UserId = "222", UserName = "Jxj"};
var converter = mapper.GetConverter&lt;AutoUserDTO, User&gt;();
User result = converter.Convert(source);
</code></pre>
<h4 id="32-属性一对一配置">3.2 属性一对一配置</h4>
<h5 id="321-通过source配置">3.2.1 通过Source配置</h5>
<blockquote>
<ul>
<li>直接MapTo或ForMember</li>
</ul>
</blockquote>
<pre><code class="language-csharp">IMapper mapper = Mapper.Create();
mapper.ConfigureMap&lt;User, UserCustomDTO&gt;()
    .Source
    .MapTo(nameof(User.Id), nameof(UserCustomDTO.UId))
    .MapTo(nameof(User.Name), nameof(UserCustomDTO.UName));
var source = new User { Id = 222, Name = "Jxj2" };
var converter = mapper.GetConverter&lt;User, UserCustomDTO&gt;();
UserCustomDTO result = converter.Convert(source);
</code></pre>
<pre><code class="language-csharp">IMapper mapper = Mapper.Create();
mapper.ConfigureMap&lt;User, UserCustomDTO&gt;()
    .Source
    .ForMember(nameof(User.Id)).MapTo(nameof(UserCustomDTO.UId))
    .ForMember(nameof(User.Name)).MapTo(nameof(UserCustomDTO.UName));
var source = new User { Id = 222, Name = "Jxj2" };
var converter = mapper.GetConverter&lt;User, UserCustomDTO&gt;();
UserCustomDTO result = converter.Convert(source);
</code></pre>
<h5 id="322-通过dest配置">3.2.2 通过Dest配置</h5>
<blockquote>
<ul>
<li>直接MapFrom或ForMember</li>
</ul>
</blockquote>
<pre><code class="language-csharp">IMapper mapper = Mapper.Create();
mapper.ConfigureMap&lt;UserCustomDTO, User&gt;()
    .Dest
    .MapFrom(nameof(User.Id), nameof(UserCustomDTO.UId))
    .MapFrom(nameof(User.Name), nameof(UserCustomDTO.UName));
var source = new UserCustomDTO("Jxj2") { UId = 222 };
var converter = mapper.GetConverter&lt;UserCustomDTO, User&gt;();
User result = converter.Convert(source);
</code></pre>
<pre><code class="language-csharp">IMapper mapper = Mapper.Create();
mapper.ConfigureMap&lt;UserCustomDTO, User&gt;()
    .Dest
    .ForMember(nameof(User.Id)).MapFrom(nameof(UserCustomDTO.UId))
    .ForMember(nameof(User.Name)).MapFrom(nameof(UserCustomDTO.UName));
var source = new UserCustomDTO("Jxj2") { UId = 222 };
var converter = mapper.GetConverter&lt;UserCustomDTO, User&gt;();
User result = converter.Convert(source);
</code></pre>
<h4 id="33-忽略成员">3.3 忽略成员</h4>
<h4 id="331-被忽略的源类型成员不参与映射">3.3.1 被忽略的源类型成员不参与映射</h4>
<pre><code class="language-csharp">IMapper mapper = Mapper.Create();
mapper.ConfigureMap&lt;User, UserDTO&gt;()
    .Source
    .Ignore(nameof(User.Name));
var source = new User { Id = 111, Name = "Jxj" };
var converter = mapper.GetConverter&lt;User, UserDTO&gt;();
// result.Name == null
UserDTO result = converter.Convert(source);
</code></pre>
<h4 id="332-被忽略的目标类型成员不会被匹配">3.3.2 被忽略的目标类型成员不会被匹配</h4>
<pre><code class="language-csharp">IMapper mapper = Mapper.Create();
mapper.ConfigureMap&lt;User, UserDTO&gt;()
    .Dest
    .Ignore(nameof(UserDTO.Name));
var source = new User { Id = 111, Name = "Jxj" };
var converter = mapper.GetConverter&lt;User, UserDTO&gt;();
// result.Name == null
UserDTO result = converter.Convert(source);
</code></pre>
<h4 id="34-指定转化函数">3.4 指定转化函数</h4>
<blockquote>
<ul>
<li>通过UseConvertFunc指定函数直接转化</li>
</ul>
</blockquote>
<pre><code class="language-csharp">IMapper mapper = Mapper.Create();
mapper.ConfigureMap&lt;UserDTO, User&gt;()
    .UseConvertFunc(source =&gt; new User { Id= source.Id, Name = source.Name });
var source = new UserDTO { Id = 111, Name = "Jxj" };
var converter = mapper.GetConverter&lt;UserDTO, User&gt;();
var result = converter.Convert(source);
</code></pre>
<h4 id="35-指定构造函数">3.5 指定构造函数</h4>
<blockquote>
<ul>
<li>通过UseActivator指定构造函数</li>
<li>执行完构造函数还会尝试匹配与UseConvertFunc是不同的</li>
</ul>
</blockquote>
<pre><code class="language-csharp">IMapper mapper = Mapper.Create();
mapper.ConfigureMap&lt;User, UserCustomDTO&gt;()
    .UseActivator(u =&gt; new UserCustomDTO(u.Name) { UId = u.Id })
    .Source
    .MapTo(nameof(User.Id), nameof(UserCustomDTO.UId));
var source = new User { Id = userId, Name = "Jxj2" };
var converter = mapper.GetConverter&lt;User, UserCustomDTO&gt;();
var result = converter.Convert(source);
</code></pre>
<h4 id="36-指定默认值">3.6 指定默认值</h4>
<h5 id="361-指定固定默认值">3.6.1 指定固定默认值</h5>
<pre><code class="language-csharp">IMapper mapper = Mapper.Create()
    .UseDefault(Repository);
var dto = new UserDTO { Id = 1, Name = "Jxj" };
UserDomain user = mapper.Convert&lt;UserDTO, UserDomain&gt;(dto);

class UserDomain(UserRepository repository, int id, string name)
{
    private readonly UserRepository _repository = repository;
    public UserRepository Repository
      =&gt; _repository;
    public int Id { get; } = id;
    public string Name { get; } = name;
    // ...
}
static readonly UserRepository Repository = new();
class UserRepository
{
    void Add(UserDomain user) { }
    void Update(UserDomain entity) { }
    void Remove(UserDomain entity) { }
}
</code></pre>
<blockquote>
<ul>
<li>注: 如果不指定UserRepository的默认值,以上转化会报异常</li>
<li>注: 以上例子示例给领域模型注入仓储</li>
</ul>
</blockquote>
<h5 id="362-默认值指定工厂方法">3.6.2 默认值指定工厂方法</h5>
<pre><code class="language-csharp">IMapper mapper = Mapper.Create()
    .UseDefault(() =&gt; MessageId.NewId());
var dto = new MessageDto { Message = "Hello UseDefault" };
MessageDomain message = mapper.Convert&lt;MessageDto, MessageDomain&gt;(dto);

class MessageDto
{
    public string Message { get; set; }
}
class MessageDomain(MessageId id, string message)
{
    public MessageId Id { get; } = id;
    public string Message { get; } = message;
    // ...
}
class MessageId(int id)
{
    private static int seed = 1;
    public int Id { get; } = id;
    public static MessageId NewId()
      =&gt; new(seed++);
}
</code></pre>
<blockquote>
<ul>
<li>注: 以上例子示例给领域模型注入自增Id</li>
<li>注: 实际项目这里建议调用雪花Id</li>
</ul>
</blockquote>
<h4 id="37-转化后补刀">3.7 转化后补刀</h4>
<blockquote>
<ul>
<li>使用UseCheckAction配置转化检查逻辑用来补刀</li>
<li>对于实在不方便配置映射规则的遗留属性进行补刀</li>
</ul>
</blockquote>
<pre><code class="language-csharp">static void ConvertAddressCity(Customer customer, CustomerDTO dto)
{
    dto.AddressCity = customer.Address.City;
}
var mapper = PocoEmit.Mapper.Default;
mapper.ConfigureMap&lt;Customer, CustomerDTO&gt;()
    .UseCheckAction(ConvertAddressCity);
CustomerDTO result = mapper.Convert&lt;Customer, CustomerDTO&gt;(source);
</code></pre>
<h1 id="五pocoemit的继承和多态">五、PocoEmit的“继承”和“多态”</h1>
<h2 id="1-pocoemit的继承">1. PocoEmit的“继承”</h2>
<blockquote>
<ul>
<li>通过全局配置实现类似继承的效果</li>
<li>前面已经介绍了PocoEmit的全局配置</li>
</ul>
</blockquote>
<h2 id="2-pocoemit的多态">2. PocoEmit的“多态”</h2>
<blockquote>
<ul>
<li>对某些Mapper对象覆盖配置实现与其他Mapper不同的效果</li>
<li>可以用不同的serviceKey注入容器,使用的时候就可以做到无感</li>
</ul>
</blockquote>
<h3 id="21-举个发布平台需要多态的例子">2.1 举个发布平台需要“多态”的例子</h3>
<blockquote>
<ul>
<li>需要发布时间字段,平台内时间格式很好统一</li>
<li>如果需要接入第三方数据,特别是国外的数据,格式很可能不一样</li>
<li>牺牲性能用复杂逻辑去兼容也不是不可以</li>
<li>这里建议提供多个Mapper的方案,不同渠道用不同的Mapper,每个Mapper只有极少的配置不同</li>
<li>这就是PocoEmit“多态”的应用</li>
</ul>
</blockquote>
<h3 id="22-再举个vip的例子更明显">2.2 再举个VIP的例子更明显</h3>
<blockquote>
<ul>
<li>VIP客户提出各种格式化无关痛痒的问题</li>
<li>一般就写if特殊处理了</li>
<li>这类情况也适用PocoEmit“多态”</li>
</ul>
</blockquote>
<h1 id="六pocoemit配合容器使用">六、PocoEmit配合容器使用</h1>
<h2 id="1容器注册转化器">1、容器注册转化器</h2>
<h3 id="11-默认注册">1.1 默认注册</h3>
<blockquote>
<p>通过容器中默认的IMapper对象或Mapper.Global构造转化器</p>
</blockquote>
<pre><code class="language-csharp">services.UseConverter();
</code></pre>
<h3 id="12-指定ipoco对象注册">1.2 指定IPoco对象注册</h3>
<pre><code class="language-csharp">services.UseConverter(PocoEmit.Mapper.Global);
</code></pre>
<blockquote>
<p>注: PocoEmit.Mapper.Global继承IPoco接口</p>
</blockquote>
<h3 id="13-隔离注册">1.3 隔离注册</h3>
<blockquote>
<p>指定IPoco和serviceKey注册</p>
</blockquote>
<pre><code class="language-csharp">IPoco poco = specialMapper;
services.UseConverter(poco, "special");
</code></pre>
<h3 id="14-通过ipococonverter注入">1.4 通过IPocoConverter注入</h3>
<blockquote>
<p>通过构造函数参数、属性等方式注入</p>
</blockquote>
<pre><code class="language-csharp">public sealed class Mapper(IPocoConverter&lt;User, UserListDTO&gt; converter)
    : Mapper&lt;Request, Response, IEnumerable&lt;User&gt;&gt;
{
    // ...
}
</code></pre>
<h2 id="2容器注册复制器">2、容器注册复制器</h2>
<h3 id="21-默认注册">2.1 默认注册</h3>
<blockquote>
<p>通过容器中默认的IMapper对象或Mapper.Global构造复制器</p>
</blockquote>
<pre><code class="language-csharp">services.UseCopier();
</code></pre>
<h3 id="22-指定imapper对象注册">2.2 指定IMapper对象注册</h3>
<pre><code class="language-csharp">services.UseCopier(PocoEmit.Mapper.Global);
</code></pre>
<blockquote>
<p>注: PocoEmit.Mapper.Global继承IMapper接口</p>
</blockquote>
<h3 id="23-隔离注册">2.3 隔离注册</h3>
<blockquote>
<p>指定IPoco和serviceKey注册</p>
</blockquote>
<pre><code class="language-csharp">IPoco poco = specialMapper;
services.UseCopier(poco, "special");
</code></pre>
<h3 id="24-通过ipococopier注入">2.4 通过IPocoCopier注入</h3>
<blockquote>
<p>通过构造函数参数、属性等方式注入</p>
</blockquote>
<pre><code class="language-csharp">public sealed class Mapper(IPocoCopier&lt;User, UserListDTO&gt; copier)
    : Mapper&lt;Request, Response, IEnumerable&lt;User&gt;&gt;
{
    // ...
}
</code></pre>
<p>源码托管地址: https://github.com/donetsoftwork/MyEmit ,也欢迎大家直接查看源码。<br>
gitee同步更新:https://gitee.com/donetsoftwork/MyEmit</p>
<p>如果大家喜欢请动动您发财的小手手帮忙点一下Star。</p><br><br>
来源:https://www.cnblogs.com/xiangji/p/19062936
頁: [1]
查看完整版本: 如何使用PocoEmit.Mapper替代AutoMapper