浅入ABP 系列(7):对象映射

简介: 浅入ABP 系列(7):对象映射

基础


DTO和实体


实体

实体是领域驱动设计(Domain Driven Design)中的概念,实体通常一一映射某些对象的固有属性,最常使用的是关系型数据库中的表。


在 ABP 中,实体位于领域层中,实体类需要实现 IEntity<TKey> 接口或继承

Entity<TKey> 基类,示例如下:

public class Book : Entity<Guid>
{
    public string Name { get; set; }
    public float Price { get; set; }
}


DTO

数据传输对象(Data Transfer Object),作为数据传输过程中的数据模型,用于在应用层和表示层之间传输数据。


在 ABP 中,DTO 位于应用服务层,即本系列文章示例源码中的

AbpBase.Application 项目。


通常表示层或其它类型的客户端调用应用服务时,将 DTO 作为参数传递,它使用领域对象(实体)执行某些特定的业务逻辑,并将 DTO (跟传入的 DTO 不是同一个)返回到表示层中,因此表示层与领域层完全隔离。


DTO 类 可能会跟 实体类的字段/属性高度相似,为每个服务的每个方法创建 DTO 类可能会很枯燥且费时间。


ABP 的 DTO 类示例如下:

public class ProductDto : EntityDto<Guid>
    {
        public string Name { get; set; }
        //...
    }


麻烦的映射


前面提到,领域层和应用服务层是要隔离的,例如以下伪代码:

class HomeController
{
    AddService _service;
    [HttpPost]
    public int AddEquip(EquipDto dto)
    {
        return _service.Add(dto).Id;
    }
}
class AddService
{
    DataContext _context;  
    EquipDto Add(EquipDto dto)
    {
        Equip equip = new Equip()
        {
          Name = dto.Name;  
        };
        _context.Equip.Add(equip);
        _context.SaveChange();
        dto.Id = equip.Id;
        return dto;
    }
}
class EquipDto
{
    int Id;
    string Name;
}
----------
class Equip
{
    int Id;
    string Name;
}


这样每次都需要手动为 DTO 类和 实体类手动对字段赋值映射,当一个实体有数十个字段时,写出的代码会很冗长,而且容易忽略了某些字段,最终导致了 Bug。


大家都知道, AutoMapper 正好可以解决这个问题。


AutoMapper 集成


ABP 的 Volo.Abp.AutoMapper 模块封装或集成了 AutoMapper,所以我们正好使用模块,为 ABP 应用定义对象映射。


关于 AutoMapper 的使用,如何配置 Profile 等,笔者已经单独写到 浅入 AutoMapper,请点击链接另外学习 AutoMapper 的使用。


我们可以在 AbpBase.Application 项目中,新建 一个 AbpBaseApplicationAutoMapperProfile.cs 文件,这个文件用于实现 Profile 以及定义映射。将服务领域的映射集中到这个文件中;或者新建一个 Profiles 文件夹,在其中存放一些 Profile 类。


其内容如下:

public class AbpBaseApplicationAutoMapperProfile:Profile
    {
        public AbpBaseApplicationAutoMapperProfile()
        {
            //base.CreateMap<MyEntity,MyDto>();
        }
    }


定义完毕后,需要配置 AutoMapper 依赖注入,可在 AbpBaseApplicationModuleConfigureServices 方法中,增加以下代码:

Configure<AbpAutoMapperOptions>(options =>
            {
                // 以模块为单位注册映射
                options.AddMaps<AbpBaseApplicationModule>();
                //// 以单个 Profiel 为单位注册映射
                //options.AddProfile<AbpBaseApplicationAutoMapperProfile>();
            });


在 Debug 阶段,我们担心项目改动代码时,新增的字段忘记了加入到映射配置中,或者其它情况,在 AutoMapper 中,我们可以使用 configuration.AssertConfigurationIsValid(); 来检查映射;在 ABP 中则可使用 validate: true 参数来开启检查。


Configure<AbpAutoMapperOptions>(options =>
            {
                // 以模块为单位注册映射
                options.AddMaps<AbpBaseApplicationModule>(validate: true);
                //// 以单个 Profiel 为单位注册映射
                //options.AddProfile<AbpBaseApplicationAutoMapperProfile>(validate: true);
            });


IObjectMapper/ObjectMapper


AbpBase.Application 项目中,添加 Nuget 包,搜索 Volo.Abp.ObjectMapping 并下载相应的稳定版本。


IObjectMapper 有两个,一个是 AutoMapper 的接口,一个是 Volo.Abp.ObjectMapping 的 泛型接口。


AutoMapper 的 IObjectMapper 不好用,所以别用;用 Volo.Abp.ObjectMappingIObjectMapper <接口>


ObjectMapper 是 AutoMapper 中的,我们可以直接在控制器等位置,使用 ObjectMapper 注入,然后通过 ObjectMapper 实例映射对象。


ObjectMapper 只有 .Map() 这个方法用得顺手。

private readonly ObjectMapper<T1,T2> _mapper;
        public TestController(ObjectMapper<T1,T2> mapper)
        {
            _mapper = mapper;
            // ... 使用示例
            _ = mapper.Map<T1> ();
        }


也可以通过依赖注入使用 IObjectMapper 接口。

但是因为 ObjectMapper 是泛型类,每种类型的 DTO 都要注入一次的话,会很麻烦,因此这种方案也可以抛弃。


而 泛型的 IObjectMapper<TModule> 是一个抽象,我们使用 IObjectMapper<TModule> 做依赖注入的话,后续如果替换为别的对象映射框架,则不需要修改原有代码即可完成替代。而且 IObjectMapper<TModule> 比较舒服。


使用示例:

private readonly IObjectMapper<AbpBaseApplicationModule> _mapper;
        public TestController(IObjectMapper<AbpBaseApplicationModule> mapper)
        {
            _mapper = mapper;
            // ... 使用示例
            _ = mapper.Map<...>();
        }


对象拓展


ABP框架提供了 实体扩展系统 允许你 添加额外属性 到已存在的对象 无需修改相关类。这句话是抄 ABP 官方文档的。


要支持对象拓展映射,则需要开启配置:

public class MyProfile : Profile
{
    public MyProfile()
    {
        CreateMap<User, UserDto>()
            .MapExtraProperties();
    }
}


时间有限,笔者这里只把官方文档的内容讲清楚,读者看完后,需要继续查阅官方文档,完整了解对象拓展。


ObjectExtensionManager 是一个拓展对象映射类,可以显式为类拓展一些额外的属性,这个类型在 Volo.Abp.ObjectMapping 中定义。


ObjectExtensionManager 是一个类型,但是我们不能直接 new 它,或者使用依赖注入,只能通过 ObjectExtensionManager.Instance 这个属性获取新的类型。我们无需关心它是用了啥设计模式,还是因为缓存之类的原因这样设计。


ObjectExtensionManager 有两种属性,其说明如下:

  • AddOrUpdate :是定义对象额外属性或更新对象额外属性的主要方法;
  • AddOrUpdateProperty:快捷地定义单个拓展属性的方法;


AddOrUpdateProperty 用于定义单个属性,AddOrUpdate 是一个容器,可以包含多个 AddOrUpdateProperty


AddOrUpdateProperty 示例代码如下:

ObjectExtensionManager.Instance
    .AddOrUpdateProperty<TestA, string>("Name");
// 为 TestA 类添加了一个 G 属性


AddOrUpdate 的示例代码如下:

ObjectExtensionManager.Instance
    .AddOrUpdate<TestA>(options =>
        {
            options.AddOrUpdateProperty<string>("Name");
            options.AddOrUpdateProperty<bool>("Nice");
        }
    );


当然,我们还可以同时为多个类型同时定义一个额外的属性:

ObjectExtensionManager.Instance
    .AddOrUpdateProperty<string>(
        new[]
        {
            typeof(TestA),
            typeof(TestB),
            typeof(TestC)
        },
        "Name"
    );


如果需要定义多个属性,则可以使用 AddOrUpdate

ObjectExtensionManager.Instance
                .AddOrUpdate(options =>
                {
                    options.AddOrUpdateProperty<string>("Name");
                }, new[]{
                    typeof(TestA),
                    typeof(TestB)
                    });


另外它还可以设置默认值、增加验证规则等,这些笔者就不再赘述,读者感兴趣可以点击链接进入官方文档查看。

https://docs.abp.io/zh-Hans/abp/latest/Object-Extensions#validation


相关文章
|
监控 容器
浅入ABP系列(3):增加日志组件、依赖注入服务
浅入ABP系列(3):增加日志组件、依赖注入服务
417 0
|
8月前
|
API
技术文档撰写之道:构建清晰准确的知识传递桥梁
在科技飞速发展的今天,技术文档至关重要。撰写优质文档需明确目的与受众,构建合理结构,使用简洁语言,善用图表示例,并注重更新维护。这不仅助力团队协作和产品推广,也为技术传承奠定基础。
249 1
|
11月前
|
Kubernetes 调度 算法框架/工具
NVIDIA Triton系列02-功能与架构简介
本文介绍了NVIDIA Triton推理服务器的功能与架构,强调其不仅适用于大型服务类应用,还能广泛应用于各类推理场景。Triton支持多种模型格式、查询类型和部署方式,具备高效的模型管理和优化能力,确保高性能和系统稳定性。文章详细解析了Triton的主从架构,包括模型仓库、客户端应用、通信协议和推理服务器的核心功能模块。
458 1
NVIDIA Triton系列02-功能与架构简介
|
存储 API 数据库
如何使用 ef core 的 code first(fluent api)模式实现自定义类型转换器?
本文介绍了如何在 EF Core 的 Code First 模式下使用自定义类型转换器实现 JsonDocument 和 DateTime 类型到 SQLite 数据库的正确映射。通过自定义 ValueConverter,实现了数据类型的转换,并展示了完整的项目结构和代码实现,包括实体类定义、DbContext 配置、Repositories 仓储模式及数据库应用迁移(Migrations)操作。
231 7
如何使用 ef core 的 code first(fluent api)模式实现自定义类型转换器?
|
11月前
|
缓存 网络架构
vue2进阶篇:vue-router之基础路由
vue2进阶篇:vue-router之基础路由
144 4
|
测试技术 Shell 开发工具
Playwright 系列(13):如何调试测试用例
Playwright 系列(13):如何调试测试用例
722 0
Playwright 系列(13):如何调试测试用例
|
负载均衡 调度 Docker
Docker Swarm集群的创建与管理:命令详解
【8月更文挑战第27天】
194 1
|
存储 数据库 开发者
深入浅出讲解Entity Framework Core中的复杂类型与值对象:从理论到实践的全方位指南,附带详实代码示例与最佳应用技巧
【8月更文挑战第31天】本文通过教程形式详细介绍了如何在 Entity Framework Core 中使用复杂类型与值对象,帮助开发者更自然地映射实体和数据库间的关系。文章首先指导创建基于 EF Core 的项目,并添加相关 NuGet 包。接着,通过具体代码示例展示了如何配置数据库上下文、定义领域模型,并使用复杂类型与值对象进行数据存储和查询。最后总结了使用这些技术的优势,包括简化复杂数据结构映射、提高可维护性及数据一致性。
224 0
|
开发框架 JSON 前端开发
利用过滤器Filter和特性Attribute实现对Web API返回结果的封装和统一异常处理
利用过滤器Filter和特性Attribute实现对Web API返回结果的封装和统一异常处理
|
开发框架 JSON 前端开发
基于ABP框架的SignalR,使用Winform程序进行功能测试
基于ABP框架的SignalR,使用Winform程序进行功能测试