【问题标题】:EF Entities vs. Service Models vs. View Models (MVC)EF 实体与服务模型与视图模型 (MVC)
【发布时间】:2011-07-04 09:51:28
【问题描述】:

我正在尝试了解和找出设计应用/域模型 (POCO/DTO) 的良好做法。

假设我有以下数据库表,帐户:

UserID int
Email varchar(50)
PasswordHash varchar(250)
PasswordSalt varchar(250)

当然,EF4 会像这样构建实体:

public class Account
{
    public int UserID { get; set; }
    public string Email { get; set; }
    public string PasswordHash { get; set; }
    public string PasswordSalt { get; set; }
}

现在,假设我有一个用于注册新用户的视图模型,它可能看起来像这样:

public class RegistrationViewModel
{
    public string Email { get; set; }
    public string Password { get; set; }
}

最后,我有一个需要注册用户的服务:

public class RegistrationService
{
    public void RegisterUser(??? registration)
    {
        // Do stuff to register user
    }
}

我正在尝试找出要传递给 RegisterUser 方法的内容。视图模型当然位于我的 Web 应用程序(表示层)下,所以我不希望它被传递给我的服务。

所以,我在考虑以下四种可能性之一:

1) 设置一个与 RegistrationViewModel 相似(如果不相同)的服务模型,并使用它:

public class RegistrationServiceModel
{
    public string Email { get; set; }
    public string Password { get; set; }
}

public class RegistrationService
{
    public void RegisterUser(RegistrationServiceModel registration)
    {
        // Do stuff to register user
    }
}

2)设置模型的接口,并在我的视图模型中继承这个,并设置我的方法接受接口:

public interface IRegistrationModel
{
    string Email;
    string Password;
}

public class RegistrationServiceModel : IRegistrationModel
{
    public string Email { get; set; }
    public string Password { get; set; }
}

public class RegistrationService
{
    public void RegisterUser(IRegistrationModel registration)
    {
        // Do stuff to register user
    }
}

3) 传入 Account 实体,在我的控制器中进行 RegistrationViewModel 到 Account 的映射:

public class RegistrationService
{
    public void RegisterUser(Account account)
    {
        // Do stuff to register user
    }
}

4) 将我的视图模型从表示中移到域/服务层,并将其传递给服务方法:

public class RegistrationService
{
    public void RegisterUser(RegistrationViewModel account)
    {
        // Do stuff to register user
    }
}

这三种情况似乎都不理想,因为我发现每种情况都存在问题。所以我想知道是否还有其他我想不到的方法。

对此有什么好的做法?

提前致谢。

【问题讨论】:

  • 顺便说一句,杰拉德,你认为第三种情况到底是什么问题?
  • @Vasilio Ruzanni - 我的问题(以及我听到其他人所说的)与该选项是您的服务/业务层与您的数据模型(EF4 实体)紧密耦合,这可能会改变比业务/领域模型更频繁(抱歉混合术语,我仍然不了解这些差异)。
  • 好吧,我看到了您的问题,您正在尝试解决“可能出现但不存在”的问题。您总是必须在复杂性和可用性之间做出平衡。当然,您的 RegistrationModel 方法将所有内容抽象出来,但它不会使您的系统更易于维护(这是过度工程的威胁信号)。
  • 要点,简短地说:不要过度设计,您的 EF 实体是您的域模型的 THE 核心!域服务也是您域的一部分。不要从域模型中隐藏域模型。如果您更改您的实体,您将必须更改服务,因为您的实体和服务在概念上是绑定的。因此,我认为RegistrationService.RegisterUser(Account account, string password) 方法是足够 的理想选择(是的,主要强调足够)。
  • @Vasilio - 谢谢,我慢慢明白你的意思了。我已经开始沿着这条路线走(使用 AutoMapper),这似乎是有道理的。感谢您的帮助!

标签: asp.net-mvc entity-framework viewmodel


【解决方案1】:

您永远不会将视图模型传递给服务。服务甚至不知道您可能已在表示层中定义的视图模型的存在。服务与域模型一起工作。
使用Auto mapper 在视图模型和域模型之间进行映射,反之亦然。

就我个人而言,我从未听说过 DDD(服务视图模型)中的服务模型。

【讨论】:

  • 我的术语可能不准确。那么,在 DDD 中,我不应该采用这种方法吗?或者,您只是在 DDD 中说,它们不称为“服务模型”吗?
  • 在这种特殊情况下唯一的问题是域对象没有明文密码属性,所以没有什么可以映射它。
  • @Jerad,是的,不要采取那种方法。
  • @Yakimych,你映射你所拥有的......然后你调用另一个服务来获取密码哈希和密码盐,然后你将修改后的域对象发送到注册服务。自动映射器不是必须的。如果在这种特殊情况下更容易,您可以进行手动映射。
  • 知道这是旧的,但我对这个解决方案有疑问:在我们的领域模型中,我们对构造函数应用了几个验证,以确保对象状态。 ViewModel 并不总是拥有创建整个实体所需的所有数据,因此我可以将其传递给服务。在这种情况下,我们能做什么?我总是遇到这个问题,直到现在,我都在使用“ApplicationServices”,因为它们位于 App 域中。但过了一段时间,结果证明这并不是最优的……它看起来不对。
【解决方案2】:

使用 3rd 选项,当然。正如 šljaker 所说,Service 应该不知道应用程序的表示部分(您的 ViewModel 是其中的一部分)。

当然,也不要通过包含大量过渡模型(例如 RegistrationServiceModel 或更糟糕的 - IRegistrationModel(最后一个模型有一天会导致“界面爆炸”)来使事情变得过于复杂。

所以:

  1. 拥有一个域实体(使用 Entity Framework 或 NHibernate 或 NoRM 等持久化的 POCO 实体)。
  2. 在给定的上下文中拥有一个代表您的域模型的 ViewModel。如有必要,请不要犹豫,为每个控制器操作创建一个 ViewModel。严格的 ViewModel(与您的视图 1:1 的那些)的副作用是完全没有过度发布和发布不足的问题。不过,这取决于您的具体情况/口味。
  3. 将 DataAnnotation 属性与您的 ViewModel 一起使用以提供基本验证(请记住也要验证业务规则,但它应该位于线路后面 - 在 Services/Repositories 层内)。
  4. 不要让应用服务知道 ViewModel。创建一个域实体实例并将其提供给服务(以验证/保持)。
  5. 使用 AutoMapper 作为选项将您的域实体快速映射到 ViewModel。
  6. 在控制器操作或自定义 IModelBinder 中将传入的 ViewModelFormCollection 映射到您的实体。
  7. (可选)我建议关注Thunderdome Principle。这是 ViewModel 的一种非常非常方便的用法。

【讨论】:

  • @Vasilio - 与上述相同的评论。在这种特殊情况下,实体不包含明文密码,因此 AutoMapper(或任何工具或手动代码)无需映射。
  • @Yakimych,直到我写了我的评论才看到它。关于 AutoMapper - 您可以使用 AutoMapper 进行自定义投影。此外,正如我理解的问题 - 它本质上是关于共同使用 ViewModels 的“最佳实践”。所以AccountUserService 只是一个例子。在大多数情况下,AutoMapper 避免样板代码的东西。
  • @Vasilio - 我基本同意你的观点,是的,你可以调整自动映射器以投影到不同的属性上。但是再次在这种特殊情况下,没有纯文本密码的属性根本。您实际上需要运行业务逻辑才能生成盐并对密码进行哈希处理,因此您需要在服务层的某处为明文密码腾出空间。 IMO 在这种情况下 DTO 效果最好。至于其余的,正如我所提到的,我大多同意。
  • @Yakimych,肯定也是 is 方法,使用特殊的 Dto 进行网络传输,我必须声明它非常依赖于上下文和服务隔离要求。在 MVC 中,我更喜欢将域实体直接提供给应用服务,但我也喜欢 DTO 方法(除了它是另一个要与现有域实体和几个 ViewModel/EditModel 一起维护的对象)。
  • @Yakimych, MembershipService.RegisterUser(user, password)
【解决方案3】:

在这种情况下,使用 DTO(数据传输对象)非常有意义。您可以在服务层创建一个AccountDto 类,并使用它将注册数据向下传递给服务。在某些情况下,它可能类似于 ViewModel,但通常您可以在视图中显示比创建用户所需的更多内容。为了进一步说明这一点,您的 ViewModel 可能至少看起来像这样:

public class RegistrationViewModel
{
    [Required]
    public string Email { get; set; }

    [Required]
    public string Password { get; set; } 

    [Required]
    [Compare("Password")]
    public string RepeatPassword { get; set; } 
}

而您的 DTO 只需要 EmailPassword 属性。

public class AccountDto
{
    public string Email { get; set; }
    public string Password { get; set; }
}

如您所见,ViewModel 仅包含View 所需的数据。电子邮件验证和密码比较逻辑发生在您的 Web 层中。您使用 DTO 仅获取服务的电子邮件和密码。然后在服务层对密码进行哈希处理,填充实体对象并将值保存到数据库中。

【讨论】:

  • AccountDto 真的有必要吗?通常,注册后,用户会被重定向到个人资料页面。这意味着您必须在 AccountDto 中包含 UserID 并执行以下操作:RegistrationViewModel -> AcountDto -> Acount -> 将值保存到 db -> Acount -> AcountDto -> 返回控制器。接缝有点矫枉过正。
  • @šljaker - 这不是必需的,但在应该运行业务逻辑以从 ViewModel 填充部分域对象的情况下,这是完全合理的。关于另一种说法,我认为没有理由使用 Dto 来取回帐户。 Account account = accountService.RegisterAccount(accountDto); - 你得到你的域对象。它包含你需要的一切。
  • @Yakimych 那么您需要将注册视图模式转换为 AccountDTO 吗?而在服务层,你必须将账户 DTO 转换为账户域模型?对吗?
猜你喜欢
  • 1970-01-01
  • 2012-07-03
  • 2011-07-22
  • 1970-01-01
  • 2020-06-20
  • 1970-01-01
  • 2011-08-24
  • 1970-01-01
  • 2012-08-23
相关资源
最近更新 更多