ASP.NET MVC - 数据库实体还是视图模型?

41

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

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

其他开发人员则希望创建量身定制的ViewModel,并将其绑定到视图。

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

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

8个回答

51

一定要在视图中使用视图模型,并使用类似于AutoMapper的东西来轻松地从实体创建视图模型。

缺点:

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

优点:

  1. 通常需要以更简单的格式表示对象(经常称为展平),但在服务器端需要完全保真。这允许你在不混杂领域模型与显示垃圾的情况下在两者之间过渡。
  2. 聚合根通常具有大量值对象和其他与特定视图无关的实体,在视图模型中省略它们使得更容易处理。
  3. 您的实体将具有许多在API方面合理的双向引用,但在将它们序列化为JSON、XML等时会创建纯粹的麻烦。视图模型将消除这些循环引用。
  4. 您可能经常以不同的方式在不同的视图中使用相同的实体。试图在一个类型上平衡这两种需求可能会造成巨大的混乱。

16
传统观点认为,在视图中不应该使用原始数据库实体。像任何规则一样,如果您了解自己的实体并理解后果,那么这个规则可以被打破,但是有很多非常好的理由不要打破这个规则,特别是在团队合作和将来可能不像您一样理解规则或实体的人维护代码时。主要原因如下:
  1. ORM懒加载。假设您的顾客(Customer)有一个延迟加载的订单(Orders)集合。您将Customer传递给View,它会遍历Orders。您将在Orders表上得到一个N*1选择。但这也意味着您的数据库连接仍需要在View中打开。人们使用“每个操作一个事务”的模式,该模式在Action_Executed事件中处理数据库上下文,然后再呈现View。因此,您可能会尝试在其已被处理的情况下访问数据库。即使现在您没有这样做,以后可能会有人决定实现这种模式,因为它很流行。

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

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

  4. 可以单元测试不涉及数据库的ViewModel。 ViewModel可能包含相当多需要进行单元测试的逻辑。如果它不与您的数据库(如EF实体)绑定,那么测试会更容易。

总的来说,创建和维护ViewModel(即使没有AutoMapper)并不是一个负担,您会发现这是一种更好的开发模式。我建议除了最简单的情况(例如查找静态数据列表)之外,对于所有其他情况都应该使用ViewModel。


+1以及rob - 所有“好”的想法的超集总结了主题WL。 - jim tollan
Jim,有人应该写一个新的答案,将其他答案中的所有独特观点结合起来 :) - Rob Kent

12

我认为采用视图模型是唯一可取的方式,因此 ORM 实体没有优势 :) 视图模型不仅提供视图所需的数据,还定义了视图应该如何显示(通过定义模板)或如何进行验证(通过添加数据注释或实现 IDataErrorInfo 接口)。

使用视图模型:

优点:

  • 视图模型只包含视图所需的属性,没有其他多余的内容。
  • 视图模型可以使用数据注释或 IDataErrorInfo 接口包含特定的验证规则。
  • 视图模型可以将来自不同数据库实体的值组合起来。
  • 视图模型具有自文档性质,并且不与任何框架绑定。
  • 视图模型保护您免受伪造 POST 请求的影响,其中包含未在表单中提供但包含在 ORM 实体中的值。
  • 您可以轻松指定视图模型的显示模板,并使用 DisplayForEditorFor 助手在多个位置重用它们。

使用 ORM 实体:

缺点:

  • ORM 实体已经包含了可能会使您的验证出错的数据注释。例如:用户的密码字段可能会被标记为Required,但是当您仅更改基本用户信息时不需要该字段。
  • ORM 实体与框架(Entity Framework)密切相关,可能不容易实现规则。
  • ORM 实体可以包含多个视图的属性,但很难将验证规则分开应用到不同的视图中。
  • 使用 ORM 实体进行延迟加载可能会导致在呈现视图时执行 SQL 查询。这不应该发生。
  • 使用 ORM 实体可能会导致使用大量 SQL 查询代替小型查询。例如:当您要显示名字和姓氏的下拉列表时,您应该只从数据库中检索名字和姓氏,而不是整个实体。

8

谢谢目前为止的回答,它们对于理解两种方法的利弊有很大帮助。我有一件事要补充,其他人都没有提到过。

超限攻击

直接绑定数据库实体存在一个令人担忧的缺点是“超限攻击”。这是指攻击者可以使用不比FireBug更高级的工具插入表单字段,这些字段并不是用户打算编辑的,但确实存在于数据库实体中。

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

@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/


5

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

我还发现,view models有助于保存页面上所需的所有关联数据。一些开发人员喜欢使用动态ViewBag来处理此操作,但这对测试和调试不利。

特别是,当您希望从下拉列表中选择关联实体时,view models使其变得更加容易。

在此项目中,AutoMapper是一个救星,因为它省去了许多映射代码的编写,我只需要创建view models,然后controllers会自动从entities到view models进行映射。


+1 Gavin,我遇到了和你一样的问题,并且出于同样的原因采用了AutoMapper和ViewModels。 - jim tollan

3
不要将后端实体暴露给客户端。 现实世界的应用程序具有行为而不是CRUD。如果将实体绑定到视图,则在需要客户端上的行为时,很快就会陷入混乱的黑客行为。

2
我正要添加与hackedbychinese相同的观点。另外,关于查找表(lookup lists),你必须使用viewmodels,因为实体模型仅将指向该表中单个ID的指针保存在其中。而viewmodel允许您将所需填充列表传递到视图中——大功告成。
此外,当需要时,viewmodel可以包含离散逻辑,这绝对不是实体模型的情况。另外,验证可能因视图的使用而异,因此可以根据每个“视图”要求应用不同的验证。
ViewModel的目的主要是分离关注点,将View与Model的实现细节解耦。

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=true添加到POSt参数中,那么传递到ChangeEmail函数中的模型将具有设置为true的IsAdmin属性,这可能会意外地添加到数据库中,使用户可以自由访问更改他们没有权限更改的数据。

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


我在一周前的回答中已经讨论过这个问题了。这被称为“过度发帖攻击”。请参见我上面的帖子。 - Alex York
抱歉,当我阅读您的答案时,我认为您更关注于查看隐藏字段中的数据,而不是将数据推送到服务器以更改您不应该能够更改的数据。 - KallDrexx

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接