开源项目PocoEmit.Mapper重构之扑风捉影
一、投影技术这里的投影可以实现类似sql投影的效果也可以类比光线照射在物体上形成的影像1. 投影接口定义IProjection接口很简单只有TryConvert一个方法相同类型对象的转化,返回是否成功以及转化后的结果约定不符合规则就不转化一般情况下转化成功的结果会和源对象不同当然经过多次投影后也可能会和源对象相同了public interface IProjectionT { bool TryConvert(T source, out T result); }2. 三种投影方式2.1 使用前缀投影来演示前缀投影就是把成员前面加个前缀来映射sourceMembers模拟对User的反射通过Projection.Prefix(User)创建一个前缀投影如果没有User前缀,就增加User如果有User前缀就不处理public record User(int Id, string UserName); public record UserDTO(int UserId, string UserName); var sourceMembers new Dictionarystring, FuncUser, object() { [nameof(User.Id)] obj obj.Id, [nameof(User.UserName)] obj obj.UserName }; var projection Projection.Prefix(User);2.2 Filter投影的Case2.2.1 csharp代码IDictionarystring, FuncUser, object result projection.Filter(sourceMembers); Assert.Single(result); Assert.True(result.ContainsKey(nameof(UserDTO.UserId)));2.2.2 sql表示SELECT Id AS UserId FROM User2.2.3 影像表示UserFilterIdUserNameUserId2.3 Through投影的Case2.3.1 csharp代码IDictionarystring, FuncUser, 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表示SELECT Id AS UserId,UserName FROM User2.3.3 影像表示UserThroughThroughIdUserNameUserIdUserName2.4 Cross投影的CasePocoEmit.Mapper重构用的就是Cross投影2.4.1 csharp代码IDictionarystring, FuncUser, 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表示SELECT Id,UserName,Id AS UserId FROM User2.4.3 影像表示UserCrossCrossCrossIdUserNameIdUserNameUserId3. 投影支持横向扩展这里说的横向就是投影并联把多个投影组合成多分支的映射规则通过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两个并联的投影并联投影按照顺序依次尝试直到有一个投影成功了就返回结果expectedsourceRemoveUserRemoveURemoveURemoveUserUserIdUIdUUserNameUserUNameIdUserNameUName二、投影在PocoEmit.Mapper中的应用1. AddPrefix1.1 AddPrefix的CaseIMapper mapper Mapper.Create(); mapper.ConfigureMapAutoUserDTO, User() .Source .AddPrefix(User); var source new AutoUserDTO { UserId 222, UserName Jxj2 }; var converter mapper.GetConverterAutoUserDTO, 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和UserNameUser两个成员分别是Id和UserName对AutoUserDTO投影的效果相当于sql: SELECT UserId,UserName,UserId AS Id FROM AutoUserDTO通过这个投影,两边的成员名就能完美匹配上了1.3 影像演示一下这个过程UserAutoUserDTO投影AutoUserDTORemoveUserRemoveUserMappingMappingUserIdUserNameUserIdUserNameIdNameIdUserName1.4 使用前缀投影(Prefix)也可以实现类似的效果通过Projection.Prefix(User)创建一个前缀投影这时就需要对User进行投影对User投影的效果相当于sql: SELECT Id,UserName,Id AS UserId FROM UserPocoEmit.Mapper使用RemovePrefix是为了投影的成员名更短,更容易匹配上1.4.1 影像演示一下这个过程User投影UserAutoUserDTOAddUserMappingMappingUserIdUserNameIdUserNameIdUserNameUserId2. AddPrefix替换前缀重载2.1 AddPrefix替换前缀重载的CaseAddPrefix(User, U)会调用Projection.ReplacePrefix(User, U)创建了一个替换前缀投影IMapper mapper Mapper.Create(); mapper.ConfigureMapAutoUserDTO, UserCustomDTO() .Source .AddPrefix(User, U); var source new AutoUserDTO { UserId 555, UserName Jxj5 }; var converter mapper.GetConverterAutoUserDTO, UserCustomDTO(); var result converter.Convert(source); Assert.NotNull(result); Assert.Equal(source.UserId, result.UId.ToString()); Assert.Equal(source.UserName, result.UName);2.2 影像演示一下这个过程UserCustomDTOAutoUserDTO投影AutoUserDTOUserToUUserToUMappingMappingUserIdUserNameUserIdUserNameUIdUNameUIdUName2.3 对UserCustomDTO替换的Case对UserCustomDTO投影的效果相当于sql: SELECT UId,UName,UId AS UserId,UName AS UserName FROM UserCustomDTO通过这个投影,两边的成员名也能完美匹配上了IMapper mapper Mapper.Create(); mapper.ConfigureMapAutoUserDTO, UserCustomDTO() .Dest .AddPrefix(U, User); var source new AutoUserDTO { UserId 555, UserName Jxj5 }; var converter mapper.GetConverterAutoUserDTO, 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替换的影像UserCustomDTO投影UserCustomDTOAutoUserDTOUToUserUToUserMappingMappingUserIdUserNameUIdUNameUserIdUserNameUIdUName3. AddSuffix3.1 AddSuffix替换后缀的CaseAddSuffix(y, ies)会调用Projection.ReplaceSuffix(y, ies)创建一个替换后缀投影常用于单复数拼写转化对Customer投影的效果相当于sql: SELECT Name,City,Name AS CustomerName,City AS CustomerCities FROM CustomerConfigureMap方法默认会把源类名作为目标的前缀,也把目标类名作为源的前缀,所以在这个例子里没有显示调用AddPrefix,Customer前缀自动生效了如果不需要默认规则可以通过参数autoRecognize设置为false禁用掉AddSuffix也支持去掉后缀的重载与AddPrefix类似,用法简单,这里就不展示了IMapper mapper Mapper.Create(); mapper.ConfigureMapCustomer, CustomerDTO() .Source .AddSuffix(y, ies); var source new Customer(Jxj, 北京); var converter mapper.GetConverterCustomer, 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就很简单了,直接同名映射(一般忽略大小写,是否忽略大小写可以配置)CustomerDTOCustomerDTO投影Customer投影CustomeryToiesRemoveCustomerRemoveCustomerMappingMappingNameCityNameCityCitiesCustomerNameCustomerCitiesNameCitiesCustomerNameCustomerCities4. AddProjection4.1 AddProjection的CaseProjection.Replace(Nume, Name)创建替换Nume为Name的投影通过AddProjection引用投影,来实现更个性化的映射规则实现了可扩展的映射规则,不仅限于前缀或后缀的规则IMapper mapper Mapper.Create(); mapper.ConfigureMapProductJson, Product() .Source .AddProjection(Projection.Replace(Nume, Name)); var source new ProductJson { CityNume 北京, ProductNumeCount 999 }; var converter mapper.GetConverterProductJson, Product(); var result converter.Convert(source); Assert.NotNull(result); Assert.Equal(source.CityNume, result.CityName); Assert.Equal(source.ProductNumeCount, result.ProductNameCount);4.2 AddProjection的影像ProductProductJson投影ProductJsonNumeToNameNumeToNameMappingMappingCityNumeProductNumeCountCityNumeProductNumeCountCityNameProductNameCountCityNameProductNameCount三、总结借助投影技术极大的增强了PocoEmit.Mapper的映射能力另外还增加了PocoEmit.Mapper映射的可扩展性用户可以根据需要创建自己的投影来实现个性化的映射规则Mapper只是投影的一个应用场景,笔者还在开发投影在SourceGenerator的应用投影项目hand.projections就是把类似代码抽象成一个独立的库每个投影功能很简单,不同场景下复用并组合来实现复杂需求才是投影的魅力所在