DTO 到实体映射工具

2024-03-19

我有一个实体类Person及其对应的DTO类PersonDto.

public class Person: Entity
{
  public virtual string Name { get; set; }
  public virtual string Phone { get; set; }
  public virtual string Email { get; set; }
  public virtual Sex Sex { get; set; }
  public virtual Position Position { get; set; }
  public virtual Division Division { get; set; }
  public virtual Organization Organization { get; set; }
}

public class PersonDto: Dto
{
  public string Name { get; set; }
  public string Phone { get; set; }
  public string Email { get; set; }
  public Guid SexId { get; set; }
  public Guid PositionId { get; set; }
  public Guid DivisionId { get; set; }
  public Guid OrganizationId { get; set; }
}

收到 DTO 对象后,我必须将其转换为人员实体。现在我完全手动完成。代码如下所示。

public class PersonEntityMapper: IEntityMapper<Person, PersonDto>
{
  private IRepository<Person> _personRepository;
  private IRepository<Sex> _sexRepository;
  private IRepository<Position> _positionRepository;
  private IRepository<Division> _divisionRepository;
  private IRepository<Organization> _organizationRepository;

  public PersonEntityMapper(IRepository<Person> personRepository,
                            IRepository<Sex> sexRepository,
                            IRepository<Position> positionRepository,
                            IRepository<Division> divisionRepository,
                            IRepository<Organization> organizationRepository)
  {
    ... // Assigning repositories
  }

  Person Map(PersonDto dto)
  {
    Person person = CreateOrLoadPerson(dto);

    person.Name = dto.Name;
    person.Phone = dto.Phone;
    person.Email = dto.Email;

    person.Sex = _sexRepository.LoadById(dto.SexId);
    person.Position = _positionRepository.LoadById(dto.PositionId);
    person.Division = _divisionRepository.LoadById(dto.DivisionId);
    person.Organization = _organizationRepository.LoadById(dto.OrganizationId);

    return person;
  }
}

该代码实际上很简单。但随着实体数量的增加,映射器类的数量也会增加。结果是有很多类似的代码。另一个问题是,当存在模式关联时,我必须为其他存储库添加构造函数参数。我尝试注入某种存储库工厂,但它闻起来有一种臭名昭著的味道Service Locator所以我恢复了原来的解决方案。

这些映射器的单元测试还会产生许多类似的测试方法。

说了这么多,我想知道是否存在一种解决方案可以减少手动编写的代码量并使单元测试更容易。

提前致谢。

UPDATE

我完成了任务Value Injecter但后来我意识到我可以安全地删除它,其余的仍然可以工作。这是最终的解决方案。

public abstract class BaseEntityMapper<TEntity, TDto> : IEntityMapper<TEntity, TDto>
        where TEntity : Entity, new()
        where TDto : BaseDto
    {
        private readonly IRepositoryFactory _repositoryFactory;

        protected BaseEntityMapper(IRepositoryFactory repositoryFactory)
        {
            _repositoryFactory = repositoryFactory;
        }

        public TEntity Map(TDto dto)
        {
            TEntity entity = CreateOrLoadEntity(dto.State, dto.Id);

            MapPrimitiveProperties(entity, dto);
            MapNonPrimitiveProperties(entity, dto);

            return entity;
        }

        protected abstract void MapNonPrimitiveProperties(TEntity entity, TDto dto);

        protected void MapPrimitiveProperties<TTarget, TSource>(TTarget target, TSource source, string prefix = "")
        {
            var targetProperties = target.GetType().GetProperties(BindingFlags.Public | BindingFlags.Instance).OrderBy(p => p.Name);
            var sourceProperties = source.GetType().GetProperties(BindingFlags.Public | BindingFlags.Instance).OrderBy(p => p.Name);

            foreach (var targetProperty in targetProperties) {
                foreach (var sourceProperty in sourceProperties) {
                    if (sourceProperty.Name != string.Format("{0}{1}", prefix, targetProperty.Name)) continue;
                    targetProperty.SetValue(target, sourceProperty.GetValue(source, null), null);
                    break;
                }
            }
        }

        protected void MapAssociation<TTarget, T>(TTarget target, Expression<Func<T>> expression, Guid id) where T : Entity
        {
            var repository = _repositoryFactory.Create<T>();
            var propertyInfo = (PropertyInfo)((MemberExpression)expression.Body).Member;
            propertyInfo.SetValue(target, repository.LoadById(id), null);
        }

        private TEntity CreateOrLoadEntity(DtoState dtoState, Guid entityId)
        {
            if (dtoState == DtoState.Created) return new TEntity();

            if (dtoState == DtoState.Updated) {
                      return _repositoryFactory.Create<TEntity>().LoadById(entityId);
            }
            throw new BusinessException("Unknown DTO state");
        }
    }  

每个实体的映射是使用派生自的具体类执行的BaseEntityMapper。那个是为了Person实体看起来像这样。

public class PersonEntityMapper: BaseEntityMapper<Person, PersonDto>
    {
        public PersonEntityMapper(IRepositoryFactory repositoryFactory) : base(repositoryFactory) {}

        protected override void MapNonPrimitiveProperties(Person entity, PersonDto dto)
        {
            MapAssociation(entity, () => entity.Sex, dto.SexId);
            MapAssociation(entity, () => entity.Position, dto.PositionId);
            MapAssociation(entity, () => entity.Organization, dto.OrganizationId);
            MapAssociation(entity, () => entity.Division, dto.DivisionId);
        }
    }

显式调用MapAssociation防止将来的属性重命名。


您可以看一下两个最常用的对象-对象映射器:

自动映射器 http://automapper.org/

AutoMapper 是一个简单的小库,旨在解决一个欺骗性的问题 复杂的问题 - 摆脱将一个对象映射到的代码 其他。这种类型的代码写起来相当沉闷和无聊,所以 为什么不发明一种工具来为我们做到这一点呢?

价值注入者 http://valueinjecter.codeplex.com/

ValueInjecter 允许您定义自己的基于约定的匹配 算法(ValueInjections)以匹配(注入)源 值到目标值。

有一篇关于SO的比较文章:AutoMapper 与 ValueInjecter https://stackoverflow.com/questions/4663577/automapper-vs-valueinjecter

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

DTO 到实体映射工具 的相关文章

  • 在结构中使用 typedef 枚举并避免类型混合警告

    我正在使用 C99 我的编译器是 IAR Embedded workbench 但我认为这个问题对于其他一些编译器也有效 我有一个 typedef 枚举 其中包含一些项目 并且我向该新类型的结构添加了一个元素 typedef enum fo
  • 用于检查类是否具有运算符/成员的 C++ 类型特征[重复]

    这个问题在这里已经有答案了 可能的重复 是否可以编写一个 C 模板来检查函数是否存在 https stackoverflow com questions 257288 is it possible to write a c template
  • Asp.NET WebApi 中类似文件名称的路由

    是否可以在 ASP NET Web API 路由配置中添加一条路由 以允许处理看起来有点像文件名的 URL 我尝试添加以下条目WebApiConfig Register 但这不起作用 使用 URIapi foo 0de7ebfa 3a55
  • 类模板参数推导 - clang 和 gcc 不同

    下面的代码使用 gcc 编译 但不使用 clang 编译 https godbolt org z ttqGuL template
  • 如何使用 ICU 解析汉字数字字符?

    我正在编写一个使用 ICU 来解析由汉字数字字符组成的 Unicode 字符串的函数 并希望返回该字符串的整数值 五 gt 5 三十一 gt 31 五千九百七十二 gt 5972 我将区域设置设置为 Locale getJapan 并使用
  • 用于登录 .NET 的堆栈跟踪

    我编写了一个 logger exceptionfactory 模块 它使用 System Diagnostics StackTrace 从调用方法及其声明类型中获取属性 但我注意到 如果我在 Visual Studio 之外以发布模式运行代
  • Clang 3.1 + libc++ 编译错误

    我已经构建并安装了 在前缀下 alt LLVM Clang trunk 2012 年 4 月 23 日 在 Ubuntu 12 04 上成功使用 GCC 4 6 然后使用此 Clang 构建的 libc 当我想使用它时我必须同时提供 lc
  • 如何从 appsettings.json 文件中的对象数组读取值

    我的 appsettings json 文件 StudentBirthdays Anne 01 11 2000 Peter 29 07 2001 Jane 15 10 2001 John Not Mentioned 我有一个单独的配置类 p
  • 不同枚举类型的范围和可转换性

    在什么条件下可以从一种枚举类型转换为另一种枚举类型 让我们考虑以下代码 include
  • 使用 WebClient 时出现 System.Net.WebException:无法创建 SSL/TLS 安全通道

    当我执行以下代码时 System Net ServicePointManager ServerCertificateValidationCallback sender certificate chain errors gt return t
  • 创建链表而不将节点声明为指针

    我已经在谷歌和一些教科书上搜索了很长一段时间 我似乎无法理解为什么在构建链表时 节点需要是指针 例如 如果我有一个节点定义为 typedef struct Node int value struct Node next Node 为什么为了
  • 重载<<的返回值

    include
  • WCF 中 SOAP 消息的数字签名

    我在 4 0 中有一个 WCF 服务 我需要向 SOAP 响应添加数字签名 我不太确定实际上应该如何完成 我相信响应应该类似于下面的链接中显示的内容 https spaces internet2 edu display ISWG Signe
  • 转发声明和包含

    在使用库时 无论是我自己的还是外部的 都有很多带有前向声明的类 根据情况 相同的类也包含在内 当我使用某个类时 我需要知道该类使用的某些对象是前向声明的还是 include d 原因是我想知道是否应该包含两个标题还是只包含一个标题 现在我知
  • 什么时候虚拟继承是一个好的设计? [复制]

    这个问题在这里已经有答案了 EDIT3 请务必在回答之前清楚地了解我要问的内容 有 EDIT2 和很多评论 有 或曾经 有很多答案清楚地表明了对问题的误解 我知道这也是我的错 对此感到抱歉 嗨 我查看了有关虚拟继承的问题 class B p
  • 这些作业之间是否存在顺序点?

    以下代码中的两个赋值之间是否存在序列点 f f x 1 1 x 2 不 没有 在这种情况下 标准确实是含糊不清的 如果你想确认这一点 gcc 有这个非常酷的选项 Wsequence point在这种情况下 它会警告您该操作可能未定义
  • Windows 窗体:如果文本太长,请添加新行到标签

    我正在使用 C 有时 从网络服务返回的文本 我在标签中显示 太长 并且会在表单边缘被截断 如果标签不适合表单 是否有一种简单的方法可以在标签中添加换行符 Thanks 如果您将标签设置为autosize 它会随着您输入的任何文本自动增长 为
  • 如何使用 C# / .Net 将文件列表从 AWS S3 下载到我的设备?

    我希望下载存储在 S3 中的多个图像 但目前如果我只能下载一个就足够了 我有对象路径的信息 当我运行以下代码时 出现此错误 遇到错误 消息 读取对象时 访问被拒绝 我首先做一个亚马逊S3客户端基于我的密钥和访问配置的对象连接到服务器 然后创
  • IEnumreable 动态和 lambda

    我想在 a 上使用 lambda 表达式IEnumerable
  • 使用.NET技术录制屏幕视频[关闭]

    Closed 这个问题正在寻求书籍 工具 软件库等的推荐 不满足堆栈溢出指南 help closed questions 目前不接受答案 有没有一种方法可以使用 NET 技术来录制屏幕 无论是桌面还是窗口 我的目标是免费的 我喜欢小型 低

随机推荐

  • 如何仅使用查找和替换来查找并计算字符串中子字符串的所有出现次数?

    输入需要更低 最后程序必须打印出现的次数 例如内存 smthing str input if smthing smthing lower smthing find mem 我对此彻底崩溃了 所以我无法走多远 我忘了告诉你我不能使用计数或列表
  • openlayers 3 可以使用 gif 渲染动画标记吗

    我想问如何让标记像 openlayers 2 一样显示动画 gif 图片 它可以显示动画标记 我想要的是显示动画 gif 标记而不使标记移动 这可能吗 style anchorXUnits fraction anchorYUnits pix
  • 如何在 Emacs 中为 LaTeX 模式定义两个 ispell 字典?

    我有一份包含许多第二语言引文的文档 我如何将第二语言定义为 Ispell 我在文件顶部有这个 Local IspellDict brasileiro 我尝试了这个 但没有成功 Local IspellDict brasileiro engl
  • Java 中的异步事件调度

    我正在将 C 程序移植到 Java 该程序大量使用委托和委托的BeginInvoke异步通知事件的方法 以数据通信线程为例 它可能必须将其状态以及 GUI 通知给另一个工作线程 在我看来 通知不同班级的各种事件的最佳方法是有一个IClass
  • JavaScript 中的 getElementsByClassName().forEach() 函数不起作用

    我试图使用 JavaScript 通过类名获取 HTML 的每个元素 然后根据 a 中的值更改其高度和宽度range object onchange 浏览器显示错误 document getElementsByClassName forEa
  • VB6 有什么好的 TDD 工具或资源吗?

    是的 我知道我已经落后于时代了 但是我这里有一个古老的 VB6 编辑器应用程序 我相信没有人会很快升级到 NET 它使用了几个第三方 DLL 工具 并且由于它仍然使用旧的 RichEdit 控件 我基本上可以仅用这个工具来创建我自己的 Bu
  • 匹配法语和德语字符的正则表达式

    我正在解析请求参数以查找任何易受攻击的字符以防止 XSS 威胁 我们的网络应用程序支持英语以外的法语和德语 我使用以下正则表达式来实现此目的 但它无法处理法语和德语 a zA Z0 9 r n 对此的任何建议都将受到高度赞赏 p L 将匹配
  • 如何确定 PostScript 字体的高度和深度?

    我正在寻找估计 PostScript 字体高度 上升部分的空间 和深度 下降部分的空间 的 PostScript 代码 字体的边界框可以吗 FontBBox 用于此目的 这是我的问题的一些背景 字体的字形位于基线上 显然 带有下降部分的字形
  • 使用并行赋值交换数组元素

    感兴趣这个问题 https stackoverflow com questions 4181808 swapping in ruby 我玩过一些数组和方法调用的并行赋值 这是一个典型的示例 尝试按数组中的值交换两个成员 deck A B C
  • symfony2创建没有实体类的表单

    使用Symfony2 3 4 我正在尝试创建一个不使用类型的表单 它实际上是一个非常小的表单 只有两个选择从数据库加载它们的选项 到目前为止它有效 我不能做的是获取表单数据 在控制器 提交时 我尝试按照说明进行操作here http sym
  • Firebase Firestore,查询用户朋友的帖子

    我正在寻找使用 Firebase 创建社交媒体源 我的数据结构如下 users uid details friends uid friends sub collection fuid details posts postId postedB
  • lock(){} 是锁定资源,还是锁定一段代码?

    我仍然很困惑 当我们写这样的东西时 Object o new Object var resource new Dictionary
  • 是否可以指定何时运行构建功能?

    TeamCity 8 1 4 内部版本 30168 我注意到构建功能始终作为构建步骤中第一步的第一部分运行 但是是否可以使构建功能运行 例如作为步骤2的第一部分 或者作为 Step1 的最后一部分 任何帮助表示赞赏 如果感兴趣 这是我的具体
  • 如何在 R 数据框中用 NA 替换空字符串?

    我的第一个方法是使用na strings 当我从 csv 读取数据时 由于某种原因这不起作用 我也尝试过 df df lt NA 这给了我一个错误 不能使用矩阵或数组进行列索引 我只尝试了该专栏 df col df col lt NA 这会
  • 试飞已过期

    我正在尝试使用试飞 https developer apple com app store Testflight 新的 分享我的应用程序 我现在已经与我的 3 位内部测试人员分享了iTunes连接 https itunesconnect a
  • JPA:查询实体内的可嵌入列表

    我正在尝试根据实体列表中的某些条件 提取 可嵌入类 借助 JPQL 或 Criteria API 我不是这方面的专家 所以请帮助我 已经用谷歌搜索了 4 个小时 没有任何结果 这些是课程 Entity public class PostOf
  • 强化修复经常被误用的身份验证

    当我使用 Fortify 进行扫描时 我在下面的代码中遇到了诸如 经常被误用 身份验证 之类的漏洞 这个问题有解决办法吗 我看过相关帖子 但没能找到解决方案 使用 ESAPI 我提供了主机名和 ipadress 的正则表达式 但它不起作用
  • 在php中获取上个月的日期

    我想知道上个月的日期 我写了这个 prevmonth date M Y 这给了我当前的月份 年份 我不知道是否应该使用strtotime mktime 有什么关于时间戳的吗 我是否需要在之后添加一些内容来重置 以便我网站上的所有时间戳的日期
  • F# 中的对象表达式和捕获状态

    是什么让第一次实施KO type IToto abstract Toto unit gt unit new IToto with member this Toto fun gt new IToto with member this Toto
  • DTO 到实体映射工具

    我有一个实体类Person及其对应的DTO类PersonDto public class Person Entity public virtual string Name get set public virtual string Phon