我目前正在开发一个ASP.NET MVC项目。
团队中的一些开发人员希望将自动生成的数据库实体直接绑定到视图。
其他开发人员则希望创建量身定制的ViewModel,并将其绑定到视图。
客观地说,这两种方法的优缺点是什么?
(通过“数据库实体”,我指的是ORM框架生成的自动生成类,例如LINQ to SQL、Entity Framework或LLBLGen)。
我目前正在开发一个ASP.NET MVC项目。
团队中的一些开发人员希望将自动生成的数据库实体直接绑定到视图。
其他开发人员则希望创建量身定制的ViewModel,并将其绑定到视图。
客观地说,这两种方法的优缺点是什么?
(通过“数据库实体”,我指的是ORM框架生成的自动生成类,例如LINQ to SQL、Entity Framework或LLBLGen)。
一定要在视图中使用视图模型,并使用类似于AutoMapper
的东西来轻松地从实体创建视图模型。
缺点:
优点:
ORM懒加载。假设您的顾客(Customer)有一个延迟加载的订单(Orders)集合。您将Customer传递给View,它会遍历Orders。您将在Orders表上得到一个N*1选择。但这也意味着您的数据库连接仍需要在View中打开。人们使用“每个操作一个事务”的模式,该模式在Action_Executed事件中处理数据库上下文,然后再呈现View。因此,您可能会尝试在其已被处理的情况下访问数据库。即使现在您没有这样做,以后可能会有人决定实现这种模式,因为它很流行。
ViewModel的关注点与db Model不同。例如,您通常会使用验证属性装饰ViewModel属性。这些属性通常与UI不同或仅涉及UI而不是数据库。如果绑定到数据库实体,则会发现所有这些UI关注点会污染您的数据库实体。
与2相关 - ViewModel的要求可能需要计算或派生属性。例如,从名和姓构建的Fullname。这些东西最好保存在ViewModel中。
可以单元测试不涉及数据库的ViewModel。 ViewModel可能包含相当多需要进行单元测试的逻辑。如果它不与您的数据库(如EF实体)绑定,那么测试会更容易。
总的来说,创建和维护ViewModel(即使没有AutoMapper)并不是一个负担,您会发现这是一种更好的开发模式。我建议除了最简单的情况(例如查找静态数据列表)之外,对于所有其他情况都应该使用ViewModel。
我认为采用视图模型是唯一可取的方式,因此 ORM 实体没有优势 :) 视图模型不仅提供视图所需的数据,还定义了视图应该如何显示(通过定义模板)或如何进行验证(通过添加数据注释或实现 IDataErrorInfo 接口)。
使用视图模型:
优点:
DisplayFor
或 EditorFor
助手在多个位置重用它们。使用 ORM 实体:
缺点:
Required
,但是当您仅更改基本用户信息时不需要该字段。谢谢目前为止的回答,它们对于理解两种方法的利弊有很大帮助。我有一件事要补充,其他人都没有提到过。
直接绑定数据库实体存在一个令人担忧的缺点是“超限攻击”。这是指攻击者可以使用不比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" />
}
<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>
<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" />
哎呀!
我曾经尝试开发一个应用程序,该应用程序直接在ASP.NET视图中使用NHibernate实体。 我遇到了许多问题,包括延迟加载和延迟执行SQL语句直接从视图中而不是在业务逻辑层甚至控制器中运行。使用viewmodels并使用automapper似乎解决了所有这些问题,并使应用程序更易于测试,调试和维护。
我还发现,view models有助于保存页面上所需的所有关联数据。一些开发人员喜欢使用动态ViewBag来处理此操作,但这对测试和调试不利。
特别是,当您希望从下拉列表中选择关联实体时,view models使其变得更加容易。
在此项目中,AutoMapper是一个救星,因为它省去了许多映射代码的编写,我只需要创建view models,然后controllers会自动从entities到view models进行映射。
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属性,这可能会意外地添加到数据库中,使用户可以自由访问更改他们没有权限更改的数据。
这也适用于用户权限、更改实体所有者(使您的问题与我而不是您相关联)、更改原始创建日期等。