开源项目PocoEmit.Mapper重构之扑风捉影
一、投影技术
- 这里的投影可以实现类似sql投影的效果
- 也可以类比光线照射在物体上形成的影像
1. 投影接口定义
- IProjection接口很简单
- 只有TryConvert一个方法
- 相同类型对象的转化,返回是否成功以及转化后的结果
- 约定不符合规则就不转化
- 一般情况下转化成功的结果会和源对象不同
- 当然经过多次投影后也可能会和源对象相同了
public interface IProjection
{
bool TryConvert(T source, out T result);
}
2. 三种投影方式
2.1 使用前缀投影来演示
- 前缀投影就是把成员前面加个前缀来映射
- sourceMembers模拟对User的反射
- 通过Projection.Prefix("User")创建一个前缀投影
public record User(int Id, string UserName);
public record UserDTO(int UserId, string UserName);
var sourceMembers = new Dictionary>()
{
[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代码
IDictionary> 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表示
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代码
IDictionary> 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表示
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代码
IDictionary> 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表示
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
// 包含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
IMapper mapper = Mapper.Create();
mapper.ConfigureMap()
.Source
.AddPrefix("User");
var source = new AutoUserDTO { UserId = "222", UserName = "Jxj2" };
var converter = mapper.GetConverter();
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")
- 创建了一个替换前缀投影
IMapper mapper = Mapper.Create();
mapper.ConfigureMap()
.Source
.AddPrefix("User", "U");
var source = new AutoUserDTO { UserId = "555", UserName = "Jxj5" };
var converter = mapper.GetConverter();
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
- 通过这个投影,两边的成员名也能完美匹配上了
IMapper mapper = Mapper.Create();
mapper.ConfigureMap()
.Dest
.AddPrefix("U", "User");
var source = new AutoUserDTO { UserId = "555", UserName = "Jxj5" };
var converter = mapper.GetConverter();
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类似,用法简单,这里就不展示了
IMapper mapper = Mapper.Create();
mapper.ConfigureMap()
.Source
.AddSuffix("y", "ies");
var source = new Customer("Jxj", "北京");
var converter = mapper.GetConverter();
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引用投影,来实现更个性化的映射规则
- 实现了可扩展的映射规则,不仅限于前缀或后缀的规则
IMapper mapper = Mapper.Create();
mapper.ConfigureMap()
.Source
.AddProjection(Projection.Replace("Nume", "Name"));
var source = new ProductJson { CityNume = "北京", ProductNume = "手机" };
var converter = mapper.GetConverter();
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
原文地址: https://www.cveoy.top/t/topic/qGtM 著作权归作者所有。请勿转载和采集!