开源项目PocoEmit.Mapper重构之扑风捉影

一、投影技术

  • 这里的投影可以实现类似sql投影的效果
  • 也可以类比光线照射在物体上形成的影像

1. 投影接口定义

  • IProjection接口很简单
  • 只有TryConvert一个方法
  • 相同类型对象的转化,返回是否成功以及转化后的结果
  • 约定不符合规则就不转化
  • 一般情况下转化成功的结果会和源对象不同
  • 当然经过多次投影后也可能会和源对象相同了
csharp 复制代码
public interface IProjection<T>
{
    bool TryConvert(T source, out T result);
}

2. 三种投影方式

2.1 使用前缀投影来演示

  • 前缀投影就是把成员前面加个前缀来映射
  • sourceMembers模拟对User的反射
  • 通过Projection.Prefix("User")创建一个前缀投影
csharp 复制代码
public record User(int Id, string UserName);
public record UserDTO(int UserId, string UserName);
var sourceMembers = new Dictionary<string, Func<User, object>>()
{
    [nameof(User.Id)] = obj => obj.Id,
    [nameof(User.UserName)] = obj => obj.UserName
};
var projection = Projection.Prefix("User");

2.2 Filter投影的Case

2.2.1 csharp代码
csharp 复制代码
IDictionary<string, Func<User, object>> result = projection.Filter(sourceMembers);
Assert.Single(result);
Assert.True(result.ContainsKey(nameof(UserDTO.UserId)));
// 相当于sql: SELECT Id AS UserId FROM User
2.2.2 sql表示
sql 复制代码
SELECT Id AS UserId FROM User
2.2.3 影像表示

graph LR subgraph User Id UserName end Id -->|Filter| UserId

2.3 Through投影的Case

2.3.1 csharp代码
csharp 复制代码
IDictionary<string, Func<User, object>> result = projection.Through(sourceMembers);
Assert.Equal(sourceMembers.Count, result.Count);
Assert.True(result.ContainsKey(nameof(UserDTO.UserId)));
Assert.True(result.ContainsKey(nameof(UserDTO.UserName)));
2.3.2 sql表示
sql 复制代码
SELECT Id AS UserId,UserName FROM User
2.3.3 影像表示

graph LR subgraph User User-Id[Id] User-UserName[UserName] end User-Id -->|Through| UserId User-UserName -->|Through| UserName

2.4 Cross投影的Case

  • PocoEmit.Mapper重构用的就是Cross投影
2.4.1 csharp代码
csharp 复制代码
IDictionary<string, Func<User, object>> result = projection.Cross(sourceMembers);
Assert.Equal(3, result.Count);
Assert.True(result.ContainsKey(nameof(User.Id)));
Assert.True(result.ContainsKey(nameof(UserDTO.UserId)));
Assert.True(result.ContainsKey(nameof(UserDTO.UserName)));
2.4.2 sql表示
sql 复制代码
SELECT Id,UserName,Id AS UserId FROM User
2.4.3 影像表示

graph LR subgraph User User-Id[Id] User-UserName[UserName] end User-Id -->|Cross| Id User-UserName -->|Cross| UserName User-Id -->|Cross| UserId

3. 投影支持"横向"扩展

  • 这里说的"横向"就是投影并联
  • 把多个投影组合成多分支的映射规则
  • 通过FirstReturn或ToFirstReturn方法实现

3.1 "横向"扩展的Case

csharp 复制代码
// 包含User或U前缀就去掉前缀
var user = Projection.RemovePrefix("User");
var u = Projection.RemovePrefix("U");
var projection = Projection.FirstReturn(user, u);

[Theory]
[InlineData("UserId", "Id")]
[InlineData("UId", "Id")]
[InlineData("UUserName", "UserName")]
[InlineData("UserUName", "UName")]
public void TryConvert(string source, string expected)
{
    projection.TryConvert(source, out var result);
    Assert.Equal(expected, result);
}

3.2 影像表示"横向"扩展

  • RemoveUser和RemoveU两个并联的投影
  • 并联投影按照顺序依次尝试
  • 直到有一个投影成功了就返回结果
    graph LR subgraph source source-UserId[UserId] source-UId[UId] source-UUserName[UUserName] source-UserUName[UserUName] end subgraph expected expected-Id[Id] expected-UserName[UserName] expected-UName[UName] end source-UserId -->|RemoveUser| expected-Id source-UId -->|RemoveU| expected-Id source-UUserName -->|RemoveU| expected-UserName source-UserUName -->|RemoveUser| expected-UName

二、投影在PocoEmit.Mapper中的应用

1. AddPrefix

1.1 AddPrefix的Case

csharp 复制代码
IMapper mapper = Mapper.Create();
mapper.ConfigureMap<AutoUserDTO, User>()
    .Source
    .AddPrefix("User");
var source = new AutoUserDTO { UserId = "222", UserName = "Jxj2" };
var converter = mapper.GetConverter<AutoUserDTO, User>();
var result = converter.Convert(source);
Assert.NotNull(result);
Assert.Equal(source.UserId, result.Id.ToString());
Assert.Equal(source.UserName, result.UserName);

1.2 AddPrefix使用的是去除前缀投影(RemovePrefix)

  • AddPrefix("User")会调用Projection.RemovePrefix("User")来创建一个去除前缀投影
  • AutoUserDTO两个成员分别是UserId和UserName
  • User两个成员分别是Id和UserName
  • 对AutoUserDTO投影的效果相当于sql: SELECT UserId,UserName,UserId AS Id FROM AutoUserDTO
  • 通过这个投影,两边的成员名就能完美匹配上了

1.3 影像演示一下这个过程

graph LR subgraph AutoUserDTO source-UserId[UserId] source-UserName[UserName] end subgraph AutoUserDTO投影 projection-UserId[UserId] projection-UserName[UserName] projection-Id[Id] projection-Name[Name] end subgraph User result-Id[Id] result-UserName[UserName] end source-UserId -->|RemoveUser| projection-Id source-UserName -->|RemoveUser| projection-Name source-UserId -->| | projection-UserId source-UserName -->| | projection-UserName result-Id -->| Mapping | projection-Id result-UserName -->| Mapping | projection-UserName

1.4 使用前缀投影(Prefix)也可以实现类似的效果

  • 通过Projection.Prefix("User")创建一个前缀投影
  • 这时就需要对User进行投影
  • 对User投影的效果相当于sql: SELECT Id,UserName,Id AS UserId FROM User
  • PocoEmit.Mapper使用RemovePrefix是为了投影的成员名更短,更容易匹配上
1.4.1 影像演示一下这个过程

graph LR subgraph AutoUserDTO source-UserId[UserId] source-UserName[UserName] end subgraph User result-Id[Id] result-UserName[UserName] end subgraph User投影 projection-Id[Id] projection-UserName[UserName] projection-UserId[UserId] end result-Id -->| | projection-Id result-UserName -->| | projection-UserName result-Id -->|AddUser| projection-UserId projection-UserId -->|Mapping| source-UserId projection-UserName -->|Mapping| source-UserName

2. AddPrefix替换前缀重载

2.1 AddPrefix替换前缀重载的Case

  • AddPrefix("User", "U")会调用Projection.ReplacePrefix("User", "U")
  • 创建了一个替换前缀投影
csharp 复制代码
IMapper mapper = Mapper.Create();
mapper.ConfigureMap<AutoUserDTO, UserCustomDTO>()
    .Source
    .AddPrefix("User", "U");
var source = new AutoUserDTO { UserId = "555", UserName = "Jxj5" };
var converter = mapper.GetConverter<AutoUserDTO, UserCustomDTO>();
var result = converter.Convert(source);
Assert.NotNull(result);
Assert.Equal(source.UserId, result.UId.ToString());
Assert.Equal(source.UserName, result.UName);

2.2 影像演示一下这个过程

graph LR subgraph AutoUserDTO source-UserId[UserId] source-UserName[UserName] end subgraph AutoUserDTO投影 projection-UserId[UserId] projection-UserName[UserName] projection-UId[UId] projection-UName[UName] end subgraph UserCustomDTO result-UId[UId] result-UName[UName] end source-UserId -->| | projection-UserId source-UserName -->| | projection-UserName source-UserId -->|UserToU| projection-UId source-UserName -->|UserToU| projection-UName result-UId --> |Mapping| projection-UId result-UName --> |Mapping| projection-UName

2.3 对UserCustomDTO替换的Case

  • 对UserCustomDTO投影的效果相当于sql: SELECT UId,UName,UId AS UserId,UName AS UserName FROM UserCustomDTO
  • 通过这个投影,两边的成员名也能完美匹配上了
csharp 复制代码
IMapper mapper = Mapper.Create();
mapper.ConfigureMap<AutoUserDTO, UserCustomDTO>()
    .Dest
    .AddPrefix("U", "User");
var source = new AutoUserDTO { UserId = "555", UserName = "Jxj5" };
var converter = mapper.GetConverter<AutoUserDTO, UserCustomDTO>();
var result = converter.Convert(source);
Assert.NotNull(result);
Assert.Equal(source.UserId, result.UId.ToString());
Assert.Equal(source.UserName, result.UName);

2.4 UserCustomDTO替换的影像

graph LR subgraph AutoUserDTO source-UserId[UserId] source-UserName[UserName] end subgraph UserCustomDTO result-UId[UId] result-UName[UName] end subgraph UserCustomDTO投影 projection-UserId[UserId] projection-UserName[UserName] projection-UId[UId] projection-UName[UName] end result-UId -->| | projection-UId result-UName -->| | projection-UName result-UId -->| UToUser | projection-UserId result-UName -->| UToUser | projection-UserName projection-UserId -->|Mapping| source-UserId projection-UserName -->|Mapping| source-UserName

3. AddSuffix

3.1 AddSuffix替换后缀的Case

  • AddSuffix("y", "ies")会调用Projection.ReplaceSuffix("y", "ies")
  • 创建一个替换后缀投影
  • 常用于单复数拼写转化
  • 对Customer投影的效果相当于sql: SELECT Name,City,Name AS CustomerName,City AS CustomerCities FROM Customer
  • ConfigureMap方法默认会把源类名作为目标的前缀,也把目标类名作为源的前缀,
  • 所以在这个例子里没有显示调用AddPrefix,Customer前缀自动生效了
  • 如果不需要默认规则可以通过参数autoRecognize设置为false禁用掉
  • AddSuffix也支持去掉后缀的重载与AddPrefix类似,用法简单,这里就不展示了
csharp 复制代码
IMapper mapper = Mapper.Create();
mapper.ConfigureMap<Customer, CustomerDTO>()
    .Source
    .AddSuffix("y", "ies");
var source = new Customer("Jxj", "北京");
var converter = mapper.GetConverter<Customer, CustomerDTO>();
var result = converter.Convert(source);
Assert.NotNull(result);
Assert.Equal(source.Name, result.CustomerName);
Assert.Equal(source.City, result.CustomerCities);

3.2 AddSuffix替换后缀的影像

  • 上面文字描述的内容比较抽象
  • 大家看以下影像就清楚了
  • 借助强大的投影能力
  • Mapping就很简单了,直接同名映射(一般忽略大小写,是否忽略大小写可以配置)
    graph LR subgraph Customer source-Name[Name] source-City[City] end subgraph Customer投影 projection-Name[Name] projection-City[City] projection-Cities[Cities] end subgraph CustomerDTO投影 projection2-CustomerName[CustomerName] projection2-CustomerCities[CustomerCities] projection2-Name[Name] projection2-Cities[Cities] end subgraph CustomerDTO result-CustomerName[CustomerName] result-CustomerCities[CustomerCities] end source-Name -->| | projection-Name source-City -->| | projection-City source-City -->|yToies| projection-Cities result-CustomerName -->| | projection2-CustomerName result-CustomerCities -->| | projection2-CustomerCities result-CustomerName -->| RemoveCustomer | projection2-Name result-CustomerCities -->|RemoveCustomer| projection2-Cities projection2-Name -->|Mapping| projection-Name projection2-Cities -->|Mapping| projection-Cities

4. AddProjection

4.1 AddProjection的Case

  • Projection.Replace("Nume", "Name")创建替换"Nume"为"Name"的投影
  • 通过AddProjection引用投影,来实现更个性化的映射规则
  • 实现了可扩展的映射规则,不仅限于前缀或后缀的规则
csharp 复制代码
IMapper mapper = Mapper.Create();
mapper.ConfigureMap<ProductJson, Product>()
    .Source
    .AddProjection(Projection.Replace("Nume", "Name"));
var source = new ProductJson { CityNume = "北京", ProductNume = "手机" };
var converter = mapper.GetConverter<ProductJson, Product>();
var result = converter.Convert(source);
Assert.NotNull(result);
Assert.Equal(source.CityNume, result.CityName);
Assert.Equal(source.ProductNume, result.ProductName);

4.2 AddProjection的影像

graph LR subgraph ProductJson source-CityNume[CityNume] source-ProductNume[ProductNume] end subgraph ProductJson投影 projection-CityNume[CityNume] projection-ProductNume[ProductNume] projection-CityName[CityName] projection-ProductName[ProductName] end subgraph Product result-CityName[CityName] result-ProductName[ProductName] end source-CityNume -->| | projection-CityNume source-ProductNume -->| | projection-ProductNume source-CityNume -->|NumeToName| projection-CityName source-ProductNume -->|NumeToName| projection-ProductName result-CityName -->|Mapping| projection-CityName result-ProductName -->|Mapping| projection-ProductName

三、总结

  • 借助投影技术极大的增强了PocoEmit.Mapper的映射能力
  • 另外还增加了投影的可扩展性
  • 用户可以根据需要创建自己的投影来实现个性化的映射规则
  • 更多PocoEmit.Mapper可以查看本合集其他文章
  • dotnet add package PocoEmit.Mapper --version 0.8.8.1-alpha

投影库的源码地址:

github: https://github.com/donetsoftwork/HandCore.net/tree/master/Hand.Projections

gitee同步更新:https://gitee.com/donetsoftwork/HandCore.net/tree/master/Hand.Projections

PocoEmit.Mapper源码地址

github: https://github.com/donetsoftwork/MyEmit/tree/main/PocoEmit.Mapper

gitee同步更新:https://gitee.com/donetsoftwork/MyEmit/tree/main/PocoEmit.Mapper

感兴趣的同学可以去看看源码,欢迎star和pr

相关推荐
曲幽5 天前
Vue 3 组件通信,别只会用 Props 和 Emits 了,这几个狠活儿你得看看
vue3·inject·provide·pinia·v-model·props·mitt·emit
qq_283720051 个月前
WebGL基础教程(十四):投影矩阵深度解析——正交 vs 透视,从公式推导到实战
线性代数·矩阵·webgl·正交·投影
xiangji3 个月前
DBShadow.net之依赖注入
orm·异步·mapper·sqlbuilder
xiangji3 个月前
DBShadow.net之化繁为简
orm·mapper·sqlbuilder
charlee443 个月前
《实时渲染》第2章-图形渲染管线-2.3几何处理
投影·裁剪·几何处理·顶点着色·屏幕映射
hqwest3 个月前
码上通QT实战15--监控页面07-打开串口连接
开发语言·qt·多线程·signal·slot·emit·信号和槽
hqwest3 个月前
码上通QT实战08--导航按钮切换界面
开发语言·qt·slot·信号与槽·connect·signals·emit
七夜zippoe4 个月前
Spring与MyBatis整合原理及事务管理
java·spring·mybatis·事务·mapper
七夜zippoe4 个月前
MyBatis核心源码解析 从SqlSession到Mapper接口的绑定过程
java·mybatis·mapper·sqlsession·缓存机制