使用 Automapper 进行扁平化和派生属性

发布于 2024-10-30 14:06:38 字数 1897 浏览 1 评论 0原文

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

我提出的问题可能更多地与我缺乏 Automapper 和 DDD 的经验有关,因此对于你们中的一些人来说,回答这些问题可能相当容易,并为我指明了正确的方向。我也很有可能完全错误地解决这个问题,但我的方向是基于我在这里读到的内容,有些来自搜索,有些来自问我自己的问题。但我也开始意识到,我看到的一些“建议”是相互矛盾的。

快速了解一下背景,我的域模型只是从 EF4 POCO 模板(贫血)生成的 POCO。这些在我的服务层中使用,并且它们也通过服务层暴露给我的应用程序。我没有任何类型的 DTO,只有我的贫乏域模型、视图模型和 Automapper 来映射两者,根据 我得到的建议。假设它们可以一对一映射,这一切都很好。

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

两个例子:

扁平化

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

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

我不太喜欢这个,因为它似乎违反了 DRY,我越需要这样做,可能会变得有点痛苦。

封装业务逻辑

我还有另一种情况,即不存储 User 属性,而是派生属性,并且需要在返回值之前执行一些逻辑。例如,个人资料图像 URL 将取决于他们是在 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 层,但老实说我也不想走这条路(根据我上面链接的问题,它似乎更容易被接受使用 DTO 和 DDD)。

I'm working on an ASP.NET MVC site, using EF4 and Automapper. I'm making good progress, and feel fairly good for the design (considering this is my first stab at a true DDD architecture). But I'm starting to see some things that I'm not too comfortable with.

The questions I have probably relate more to my lack of experience with Automapper and DDD, so they may be fairly easy for some of you to answer and point me in the right direction. There's also a very good chance I'm going about this completely wrong, but my direction is based on what I'm reading on here, some from searching, and some from asking my own questions. But I'm also starting to realize that some of the "advice" I'm seeing is contradictory to each other.

Quick background, my domain model is simply the generated POCOs from EF4 POCO template (anemic). These are used in my service layer, and they are also exposed to my application via the service layer. I don't have any sort of DTO, just my anemic domain model, view models, and Automapper to map the two, per the advice I was given. This is all fine and dandy assuming they can be mapped one-to-one.

My problems come in when I have to flatten my domain models into one view model, or when I need some business logic to define a view model property.

Two examples:

Flattening

I have two models, User and UserProfile. This is a one-to-one mapping, so it already is logically flat -- it's just in two separate models. My view model needs properties of both User and UserProfile in one object. From what I've seen, Automapper has no easy way to do this, so what I did was extend my User POCO, adding the needed UserProfile properties to it:

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

I'm not a big fan of this as it seems to violate DRY, and may become a bit of a pain the more I have to do this.

Encapsulating business logic

I have another case where a User property isn't stored, but rather derived, and needs to do some logic before returning a value. For example, a profile image URL will depend on whether they registered with Facebook or Twitter. So I have something like this:

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

I'm pretty sure this isn't something that Automapper is responsible for, but I'm not sure if this should be done with a domain model extension if I want to keep it anemic. So I'm not sure where this belongs.

I'm not completely stuck on having my domain model be anemic (I know that's its own debate), but I do anticipate this domain model being used by multiple applications with different view models and validation.

Thank in advance.

UPDATE: In response to @jfar, my main issue with the flattening example is that it seems like this should be something Automapper should be able to do. If not, then I can live with this option. I was just looking for someone to double check my design to make sure there wasn't a better way to do this.

My issue with the encapsulating business logic example, is that I'm violating the anemia of my domain model. In some cases, this may even need to hit the repositories to pull certain data for business logic, and this seems like a bad design to me.

The other thing I'm starting to wonder, is if I shouldn't have a DTO layer, but I honestly don't want to go that route either (and per the question I linked above, it seems to be more accepted not to use DTOs w/ DDD).

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(4

樱娆 2024-11-06 14:06:38
  1. 扁平化:您可以使用 Automapper 映射到现有对象的功能将映射链接在一起。
    链示例
  2. 封装业务逻辑:我真的不明白你的问题。但是,如果您希望 Automapper 负责,请查看之前和之后的映射或自定义解析器。 自定义解析器示例
  1. Flattening: You can chain your mappings together using Automapper's ability to map to an existing object.
    Chain Example
  2. Encapsulatng Business Logic: I don't really see your problem. However, if you want Automapper to be responsible, take a look at before and after maps or custom resolvers. Custom Resolver Example
乖乖哒 2024-11-06 14:06:38

为什么你如此专注于让你的领域模型变得贫乏?在我看来,您的域对象只不过是 DTO。

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

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

Why are you so intent on keeping your domain model anemic? It looks to me like your domain objects are nothing more than DTOs.

The ProfileImageUrl belongs on your domain class that has the necessary information to return the correct URL. I'd imagine this is UserProfile. If a single class doesn't have the needed information thats what a service is for. You either create a method on your service that returns the URL or a composite object like Darin Dimitrov suggested.

Firestrand and Darin Dimitrov's answers are both good ways to accomplish flattening.

很糊涂小朋友 2024-11-06 14:06:38

定义一个重新组合这两个模型的模型:

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>()
      ...

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

Define a model that regroups those two models:

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

then have your service layer return this model. Another possibility if there is a 1-to-1 mapping between User and UserProfile is to define the following model:

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

Then define a view model containing only what is needed by the given view:

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

next define a mapping between your model and your view model:

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

and finally in your controller use the service layer to fetch the model, map it to a view model and pass this view model to the view for rendering, pretty standard stuff.

陈甜 2024-11-06 14:06:38

我搜索并阅读了更多关于扁平化的内容,搜索得越多,就越发现 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

也许这对我来说应该是显而易见的,也许对大多数人来说都是如此。现在我想起来,这是有道理的——这确实是唯一可行的方法。我只是直到现在才弄清楚。

I searched and read about flattening more, and the more I searched, to more it looked like Automapper should be handling this automagically. After playing around a bit, I found a way to get this to work, though I still can't find any examples of doing this, even with Automapper's documentation.

The key is to name the property on the destination object with the fully qualified name of the source object graph.

Source objects:

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

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

Destination objects:

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

So:

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

Maybe this should have been obvious to me, and maybe it is for most people. And now that I think about it, it makes sense -- it's really the only way it could work. I just didn't figure it out until now.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文