使用Automapper进行展平和派生的属性

时间:2011-04-04 17:28:03

标签: asp.net-mvc entity-framework design-patterns domain-driven-design automapper

我正在使用EF4和Automapper在ASP.NET MVC网站上工作。我取得了很好的进展,并且对设计感觉相当好(考虑到这是我对真正的DDD架构的第一次尝试)。但我开始看到一些我不太满意的事情。

我可能提出的问题可能与我对Automapper和DDD缺乏经验有关,因此对于一些人来说,他们可能相当容易回答并指出我正确的方向。还有一个非常好的机会我完全错了,但我的方向是基于我在这里阅读的内容,一些来自搜索,一些来自我自己的问题。但我也开始意识到我看到的一些“建议”是相互矛盾的。

快速背景,我的域模型只是从EF4 POCO模板生成的POCO(贫血)。这些在我的服务层中使用,它们也通过服务层公开给我的应用程序。根据{{​​3}},我没有任何类型的DTO,只有我的贫血域模型,视图模型和Automapper来映射这两者。假设它们可以一对一映射,这一切都很好,花花公子。

当我必须将我的域模型展平为一个视图模型,或者当我需要一些业务逻辑来定义视图模型属性时,我的问题就出现了。

两个例子:

压扁

我有两个模型,User和UserProfile。这是一对一的映射,因此它已经在逻辑上是平的 - 它只是在两个单独的模型中。我的视图模型需要在一个对象中同时使用User和UserProfile的属性。从我所看到的,Automapper没有简单的方法来做到这一点,所以我做的是扩展我的用户POCO,向它添加所需的UserProfile属性:

public string UserName
{
    get { return UserProfile.UserName; }
}

我不是这方面的忠实粉丝,因为它似乎违反DRY,并且我可能会变得有点痛苦。

封装业务逻辑

我有另一种情况,其中没有存储User属性,而是派生,并且在返回值之前需要做一些逻辑。例如,个人资料图片网址取决于他们是在Facebook还是Twitter注册。所以我有这样的事情:

public string ProfileImageUrl
{
    get
    {
        if (User.TwitterId != null)
        {
            // return Twitter profile image URL
        }
        if (User.FacebookId != null)
        {
            // return Facebook profile image URL
        }
    }
}

我很确定这不是Automapper负责的事情,但我不确定如果我想让它保持贫血,是否应该使用域模型扩展。所以我不确定它属于哪里。

我并不是完全坚持让我的域模型变得贫乏(我知道这是它自己的争论),但我确实预计这个域模型会被具有不同视图模型和验证的多个应用程序使用。

提前感谢。

更新:为了回应@jfar,我的主要问题是展平示例,看起来这应该是Automapper能够做的事情。如果没有,那么我可以使用这个选项。我只是想找人来仔细检查我的设计,以确保没有更好的方法来做到这一点。

我对封装业务逻辑示例的问题是,我违反了我的域模型的贫血症。在某些情况下,甚至可能需要访问存储库来为业务逻辑提取某些数据,这对我来说似乎是一个糟糕的设计。

我开始怀疑的另一件事是,如果我不应该有一个DTO层,但我老实说也不想走那条路(而且根据我上面提到的问题,似乎更多接受使用带有DDD的DTO。)

4 个答案:

答案 0 :(得分:1)

  1. 展平:您可以使用Automapper映射到现有对象的能力将映射链接在一起。 Chain Example
  2. 封装业务逻辑:我真的没有看到你的问题。但是,如果您希望Automapper负责,请查看地图或自定义解析器之前和之后的内容。 Custom Resolver Example

答案 1 :(得分:1)

为什么你这么想保持你的领域模式贫血?在我看来,你的域名对象只不过是DTO。

ProfileImageUrl属于您的域类,其中包含返回正确URL的必要信息。我想这是UserProfile。如果单个类没有所需的信息,那就是服务的用途。您可以在服务上创建一个返回URL或Darin Dimitrov建议的复合对象的方法。

Firestrand和Darin Dimitrov的答案都是实现扁平化的好方法。

答案 2 :(得分:0)

定义重组这两个模型的模型:

public class UserWithProfile
{
    public User User { get; set; }
    public UserProfile Profile { get; set; }
}

然后让您的服务层返回此模型。如果User和UserProfile之间存在一对一映射,则另一种可能性是定义以下模型:

public class UserWithProfile: User
{
    public UserProfile Profile { get; set; }
}

然后定义一个仅包含给定视图所需内容的视图模型:

public class SomeUserViewModel
{
    ... only properties needed by the given view
}

接下来定义模型和视图模型之间的映射:

Mapper.CreateMap<UserWithProfile, SomeUserViewModel>()
      ...

最后在您的控制器中使用服务层来获取模型,将其映射到视图模型并将此视图模型传递给视图以进行渲染,非常标准的东西。

答案 3 :(得分:0)

我搜索并阅读了更多关于展平的信息,我搜索得越多,看起来Automapper看起来应该自动处理这个问题。在玩了一下之后,我找到了一种方法来实现这一点,尽管我仍然找不到任何这样做的例子,即使使用Automapper的文档。

关键是使用源对象图的完全限定名称命名目标对象上的属性。

源对象:

class User
{
    int Id { get; set; }
    FacebookUser FacebookUser { get; set; }
}

class FacebookUser
{
    string UserName { get; set; }
}

目标对象:

class UserViewModel
{
    int Id { get; set; }
    string FacebookUserUserName { get; set; }
}

所以:

UserViewModel.Id -> User.Id
UserViewModel.FacebookUserUserName -> User.FacebookUser.UserName

也许这对我来说应该是显而易见的,也许这对大多数人来说都是如此。现在,我想到它,它是有道理的 - 它确实是唯一可行的方式。直到现在我才想出来。