`
zz8ss5ww6
  • 浏览: 64901 次
  • 性别: Icon_minigender_1
  • 来自: 北京
文章分类
社区版块
存档分类
最新评论

使用AutoMapper实现Dto和Model的自由转换(下)

阅读更多
书接上文。在上一篇文章中我们讨论了使用AutoMapper实现类型间1-1映射的两种方式——Convention和Configuration,知道了如何进行简单的OO Mapping。在这个系列的最后一篇文章我想基于我们的需求讨论一些中级别的话题,包括:如何实现类型体型之间的映射,以及如何为两个类型实现多个映射规则。
【四】将一个类型映射为类型体系
先回顾一下我们的Dto和Model。我们有BookDto,我们有Author,每个Author有自己的ContactInfo。现在提一个问题:如何从BookDto得到第一个作者的Author对象呢?答案即简单,又不简单。
最简单的做法是,使用前面提到的CountructUsing,指定BookDto到Author的全部字段及子类型字段的映射:
            var map = Mapper.CreateMap<BookDto,Author>();
            map.ConstructUsing(s => new Author
                                        {
                                            Name = s.FirstAuthorName,
                                            Description = s.FirstAuthorDescription,
                                            ContactInfo = new ContactInfo
                                                              {
                                                                  Blog = s.FirstAuthorBlog,
                                                                  Email = s.FirstAuthorEmail,
                                                                  Twitter = s.FirstAuthorTwitter
                                                              }
                                        });

这样的做法可以工作,但很不经济。因为我们是在从头做BookDto到Author的映射,而从BookDto到ContactInfo的映射是我们之前已经实现过的,实在没有必要重复再写一遍。设想一下,如果有一个别的什么Reader类型里面也包含有ContactInfo,在做BookDto到Reader映射的时候,我们是不是再写一遍这个BookDto -> ContactInfo逻辑呢?再设想一下如果我们在实现BookDto到Book的映射的时候,是不是又需要把BookDto到Author的映射规则再重复写一遍呢?
所以我认为对于这种类型体系间的映射,比较理想的做法是为每个具体类型指定简单的映射,而后在映射复杂类型的时候再复用简单类型的映射。用简单点的语言描述:
我们有A,B,C,D四个类型,其中B = [C, D]。已知A -> C, A -> D, 求A -> B。
我的解法是使用AutoMapper提供的——IValueResolver。IValueResolver是AutoMapper为实现字段级别的特定映射逻辑而定义的类型,它的定义如下:
public interface IValueResolver
{
    ResolutionResult Resolve(ResolutionResult source);
}

而在实际的应用中我们往往会使用它的泛型子类——ValueResolver,并实现它的抽象方法:
protected abstract TDestination ResolveCore(TSource source);

其中TSource为源类型,TDestination为目标字段的类型。
回到我们的例子,我们现在可以这样来映射BookDto -> Author:
            var map = Mapper.CreateMap<BookDto, Author>();
            map.ForMember(d => d.Name, opt => opt.MapFrom(s => s.FirstAuthorName))
                .ForMember(d => d.Description, opt => opt.MapFrom(s => s.FirstAuthorDescription))
                .ForMember(d => d.ContactInfo,
                           opt => opt.ResolveUsing<FirstAuthorContactInfoResolver>()));

在FirstAuthorContactInfoResolver中我们实现ValueResolver并复用BookDto -> ContactInfo的逻辑:
    public class FirstAuthorContactInfoResolver : ValueResolver<BookDto,ContactInfo>
    {
        protected override ContactInfo ResolveCore(BookDto source)
        {
            return Mapper.Map<BookDto, ContactInfo>(source);
        }
    }

一切就搞定了。
类似的,我们现在也可以实现BookDto -> Book了吧?通过复用BookDto -> Author以及BookDto -> Publisher。
真的可以吗?好像还有问题。是的,我们会发现需要从BookDto映射到两个不同的Author,它们的字段映射规则是不同的。怎么办?赶紧进入我们的最后一个议题。
【五】为两个类型实现多套映射规则
我们的问题是:对于类型A和B,需要定义2个不同的A -> B,并让它们可以同时使用。事实上目前的AutoMapper并没有提供现成的方式做到这一点。
当然我们可以采用“曲线救国”的办法——为first author和second author分别定义Author的两个子类,比如说FirstAuthor和SecondAuthor,然后分别实现BookDto -> FirstAuthor和BookDto -> SecondAuthor映射。但是这种方法也不太经济。假如还有第三作者甚至第四作者呢?为每一个作者都定义一个Author的子类吗?
另一方面,我们不妨假设一下,如果AutoMapper提供了这样的功能,那会是什么样子呢?CreateMap方法和Map方法应该这样定义:
CreateMap<TSource, TDestination>(string tag)
Map<TSource, TDestination>(TSource, string tag)

其中有一个额外的参数tag用于标识该映射的标签。
而我们在使用的时候,就可以:
                var firstAuthorMap = Mapper.CreateMap<BookDto, Author>("first");
                // Define BookDto -> first Author rule
                var secondAuthorMap = Mapper.CreateMap<BookDto, Author>("second");
                // Define BookDto -> second Author rule
                var firstAuthor = Mapper.Map<BookDto, Author>(source, "first");
                var secondAuthor = Mapper.Map<BookDto, Author>(source, "second");

遗憾的是,这一切都是假如。但是没有关系,虽然AutoMapper关上了这扇门,却为我们留着另一扇门——MappingEngine。
MappingEngine是AutoMapper的映射执行引擎,事实上在Mapper中有默认的MappingEngine,我们在调用Mapper.CreateMap的时候,是往与这个默认的MappingEngine对应的Configuration中写规则,在调用Mapper.Map获取对象的时候则是使用默认的MappingEngine执行其对应Configuration中的规则。
简而言之一个MappingEngine就是一个AutoMapper的“虚拟机”,如果我们同时启动多个“虚拟机”,并且将针对同一对类型的不同映射规则放到不同的“虚拟机”上,就可以让它们各自相安无事的运行起来,使用的时候要用哪个规则就问相应的“虚拟机”去要好了。
说做就做。首先我们定义一个MappingEngineProvider类,用它来获取不同的MappingEngine:
    public class MappingEngineProvider
    {
        private readonly MappingEngine _engine;

        public MappingEngine Get()
        {
            return _engine;
        }
    }

我们将不同类型的映射规则抽象为接口IMapping:
    public interface IMapping
    {
        void AddTo(Configuration config);
    }

然后在MappingEngineProvider的构造函数里将需要的规则放到对应的MappingEngine中:
        private static Dictionary<Engine,List<IMapping>> _rules=new Dictionary<Engine, List<IMapping>>();

        public MappingEngineProvider(Engine engine)
        {
            var config = new Configuration(new TypeMapFactory(), MapperRegistry.AllMappers());
            _rules[engine].ForEach(r=> r.AddTo(config));
            _mappingEngine = new MappingEngine(config);
        }

注意到这里我们用了一个枚举类型Engine用于标识可能的MappingEngine:
    public enum Engine
    {
        Basic = 0,
        First,
        Second
    }

我们用到了3个Engine,Basic用于放置所有基本的映射规则,First用于放置所有Dto -> FirstXXX的规则,Second则用于放置所有Dto -> SecondXXX的规则。
我们还定义了一个放置所有映射规则的字典_rule,将规则分门别类放到不同的Engine中。
剩下的事情就是往字典_rule里填充我们的mapping了。比如说我们把BookDtoToFirstAuthorMapping放到First engine里并把BookDtoToSecondAuthorMapping放到Second engine里:
        private static readonly Dictionary<Engine, List<IMapping>> _rules =
            new Dictionary<Engine, List<IMapping>>
                {
                    {
                        Engine.First, new List<IMapping>
                                          {
                                              new BookDtoToFirstAuthorMapping(),
                                          }
                        },
                    {
                        Engine.Second, new List<IMapping>
                                           {
                                               new BookDtoToSecondAuthorMapping(),
                                           }
                        },
                };

当然为了方便使用我们可以事先实例化好不同的MappingEngineProvider对象:
        public static SimpleMappingEngineProvider First = new MappingEngineProvider(Engine.First);
        public static SimpleMappingEngineProvider Second = new MappingEngineProvider(Engine.Second);

现在我们就可以在映射BookDto -> Book的时候同时使用这2个Engine来得到2个Author并把它们组装到字段Book.Authors里面了:
    public class BookDtoToBookMapping : DefaultMapping<BookDto, Book>
    {
        protected override void MapMembers(IMappingExpression<BookDto, Book> map)
        {
            map.ForMember(d => d.Authors,
                           opt => opt.ResolveUsing<AuthorsValueResolver>());
        }

        private class AuthorsValueResolver : ValueResolver<BookDto, List<Author>>
        {
            protected override List<Author> ResolveCore(BookDto source)
            {
                var firstAuthor = SimpleMappingEngineProvider.First.Get().Map<BookDto, Author>(source);
                var secondAuthor = SimpleMappingEngineProvider.Second.Get().Map<BookDto, Author>(source);
                return firstAuthor.IsNull()
                           ? secondAuthor.IsNull() ? new List<Author>() : new List<Author> {new Author(), secondAuthor}
                           : secondAuthor.IsNull()
                                 ? new List<Author> {firstAuthor}
                                 : new List<Author> {firstAuthor, secondAuthor};
            }
        }

    }

最后,还记得我们在本节开始的时候提到的美好愿望吗?既然AutoMapper没有帮我们实现,就让我们自己来实现吧:
    public class MyMapper
    {
        private static readonly Dictionary<Engine, MappingEngine> Engines = new Dictionary<Engine, MappingEngine>
                                                                       {
                                                                           {Engine.Basic, MappingEngineProvider.Basic.Get()},
                                                                           {Engine.First, MappingEngineProvider.First.Get()},
                                                                           {Engine.Second, MappingEngineProvider.Second.Get()},
                                                                       };

        public static TTarget Map<TSource, TTarget>(TSource source, Engine engine = Engine.Basic)
        {
            return Engines[engine].Map<TSource, TTarget>(source);
        }
    }

一切又都回来了,我们可以这样:
var firstAuthor = MyMapper.Map<BookDto,Author>(dto, Engine.First);
var secondAuthor = MyMapper.Map<BookDto,Author>(dto, Engine.Second);

也可以这样了:
var book = MyMapper.Map<BookDto,book>(dto);


后记: 发现在家里要上传文件到Github真是奇慢无比,所有我决定先把自己的代码打包上传,欢迎大家参考使用。
2
2
分享到:
评论
3 楼 bangjun 2014-11-13  
楼主辛苦了,非常受用
2 楼 zz8ss5ww6 2012-10-07  
不好意思忘记密码了,这才找回的这个账户。
希望这个小系列能有所帮助。
1 楼 angel6278 2012-03-14  
讲解很详细,代码很完备
想了解下这个工具,找来找去都没找到好的资料,最后找到博主这了
楼主是否迁移博客了?

相关推荐

    Dto转实体类 AutoMapper

    4. **自定义映射**:在某些情况下,Dto和实体类的属性名称可能不完全匹配,或者需要进行更复杂的转换,如类型转换、条件判断等。这时,可以使用`ForMember`或`ForPath`方法自定义映射规则: ```csharp config....

    EF+Mapper结合使用实现Dto到实体类再到数据的(框架模型基础实现)

    在.NET开发领域,Entity Framework(简称EF)是微软提供的一款强大的对象关系映射(ORM)框架,它允许开发者使用面向对象的...在实际项目中,这样的设计模式和实现方式对于构建可扩展且易于维护的软件架构至关重要。

    AutoMapper的使用

    6. **DTO(Data Transfer Objects)**:在ASP.NET MVC或Web API中,通常使用DTO作为控制器与视图或客户端之间的数据交换格式,AutoMapper可以帮助轻松实现业务对象与DTO之间的转换。 7. **性能优化**:通过缓存映射...

    详解c# AutoMapper 使用方式

    在了解了 DTO 的概念后,我们可以使用 AutoMapper 实现 DTO 和领域模型之间的转换。AutoMapper 是一个轻量级的 Object-Object Mapping 工具,非常流行且国外的大牛们都使用它。 使用 AutoMapper 有多种方式,下面是...

    AutoMapper,AutoMapper,AutoMapper

    2. **视图模型与领域模型的互换**:在 MVC 应用中,视图模型可能需要与数据库中的领域模型有所不同,AutoMapper 可以方便地实现两者之间的转换。 3. **ORM 映射**:与 Entity Framework 或 NHibernate 等 ORM 工具...

    AutoMapper映射配置和使用实例运用。

    AutoMapper是一款强大的对象到对象映射库,广泛应用于.NET开发中,用于简化对象之间的...在实际的AutoMapperDemo项目中,你可以找到更多关于如何使用AutoMapper的实际示例,包括复杂的映射规则和多层嵌套对象的转换。

    AutoMapper语法配源码总结

    使用Condition方法可以实现条件映射。 AutoMapper还支持泛型类型映射,可以直接支持开放泛型类型映射,不需要创建封闭泛型类型。AutoMapper反射类型映射时支持类型转换。 嵌套类型映射实际上是相当于2对类型的映射...

    AutoMapper一个PHP的AutoMapper

    3. **执行映射**:在实际业务逻辑中,当需要进行对象间的数据转换时,调用`Map()`方法,传入源对象和目标对象的实例,`AutoMapper`会根据之前定义的映射配置自动进行数据复制。 例如,假设我们有一个`UserEntity`...

    automapper

    4. **类型转换**:在映射过程中,AutoMapper可以自动处理源类型和目标类型之间的转换,只要.NET Framework提供了相应的转换器。 5. **双向映射**:不仅可以从实体到合同,也可以从合同到实体,只需定义一次映射规则...

    EF6Fluent API+AutoMapper.EF6使用

    在结合EF6使用时,AutoMapper.EF6扩展可以帮助我们在保存到数据库之前自动转换实体对象,简化数据处理。例如,当接收一个视图模型并将其保存到数据库时,可以这样配置: ```csharp Mapper.CreateMap, Entity&gt;() ....

    ASP.NET 6 集成 AutoMapper

    在`Get`方法中,我们创建了一个`SourceClass`实例,并使用AutoMapper将其转换为`DestinationClass`。 总结一下,ASP.NET 6集成AutoMapper的主要步骤包括:安装AutoMapper相关包,定义映射规则,注册配置,以及在...

    AutoMapper和表达式条件的实体映射(DDD的使用)

    在这个例子中,`ProjectTo`方法将数据库中的`User`实体转换为DTO(数据传输对象),同时应用了之前定义的映射规则和动态条件。 5. **优化映射性能**:在处理大量数据时,应考虑使用`BatchMapper.Map`或`ProjectTo`...

    10.3 AutoMapper

    5. **类型转换**:AutoMapper 不仅支持简单的属性映射,还支持复杂的类型转换,包括自定义类型转换器、成员映射条件、嵌套对象和集合的映射等。 6. **性能优化**:为了提高性能,AutoMapper 缓存映射配置,确保每个...

    Automapper实现自动映射的实例代码

    这时,我们可以使用Automapper来帮助我们完成类型转换。 首先,我们需要安装Automapper的Nuget包。在Visual Studio中,我们可以通过 Tools - Nuget Package Manager - Package Manager Console输入Install-Package ...

    automapper+autofac+mvc5+三层-ioc

    标题中的“automapper+autofac+mvc5+三层-ioc”揭示了这个项目或教程是关于使用四个关键技术和框架来构建一个三层架构的MVC应用程序。这些技术包括: 1. AutoMapper:AutoMapper 是一个对象对象映射库,用于.NET...

    java DTO 详解

    2. **降低耦合**:通过使用DTO,客户端和服务端之间的耦合度被大大降低,因为它们只需要关注DTO的结构,而无需关心具体的业务逻辑。 3. **提高效率**:相比于多次调用服务端接口获取数据,使用DTO可以在一次调用中...

    22-10-18-09-SqlSugarAcqua(初识SqlSugarCore之AutoMapper)

    SqlSugarCore是一款高效、轻量级的.NET Core数据库操作库,它提供了丰富的ORM(对象关系映射)功能,...在实际项目中,根据具体需求和场景,合理地使用和配置这两个工具,将有助于打造高效且易于维护的数据库解决方案。

Global site tag (gtag.js) - Google Analytics