ASP.NET MVC - 数据库实体还是ViewModels?

时间:2012-06-07 08:29:15

标签: asp.net-mvc asp.net-mvc-3 orm viewmodel

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

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

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

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

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

8 个答案:

答案 0 :(得分:49)

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

缺点:

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

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

答案 1 :(得分:16)

正统观点是,您绝不应在视图中使用原始数据库实体。像任何规则一样,如果您了解自己的实体并且了解后果,那么它就会被打破,但是有很好的理由不打破这个规则,特别是在团队合作和将来会保留的代码时谁也可能不了解规则或实体。主要原因是:

  1. ORM延迟加载。想象一下,您的客户有一个延迟加载的集合订单。您将Customer传递给View,它会遍历Orders。您在订单表上获得N * 1选择。但这也意味着您的数据库连接仍需要在View中打开。有一种模式,人们使用'每个操作事务'来处理Action_Executed事件中的数据库上下文,该事件发生在View之前。因此,您可能会在处理完数据库后尝试访问该数据库。即使你现在没有这样做,未来的某个人可能会决定实施这种模式,因为它很时髦。

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

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

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

  5. 通常,创建和维护ViewModel(即使没有AutoMapper)也不是一个开销,你会发现它是一个更好的开发模式。除了最简单的情况(例如静态数据的查找列表)之外,我会推荐它。

答案 2 :(得分:12)

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

使用视图模型:

<强>优点:

  • 查看模型仅包含视图所需的属性,无其他。
  • 视图模型可能包含使用数据注释或IDataErrorInfo的特定验证规则。
  • 视图模型可以组合来自不同数据库实体的值。
  • 查看模型文档本身,不依赖于任何框架。
  • 查看模型可以保护您免受伪造的POST,这些POST包含未在表单中提供但包含在ORM实体中的值。
  • 您可以为视图模型轻松指定显示模板,并使用DisplayForEditorFor帮助程序在许多地方重复使用它们。

使用ORM实体:

<强>缺点:

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

答案 3 :(得分: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/

答案 4 :(得分:5)

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

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

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

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

答案 5 :(得分:3)

不要将后端实体公开给客户端。 真实世界的应用程序有行为 - 不是CRUD。你将你的实体数据绑定到视图中,当你在客户端需要行为时深入挖掘泥泞的黑客攻击只是时间问题。

答案 6 :(得分:1)

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

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

ViewModel的目的主要是关注点的分离 - 将View与模型的实现细节分离。

答案 7 :(得分: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函数的模型将IsAdmin属性设置为true,这很容易被意外添加到数据库,让用户可以自由访问他们无权更改的数据。

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