39

我目前正在开发一个 ASP.NET MVC 项目。

团队中的一些开发人员希望将自动生成的数据库实体直接绑定到视图。

其他开发人员想要创建定制的 ViewModel 并将它们绑定到 Views。

客观地说,这两种方法的优缺点是什么?

(“数据库实体”是指 ORM 框架生成的自动生成的类,例如 LINQ to SQL、Entity Framework 或 LLBLGen)。

4

8 回答 8

50

绝对在您的视图中使用视图模型,并使用类似的东西AutoMapper轻松地从实体创建视图模型。

缺点:

  1. 有时感觉就像在复制代码,特别是当视图模型和实体具有完全相同的属性时

优点:

  1. 您经常需要以更简单的格式(通常称为展平)来表示对象,但您需要在服务器端完全保真。这使您可以在两者之间进行转换,而不会用演示文稿弄乱您的域模型。
  2. 聚合根通常有许多与特定视图无关的值对象和附加实体,在视图模型中省略它们可以使其更易于使用。
  3. 您的实体将有许多在 API 方面合理的双向引用,但在为 JSON、XML 等序列化它们时会创建纯粹的地狱。视图模型将消除这些循环引用。
  4. 您可能经常使用相同的实体,但以不同的方式用于不同的视图。试图在一种类型上平衡两种需求可能会造成巨大的混乱。
于 2012-06-07T08:39:53.367 回答
16

正统的观点是永远不要在视图中使用原始数据库实体。像任何规则一样,如果您非常了解您的实体并了解其后果,那么它就会被打破,但是有很好的理由不打破该规则,特别是在团队合作和未来将由人们维护的代码时谁可能不像你那样理解规则或实体。主要原因是:

  1. ORM 延迟加载。假设您的客户有一个延迟加载的集合 Orders。您将 Customer 传递给 View,它会遍历 Orders。您会在 Orders 表上获得 N*1 选择。但这也意味着你的数据库连接仍然需要在 View 中打开。有一种模式是人们使用“Transaction per Action”来处理 Action_Executed 事件中的数据库上下文,该事件发生在您的视图呈现之前。因此,您可能会在处理完数据库后尝试访问它。即使您现在不这样做,将来有人可能会决定实施该模式,因为它很流行。

  2. ViewModel 的关注点与 db Model 不同。例如,您通常使用验证属性装饰您的 ViewModel 属性。这些通常是不同的,或者只涉及 UI 而不是数据库。如果您绑定到数据库实体,您会发现所有这些 UI 问题都会污染您的数据库实体。

  3. 与 2 相关 - ViewModel 的要求可能需要计算或派生的属性。例如,由名字和姓氏构成的全名。这些东西最好保存在 ViewModel 中。

  4. 您可以独立于数据库对 ViewModel 进行单元测试。ViewModel 最终可能包含大量需要进行单元测试的逻辑。如果它不绑定到您的数据库(与 EF 实体一样),这更容易测试。

一般来说,创建和维护 ViewModels(即使没有 AutoMapper)不是开销,你会发现它是一个整体上更好的开发模式。除了最简单的情况(例如,静态数据的查找列表),我会推荐它。

于 2012-06-07T08:51:58.873 回答
12

我相信使用视图模型是唯一的方法,因此 ORM 实体没有优点:) 视图模型不仅为视图提供数据,而且还定义视图的外观(通过定义模板)或它应该如何验证(通过添加数据注释或实现 IDataErrorInfo)。

使用视图模型:

优点:

  • 视图模型只包含视图所需的属性,没有别的。
  • 视图模型可能包含使用数据注释或 IDataErrorInfo 的特定验证规则。
  • 视图模型可以组合来自不同数据库实体的值。
  • 视图模型自己记录,不依赖于任何框架。
  • 视图模型可以保护您免受伪造的 POST,其中包含未以表单提供但包含在 ORM 实体中的值。
  • 您可以轻松地为视图模型指定显示模板,并在许多地方使用DisplayForEditorFor助手重用它们。

使用 ORM 实体:

缺点:

  • ORM 实体已经包含数据注释,这可能会打乱您的验证。示例:用户中的密码字段可能标记为Required,但仅更改基本用户信息时不需要。
  • ORM 实体与框架(Entity Framework)有很强的联系,可能不容易在其中实现规则。
  • ORM 实体可以包含多个视图的属性,但很难区分不同视图的验证规则。
  • 使用带有延迟加载的 ORM 实体会导致您在呈现视图时执行 SQL 查询。它不应该发生。
  • 使用 ORM 实体可能会导致使用大型 SQL 查询而不是小型 SQL 查询。当您想显示包含名字和姓氏的下拉列表时,您应该只从数据库中检索名字和姓氏,而不是整个实体。
于 2012-06-07T09:00:32.153 回答
8

到目前为止,感谢您的回答——它们对理解这两种方法的优缺点有很大帮助。我要补充一件事,其他人没有提到。

过度发布攻击

直接绑定数据库实体的一个令人担忧的缺点是“过度发布攻击”。这是攻击者使用不比 FireBug 更高级的工具的地方,可以插入不打算由用户编辑但确实存在于 DB 实体上的表单字段。

考虑一个“编辑我的个人资料”页面。您的视图可能如下所示:

@using(Html.BeginForm() {
  <div>
    @Html.LabelFor(x=> x.FirstName)
    @Html.TextBoxFor(x=> x.FirstName)
  </div>
  <div>
    @Html.LabelFor(x=> x.LastName)
    @Html.TextBoxFor(x=> x.LastName)
  </div>

  <input type="Submit" value="Save" />
}

它将呈现以下 HTML:

<form action="/profile/edit" method="post">
  <div>
    <label for="FirstName">FirstName</label>
    <input type="text" name="FirstName" value="" />
  </div>
  <div>
    <label for="LastName">LastName</label>
    <input type="text" name="LastName" value="" />
  </div>

  <input type="Submit" value="Save" />
</form>

使用 FireBug,攻击者只需在表单中插入一段 HTML:

  <input type="hidden" name="IsAdmin" value="true" />

...突然间,用户能够以非常意想不到和有害的方式更改数据。

这里有一些更可怕的隐藏表单域:

  <input type="hidden" name="ShoppingCart.Items[0].Price" value="0.01" />
  <input type="hidden" name="BankAccount.Balance" value="1000000" />
  <input type="hidden" name="User.Administrator.Password" value="hackedPassword" />

哎哟!

信息取自: http ://hendryluk.wordpress.com/tag/asp-net-mvc/

于 2012-06-07T21:59:38.560 回答
5

我曾经尝试开发一个直接在 ASP.NET 视图中使用 NHibernate 实体的应用程序。我遇到了许多延迟加载和延迟 SQL 执行的问题,这些问题直接从视图运行,而不是在业务逻辑层甚至控制器中运行。迁移到视图模型并使用自动映射器似乎解决了所有这些问题,并使应用程序更易于测试、调试和维护。

我还发现视图模型有助于在页面上保存我需要的所有相关数据。一些开发人员喜欢为此使用动态 ViewBag,但这不利于测试和调试。

特别是,当您想从下拉列表中选择关联实体时,视图模型让您变得很容易。

AutoMapper 是这个项目的救星,因为它省去了编写大量映射代码的工作,我所要做的就是创建视图模型,然后将控制器从实体自动映射到视图模型。

于 2012-06-07T09:41:51.220 回答
3

不要向客户端公开后端实体。现实世界的应用程序有行为 - 而不是 CRUD。如果您将实体数据绑定到视图,那么当客户端需要行为时,您深入研究泥泞的黑客只是时间问题。

于 2012-06-07T11:43:21.223 回答
2

我正要添加与 hackedbychinese 完全相同的情绪。我还要补充一点,使用 fk 来查找列表,您只需要使用视图模型,因为实体模型只会在该表中保存一个指向单个 id 的指针。视图模型允许您将所需的填充列表传递到视图中 - 瞧。

此外,视图模型可以在需要时包含谨慎的逻辑,这绝对不是实体模型的情况。此外,您的验证可能会因视图的使用而异,因此每个“视图”要求可以应用不同的验证。

ViewModel 的目的主要是关注点分离——将 View 与 Model 的实现细节解耦。

于 2012-06-07T08:47:01.433 回答
0

在您的视图中使用数据库实体,尤其是您的表单是一个巨大的安全问题。取以下 POCO 对象

public class User
{
    public int Id { get; set; }
    public string Username { get; set; }
    public string Email { get; set; }
    public bool IsAdmin { get; set; }
}

现在假设您正在展示一个允许用户更改其电子邮件的视图。使用 Db 实体而不是视图模型时处理表单结果的 MVC 方法如下所示:(除非您不使用模型绑定,在这种情况下,您需要为自己做更多的工作)

public class HomeController : Controller
{
    [HttpPost]
    public ActionResult ChangeEmail(User user)
    {
        //....
    }
}

Asp.net 中的模型绑定通过查找与模型中的属性名称匹配的 GET 或 POST 参数来工作。因此,用户只需添加IsAdmin=truePOST 参数和 viola,传入ChangeEmail函数的模型会将 IsAdmin 属性设置为 true,这很容易被意外添加到数据库中,让用户可以自由访问更改数据他们无权改变。

这适用于用户权限、更改谁拥有实体(使您的问题与我而不是您相关联)、更改原始创建日期等...

于 2012-06-14T03:58:42.223 回答