Asp.mvc(二)~使用AutoMapper实现领域模型与DTO映射

本文介绍如何利用AutoMapper简化对象转换过程,实现领域模型与DTO之间的映射,减少代码冗余,提高数据传输效率。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

Asp.mvc(二)~使用AutoMapper实现领域模型与DTO映射

上一篇中介绍了Core, Data 以及 Services 层,在介绍 Presentation 层之前,我们需要了解下面几个知识点:

  1. AutoMapper
  2. Autofac
  3. 以及上篇博文中 Data 层未详细介绍的 WebActivatorEx

AutoMapper

有时候,你需要将一种类型转换为另外一种类型,这种情况在mvc 项目中较为常见,在数据查询的时候,通过数据持久层将数据绑定到领域模型中,在数据写入的时候,通过数据持久层将绑定到领域模型上的数据保存至数据库。 但是在用户界面上, 我们并不需要将数据完完全全的暴露的用户眼前, 所以在这里我们一般都会使用到 “贫血模式”, 建立一个 ViewModel 层,其作用就是将实际需要的数据定义为模型, 也就是DTO (Data Transfer Object), 在用户查询的时候, 程序通常会将数据保存至领域模型中, 然后将领域模型转换至 DTO,来保证传输的数据都是必须的,这样的一种做法可以减少领域模型与 Presentation 的耦合, 以及完全不需要将数据字段暴露在 Presentation 层,也保证了一定情况下数据的安全性,获取必要的数据, 也可以提高数据传输的效率。

一般情况下,如果将一个对象的部分属性克隆给另外一个对象, 通常的做法就是:

Object A = new Object(args...);

Object B = new Object
{
	Property1 = A.Property1,	
	Property2 = A.Property2,
	Property3 = A.Property3...
}; 

这样的做法存在着较大的不足:

  1. 冗杂,繁琐
  2. 灵活性较差
  3. 都讨厌编写这种无聊的代码…

我们需要一种工具来帮助我们完成这段枯燥无味的工作: http://automapper.org/

根据官网对AutoMapper的介绍:

AutoMapper is a simple little library built to solve a deceptively complex problem - getting rid of code that mapped one object to another. This type of code is rather dreary and boring to write, so why not invent a tool to do it for us?

可以看出 AutoMapper 就是为了处理这一项枯燥代码的工具。 下面建立一个控制台 Demo 来演示 AutoMapper 的作用:

新建 AutoMapperSample 控制台程序,打开 Nuget 包控制台,键入:

Install-Package AutoMapper

创建以下领域模型:

using System;
using System.Collections.Generic;

namespace AutoMapperSample
{
    public partial class Student 
    {
        public string Id { get; set; }
        public string Name { get; set; }
        public string Gender { get; set; }
        public DateTime? Birthday { get; set; }
        public string ClassId { get; set; }
        public virtual Class Class { get; set; }
    }

    public partial class Class
    {
        public Class()
        {
            this.Students = new List<Student>();
        }

        public string Id { get; set; }
        public string Name { get; set; }
        public List<Student> Students { get; set; }
        public string GradeId { get; set; }
        public virtual Grade Grade { get; set; }
    }

    public partial class Grade 
    {
        public Grade()
        {
            this.Classes = new List<Class>();
        }

        public string Id { get; set; }
        public string Name { get; set; }
        public List<Class> Classes { get; set; }
    }
}

较为清晰的嵌套结构,下面创建 DTO 模型,里面声明一些必要的属性:

using System;

namespace AutoMapperSample
{
    public partial class StudentDto 
    {
        /// <summary>
        /// 学员编号 --> Student.Id
        /// </summary>
        public string Id { get; set; }
        
        /// <summary>
        /// 姓名 --> Student.Name
        /// </summary>
        public string Name { get; set; }
        
        /// <summary>
        /// 生日 --> Student.Birthday
        /// </summary>
        public DateTime? Birthday { get; set; }

        /// <summary>
        /// 班级编号 --> Student.ClassId
        /// </summary>
        public string ClassId { get; set; }

        /// <summary>
        /// 班级 --> Student.Class.Name
        /// </summary>
        public string ClassName { get; set; }

        /// <summary>
        /// 年级编号 Student.Class.GradeId
        /// </summary>
        public string GradeId { get; set; }

        /// <summary>
        /// 年级 Student.Class.Grade.Name
        /// </summary>
        public string GradeName { get; set; }
    }
}

在上面的注释中可以看到一些嵌套的映射关系,下面建立映射,创建 Mapping

using AutoMapper;

namespace AutoMapperSample
{
    public static class Mapping
    {
        /// <summary>
        /// 注册映射关系 
        /// </summary>
        public static void Register()
        {
            Mapper.CreateMap<Student, StudentDto>()
                .ForMember(dest => dest.ClassName, mo => mo.MapFrom(src => src.Class.Name))
                .ForMember(dest => dest.GradeId, mo => mo.MapFrom(src => src.Class.GradeId))
                .ForMember(dest => dest.GradeName, mo => mo.MapFrom(src => src.Class.Grade.Name));
            Mapper.CreateMap<StudentDto, Student>()
                .ForMember(dest => dest.Gender, mo => mo.Ignore())
                .ForMember(dest => dest.Class, mo => mo.Ignore());
        }

        /// <summary>
        /// 领域模型转化为Dto
        /// </summary>
        /// <param name="entity"></param>
        /// <returns></returns>
        public static StudentDto ToModel(this Student entity)
        {
            return Mapper.Map<Student, StudentDto>(entity);
        }

        /// <summary>
        /// Dto转化为领域模型
        /// </summary>
        /// <param name="model"></param>
        /// <returns></returns>
        public static Student ToEntity(this StudentDto model)
        {
            return Mapper.Map<StudentDto, Student>(model);
        }

        /// <summary>
        /// 重载 ToEntity, 在已有 Dto模型基础上使用领域模型转换成 Dto
        /// </summary>
        /// <param name="model"></param>
        /// <param name="entity"></param>
        /// <returns></returns>
        public static Student ToEntity(this StudentDto model, Student entity)
        {
            return Mapper.Map(model, entity);
        }
    }
}

把解释都写在注释中了,很详细。下面来测试一下:

using System;

namespace AutoMapperSample
{
    class Program
    {
        static void Main(string[] args)
        {
            //注册映射关系
            Mapping.Register();

            //数据初始化
            var grade = new Grade { Id = "g001", Name = "一年级" };
            var _class = new Class { Id = "c001", Name = "一班", GradeId = grade.Id, Grade = grade };
            var student = new Student
            {
                Id = "s001",
                Name = "Cigarette",
                Birthday = DateTime.Now,
                Gender = "Male",
                ClassId = _class.Id,
                Class = _class
            };
            _class.Students.Add(student);
            grade.Classes.Add(_class);

            //1.Student --> StudentDto
            var studentDto = student.ToModel();
            //2.StudentDto --> Student
            student = studentDto.ToEntity();
            //3.StudentDto --> Student(以一个已存在的Student作为基础)
            var studentPart = new Student { Gender = "Female", Class = new Class { Name = "new class" } };
            student = studentDto.ToEntity(studentPart);

            Console.ReadKey();
        }
    }
}

在1.2.3处分别打上断点,监视情况为: (图片无法上传…所以这里直接Copy了)

-		student		{AutoMapperSample.Student}	AutoMapperSample.Student
+		Birthday	{2015/7/22 21:55:38}		System.DateTime?
+		Class		{AutoMapperSample.Class}	AutoMapperSample.Class
		ClassId		"c001"						string
		Gender		"Male"						string
		Id			"s001"						string
		Name		"Cigarette"					string
-		studentDto	{AutoMapperSample.StudentDto}	AutoMapperSample.StudentDto
+		Birthday	{2015/7/22 21:55:38}			System.DateTime?
		ClassId		"c001"							string
		ClassName	"一班"							string
		GradeId		"g001"							string
		GradeName	"一年级"							string
		Id			"s001"							string
		Name		"Cigarette"						string 
-		student		{AutoMapperSample.Student}	AutoMapperSample.Student
+		Birthday	{2015/7/22 21:55:38}		System.DateTime?
-		Class		{AutoMapperSample.Class}	AutoMapperSample.Class
		Grade		null						AutoMapperSample.Grade
		GradeId		null						string
		Id			null						string
		Name		"new class"					string
+		Students	Count = 0					System.Collections.Generic.List<AutoMapperSample.Student>
		ClassId		"c001"						string
		Gender		"Female"					string
		Id			"s001"						string
		Name		"Cigarette"					string

从上面可以看出我们的领域模型对象student转换为studentDto之后,根据我们所配置的映射关系,数据已经完全映射正确,其实在上面这段代码中,已经解决了两个看似复杂的问题:相同类型不同名的属性映射,嵌套属性映射。

让我们再来看看注册映射规则的重要代码段(Mapping.Register()):

/// <summary>
/// 注册映射关系 
/// </summary>
public static void Register()
{
    Mapper.CreateMap<Student, StudentDto>()
        .ForMember(dest => dest.ClassName, mo => mo.MapFrom(src => src.Class.Name))
        .ForMember(dest => dest.GradeId, mo => mo.MapFrom(src => src.Class.GradeId))
        .ForMember(dest => dest.GradeName, mo => mo.MapFrom(src => src.Class.Grade.Name));
    Mapper.CreateMap<StudentDto, Student>()
        .ForMember(dest => dest.Gender, mo => mo.Ignore())
        .ForMember(dest => dest.Class, mo => mo.Ignore());
}

通过 Mapper 的静态方法 CreateMap 来创建两个模型之间的映射规则,第一个类型为 Source 即源模型,第二个类型为 Destination 即目标模型,

CreateMap<TSource,TDestination>()

返回一个

IMappingExpression<TSource, TDestination> 

,使用它的 ForMember 来定义规则。 前者表示 Student --> StudentDto 的规则,后者则为 StudentDto --> Student 的规则。 这段代码一般都在程序启动后第一时间执行,完成对规则的注册, 在 asp.net mvc项目中, 我们可以使用 WebActivatorEx 来完成这种类型代码的执行,这个是要讲解的第三个点了。 Ok, AutoMapper 的基本使用就已经说得差不多了, 下一篇会了解一下 Autofac 这个依赖注入容器, 以解耦合。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值