一、投影技术
- 这里的投影可以实现类似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