【问题标题】:Patterns for mapping data between domain models在域模型之间映射数据的模式
【发布时间】:2012-01-08 01:30:53
【问题描述】:

这是我最近需要做的一件常见的事情,我正在寻找任何常见的模式来让这更容易一些。

这一切的主要要点是我有一些数据模型,它们被建模以满足 ORM 并纯粹对对象进行 CRUD 操作。这些模型目前通过存储库/工厂公开(取决于它的 C 或 RUD)。

然后我有一个视图模型,它更具可读性,并且散布着 UI 问题,例如视图之间的验证和映射数据(这是一个 ASP.MVC 场景,但这种情况可以抽象为大多数情况)。

假设我转到 localhost/user/1,应该会在 DB 中获取 ID 为 1 的用户,然后将其显示在 UI 上。最终,这必须从数据域中提取数据,然后将其映射到 ui 模型以进行显示。

这是一个示例场景:

public class OrmUser
{
    public int Id {get;set;}
    public string Name {get;set;}
    public IList<Permission> Permissions {get;set;}
}

public class UiUser
{
    [Required]
    public int Id {get;set;}
    [Required]
    public string Name {get;set;}
    public bool IsUserAdmin {get;set;}
}

public class UserMapper : IMapper<UiUser>
{
    public UiUser Get(int id)
    {
        var ormUser = UserRepository.Get(id);
        var uiUser = new UiUser
        {
            Id = ormUser.Id,
            Name = ormUser.Name,
            IsUserAdmin = IsUserAdmin(ormUser.Permissions)
        }
    }

    private bool IsUserAdmin(IList<Permission> permissions)
    {
        return permissions.SomeLinq(ToFindIfTheyAreAnAdmin);
    }
}

这是一个简单的示例,但显示了数据模型如何包含许多相同类型的信息,但在这个有问题的视图中,您并不关心所有信息,只是其中的一个子集。通过这种方式,拥有一个映射器,您不仅可以抽象映射,还可以抽象与数据域的通信,但是您需要为每种类型编写一个映射器类,上面假设它是单向映射,而不是 2 向映射需要更多代码。

那么你们是如何进行这种映射的呢?目前我刚刚在编写抽象映射器,它基本上允许 UI 层运行查询,并取回视图模型,抽象存储库并将数据从一个模型复制到另一个模型,感觉应该有一个更好的方法来做到这一点。

【问题讨论】:

  • 对我来说,guestion 很抽象,你到底想映射什么?
  • 嗯,它不是一个特定的模型,它意味着有点抽象。我敢肯定,如今大多数 Web 应用程序都有多个层/域,其中包含相似的模型来表示相同类型的数据。我只是在寻找一种很好的模式来管理层之间的映射和通信。将在我最初的问题中添加一些虚拟模型,以帮助了解我所追求的目标,尽管到目前为止 Paul 已经给出了一个很好的答案。
  • 另外值得注意的是,虽然这是一个 .net 示例,但实际上任何语言都可能发生相同的情况。因此,任何与语言无关的模式都是理想的,尽管为给定平台简化这项任务的框架也很好......
  • 考虑到 Automapper 将是一个不错的选择
  • @Arnis L. 与仅处理您的数据源(即 Nhibernate、MongoDb 等)有关的类和模型的逻辑分组。因为您不会使用相同的模型将实体持久保存在 Nhibernate 中会将它们显示到用户界面,因此数据/用户界面域的区别。

标签: design-patterns viewmodel models


【解决方案1】:

在这种特殊情况下,我可能只是在我的表示层中为 User 编写一个扩展方法。

public static class UserPresentationExtensions{
 public bool IsAdmin(this User user){
  return permissions.SomeLinq(ToFindIfTheyAreAnAdmin);
 }
}

视图看起来像这样:

@model User
<fieldset>
 <legend>User: @Model.Name</legend>
 @if(user.IsAdmin()){
  User is an admin
 }
</fieldset>

为避免重复导入命名空间,请使用 web.config:

<configuration>
 <system.web.webPages.razor>
  <pages pageBaseType="System.Web.Mvc.WebViewPage">
   <namespaces>
    <add namespace="MyProject.PresentationExtensions" />
   ...

您说得有道理,但是假设您想为模型添加验证,因此您决定将 [Required] 属性添加到您的 Name 属性。虽然如果您使用共享模型,那么您的数据层需要了解注释,并且如果您的数据层必须具有某些属性,那么您的 UI 层将需要了解它们。在最简单的情况下,你是对的,有时只有 1 个模型是真实的,然后以不同的方式访问它会更容易,但对于大多数复杂的项目,你会发现自己弄脏了双方以尝试保存几行代码。

我说 - 在这种特殊情况下。这种方法仅适用于映射不值得并且只有单向通信(您只需要渲染它)的情况。

在接收帖子时,情况会有所不同。大多数“一刀切”的方法将应用所谓的Thunderdome principle。那就是:

所有 Controller 方法都接受一个 ViewModel 对象(或在某些情况下为零个对象)并返回一个 ViewModel 对象(一个对象进入,一个对象离开)。

但是,我通常更喜欢使用扩展/html 辅助方式,只需将相应的参数传递给这样的操作:

public void BatheCat(int id /* cat id */, int bathId, string shampoo){
 ...
}

如果参数计数失控(我不打扰它here's an example 来自我的项目)。

【讨论】:

  • 这样你需要为你想要映射的几乎每个模型或字段编写一个扩展方法或某种自定义逻辑。虽然它解决问题的有效方法,但我只是认为当您开始在不同领域拥有 10 多个模型时,它不会导致可管理的代码库。
  • @Grofit 有时这比映射看起来与域模型相同的视图模型要简单得多。这就是我的观点。
  • 您说得有道理,但是假设您想为模型添加验证,因此您决定将 [Required] 属性添加到您的 Name 属性。虽然如果您使用共享模型,那么您的数据层需要了解注释,并且如果您的数据层必须具有某些属性,那么您的 UI 层将需要了解它们。在最简单的情况下,你是对的,有时只有 1 个模型是真实的,然后以不同的方式访问它会更容易,但对于大多数复杂的项目,你会发现自己弄脏了双方以尝试保存几行代码。
【解决方案2】:

是的。 AutoMapper 是适合您的工具。有大量关于此的文章,但请转到来源 - Jimmy Bogard。他是 AutoMapper 背后的主脑。

看这篇博文http://lostechies.com/jimmybogard/2009/06/30/how-we-do-mvc-view-models/

AutoMapper 现在以一种非常有效的方式支持 MVC,您可以使用属性来装饰您的类,说明应该进行哪些映射。

/最好的问候马格纳斯

【讨论】:

  • 向双方添加属性会更脏,我试图为双方提供一个干净的抽象。尽管您适合 .net,但似乎自动映射器是选择。希望有更多与语言无关的解决方案,但我猜 AutoMapper 和 ValueInjector 等背后的原理可以应用于其他语言/平台。
  • 我没有使用 Jimmys 最新的 MVC 插件,所以你可以使用 Attributes。是的,它有点脏。它还取决于您的域模型与您的视图模型的相似程度。如果您需要帮助 AutoMapper 很多,最好跳过属性并像平常一样使用 AutoMapper(映射配置文件)。但是映射对我来说是跨领域的关注点,所以我将映射移到一个单独的项目中并将其包装到 Facade (IAssamble) 中。然后,您可以将该 IAssemble 服务接口注入您想要的位置。我你喜欢替换 AutoMapper - 去做吧。但 IAssemble 接口仍然可以完好无损。
【解决方案3】:

在 .net 中,您可能应该看看 Automapper

https://github.com/AutoMapper/AutoMapper

它本质上允许你做:

CreateMap<Domain.Customer, ViewModel.Customer>()

然后您可以通过以下方式在两者之间进行映射:

var vmCustomer = Mapper.Map<Domain.Customer, ViewModel.Customer>(domainCustomer);

它可能会为您节省大量样板代码。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2021-02-03
    • 1970-01-01
    • 2015-11-12
    • 1970-01-01
    • 2011-07-15
    • 2014-01-23
    • 2010-12-17
    相关资源
    最近更新 更多