The advantages of view models in MVC exist regardless of the database system used (damn, even if you don't use it). In simple CRUD situations, your business model objects will very accurately mimic what you show in the views, but in something more than a basic CRUD, it will not.
One of the important things is the problem of business logic and data integrity using the same class for modeling and storing data that you use in views. Take a situation where you have the DateTime DateAdded property in your custom class to indicate when the user was added. If you provided a form that goes directly to your UserInfo class, you will get an action handler that looks like this:
[HttpPost] public ActionResult Edit(UserInfo model) { }
Most likely, you do not want the user to be able to change when they were added to the system, so your first thought is not to provide a field in the form.
However, you cannot rely on this for two reasons. Firstly, the value for DateAdded will be the same as you if you made new DateTime() or it will be null (any of these methods will be wrong for this user).
The second problem is that users can spoof this in the form request and add &DateAdded=<whatever date> to the POST data, and now your application will change the DateAdded field in the database to whatever the user entered.
This is by design because the MVC model binding mechanism looks at the data sent through POST and automatically tries to associate it with any available properties in the model. He has no way of knowing that the property passed was not in its original form and, therefore, it will still bind it to this property.
ViewModels does not have this problem, because your view model needs to know how to convert itself to / from a data object, and it does not have a DateAdded field to replace it, it only has the minimum fields that it needs to display (or receive) data.
In your specific scenario, I can easily reproduce this by manipulating the POST string, since your view model has direct access to your database.
Another problem with using data classes directly in views is when you try to present your view in a way that is not suitable for modeling your data. As an example, suppose you have the following fields for users:
public DateTime? BannedDate { get; set; } public DateTime? ActivationDate { get; set; }
Now let me say that the administrator is interested in the status of all users, and you want to display a status message next to each user, and also give different actions that the administrator can perform based on this user status. If you use your data model, your code will look like this:
// In status column of the web page data grid @if (user.BannedDate != null) { <span class="banned">Banned</span> } else if (user.ActivationDate != null) { <span class="Activated">Activated</span> } //.... Do some html to finish other columns in the table // In the Actions column of the web page data grid @if (user.BannedDate != null) { // .. Add buttons for banned users } else if (user.ActivationDate != null) { // .. Add buttons for activated users }
This is bad, because now you have a lot of business logic (user status is always denied over activated users, banned users are defined by users with a banned date, etc.). It is also much more complicated.
Instead, the best (at least) solution is to wrap your users in a ViewModel that has an enumeration for their status, and when you convert your model to your view model (the view model constructor is a good place to do this ) you can insert your business logic once to see all the dates and find out what status the user should be.
Then your code above is simplified as:
// In status column of the web page data grid @if (user.Status == UserStatuses.Banned) { <span class="banned">Banned</span> } else if (user.Status == UserStatuses.Activated) { <span class="Activated">Activated</span> } //.... Do some html to finish other columns in the table // In the Actions column of the web page data grid @if (user.Status == UserStatuses.Banned) { // .. Add buttons for banned users } else if (user.Status == UserStatuses.Activated) { // .. Add buttons for activated users }
There may be less code in this simple scenario, but it makes things much more convenient when the logic for determining user status becomes more complex. Now you can change the logic of determining the status of a user without changing the data model (you do not need to change the data model due to the way you view the data), and it saves the state definition in one place.