I've been struggling for a while with many-to-many associations in a breeze app. I have issues both on the client and server side but for now, I'll just expose my client-side issue. I don't know if the approach I've come up with is correct or not, and I would really like to get feedback from the breeze team on this:
My business model:
public class Request
{
public virtual IList<RequestContact> RequestContacts { get; set; }
}
public class RequestContact
{
public virtual Contact Contact { get; set; }
public virtual Guid ContactId { get; set; }
public virtual Request Request { get; set; }
public virtual Guid RequestId { get; set; }
}
public class Contact
{
public virtual Client Client { get; set; }
public virtual Guid? ClientId { get; set; }
public virtual string Username { get; set; }
}
In the success callback of my getRequest query, I add a contacts property to the Request and I populate it :
request.contacts = [];
request.requestContacts.forEach(function (reqContact) {
request.contacts.push(reqContact.contact);
});
The view is bound to a contacts array, defined in the controller:
<select ng-multiple="true" multiple class="multiselect" data-placeholder="Select Contacts" ng-change="updateBreezeContacts()" ng-model="request.contacts" ng-options="c as c.username for c in contacts | filter:{clientId: request.client.id} track by c.id"></select>
The controller:
//the collection to which the multiselect is bound:
$scope.contacts = dataService.lookups.contacts;
whenever an item is selected or unselected in the multiselect, this method is called:
$scope.updateBreezeContacts = function () {
//wipe out all the RequestContact entities
$scope.request.requestContacts.forEach(function (req) {
req.entityAspect.setDeleted();
});
//populate the RequestContact based on selected contacts
for (var i = 0; i < $scope.request.contacts.length; i++) {
var requestContact = dataService.createRequestContact($scope.request, $scope.request.contacts[i]);
$scope.request.requestContacts.push(requestContact);
}
where the createRequestContact method of the dataService actually does that:
manager.createEntity('RequestContact', { request: myRequest, contact: myContact});
Use case scenario:
The request has one selected contact.
User unselect the contact and then select another one from the list.
Then she decides to reselect the one that was previously unselected. We now have two selected contacts.
User hits the save button and the call to saveChanges is made. Breeze sends 3 entities to the server: the first contact with 'Deleted' status, the same contact again with 'Added' status, and finally the other contact that was selected, also with 'Added' status.
Is this what should be done when working with many-to-many associations ?
I actually get a server error ("not-null property references a null or transient value Business.Entities.RequestContact.Request") but before I draw any conclusions, I'd like to know if what I do on the client-side is correct.
Server-side
You have server-side modeling problems to deal with first. I noted the absence of PKs in my comment to your question. I suggest that you get that working first, before bothering with the client.
Client-side
I have long experience with this kind of scenario. For me the canonical case is a User who can have any number of Roles and the roles s/he has are in the UserRoles table.
The typical UI:
select and present a User
present a list of all possible roles for that user with a preceding checkbox
the checkbox is checked if the user has that role; unchecked if s/he does not
Uh Oh
Many times I have seen people bind the list of all possible roles to a list of UserRole entities. This rarely works.
Many time I have seen people create and destroy UserRole entities as the user clicks the checkbox. This rarely works.
Too often I have seen UserRole entities added and deleted and added and deleted to the cache. This is usually fatal as the client loses track of whether a UserRole entity does or does not correspond at this moment to a record in the database.
If I read your code correctly, you are making everyone of these mistakes.
Item ViewModel instead
I have had more success when I represented this user's roles as a list of "Item ViewModel" instances and postponed entity manipulation until it was time to save the user's selections.
For our discussion, let's call this object a UserRoleVm. It might be defined as follows in JavaScript
{
role,
isSelected,
userRole
}
When you build the screen,
populate a list of UserRoleVm instances, one for every Role
set each vm's role property with the appropriate Role entity
bind the view to vm.role.name
set each vm's userRole property with the pertinent user's UserRole entity if and only if such an entity already exists
set vm's isSelected=true if the vm has a userRole and if vm.userRole.entityAspect.entityState is not Deleted.
bind the vm's isSelected to the checkbox
Now the user can check and uncheck at will.
I do not create/delete/modify any UserRole entity while this is going on. I wait for the UI signal to save (whatever that signal happens to be).
During the Save preparation, I iterate over the list of UserRoleVm instances
if not checked and no vm.userRole, do nothing
if not checked and have vm.userRole, then vm.userRole.entityAspect.setDeleted(). If vm.userRole.entityAspect.entityState is Detached (meaning it was previously in the Added state), set vm.userRole = null.
if checked and no vm.userRole, create a new UserRole and assign it to vm.userRole
if checked and have vm.userRole, then if vm.userRole.entityAspect.entityState is
Unchanged, do nothing
Modified (why? how?), revert by calling vm.userRole.entityAspect.rejectChanges()
Deleted (must have been a pre-existing UserRole that was "unchecked" but still not saved; how did that happen?), revert by calling vm.userRole.entityAspect.rejectChanges()
Now call manager.saveChanges().
If the save succeeds, all is well.
If it fails, the cleanest approach is call manager.rejectChanges(). This clears the decks (and discards whatever changes the user made since the last save).
Either way, rebuild the list from scratch as we did at the beginning.
Ideally you do not let the user make more changes to user roles until the async save returns either successfully or not.
I'm sure you can be more clever than this. But this approach is robust.
Variation
Don't bother with UserRoleVm.userRole. Don't carry the existing UserRole entity in the UserRoleVm. Instead, refer to the user's cached UserRole entities while initializing the UserRoleVm.isSelected property. Then evaluate the list during save preparation, finding and adjusting the cached UserRole instances according to the same logic.
Enabling the Save button (update 19 Dec)
Sam asks:
The Save button's disabled attribute is bound to a property set to true when the EntityManager has changes. However, since my ViewModel is NOT part of the EntityManager, when the user adds/removes contacts, that does not change the Model attached to the EntityManager. Therefore the Save button is never enabled (unless I change another property of the model). Can you think of a workaround for that?
Yes, I can think of several.
Define the isSelected property as an ES5 property with get and set methods; inside the set method you signal to the outer VM that the UserRoleVm instance has changed. This is possible because you must be using an ES5 browser if you've got Angular and Breeze working together.
Add an ngClick (or ngChanged) to the checkbox html that binds to a function in the outer vm, e.g.,
<li ng-repeat="role in vm.userRoles">
...
<input type="checkbox"
ng-model="role.isSelected"
ng-click="vm.userRoleClicked(role)"</input>
...
</li>
Leverage angular's native support for "view changed" detection ("isPristine" I think). I don't usually go this way so I don't know details. It's viable as long as you don't allow the user to leave this screen and come back expecting that unsaved changes to the UserRoleVm list have been preserved.
The vm.userRoleClicked could set a vm.hasChanges property to true. Bind the save button's isEnabled is to vm.hasChanges. Now the save button lights up when the user clicks a checkbox.
As described earlier, the save button click action iterates over the userRoleVm list, creating and deleting UserRole entities. Of course these actions are detected by the EntityManager.
You could get fancier. Your UserRoleVm type could record its original selected state when created (userRoleVm.isSelectedOriginal) and your vm.userRoleClicked method could evaluate the entire list to see if any current selected states differ from their original selected states ... and set the vm.hasChanges accordingly. It all depends on your UX needs.
Don't forget to clear vm.hasChanges whenever you rebuild the list.
I think I prefer #2; it seems both easiest and clearest to me.
Update 3 February 2014: an example in plunker
I've written a plunker to demonstrate the many-to-many checkbox technique I described here. The readme.md explains all.
The Breeze.js client does not support "many to many" relationships at this time. You will have to expose the junction/mapping table as an entity. There are several other posts on this same topic available.
We do plan to add many-many support in the future. Sorry, but no date yet...
Related
I have 2 classes, like the below.
They can have very large collections - a Website may have 2,000+ WebsitePages and vice-versa.
class WebsitePage
{
public int ID {get;set;}
public string Title {get;set;}
public List<Website> Websites {get;set;}
}
class Website
{
public int ID {get;set;}
public string Title {get;set;}
public List<WebsitePage> WebsitePages {get;set;}
}
I am having trouble removing a WebsitePage from a Website. Particularly when removing a WebsitePage from mutliple Websites.
For example, I might have code like this:
var pageToRemove = db.WebsitePages.FirstOrDefault();
var websites = db.Websites.Include(i => i.WebsitePages).ToList();
foreach(var website in websites)
{
website.WebsitePages.Remove(pageToRemove)
}
If each website Include() 2k pages, you can imagine it takes ages to load that second line.
But if I don't Include() the WebsitePages when fetching the Websites, there is no child collection loaded for me to delete from.
I have tried to just Include() the pages that I need to delete, but of course when saving that gives me an empty collection.
Is there a recommended or better way to approach this?
I am working with an existing MVC site and I would rather not have to create an entity class for the join table unless absolutely necessary.
No, you can't... normally.
A many-to-many relationship (with a hidden junction table) can only be affected by adding/removing items in the nested collections. And for this the collections must be loaded.
But there are some options.
Option 1.
Delete data from the junction table by raw SQL. Basically this looks like
context.Database.ExecuteSqlCommand(
"DELETE FROM WebsiteWebsitePage WHERE WebsiteID = x AND WebsitePageID = y"));
(not using parameters).
Option 2.
Include the junction into the class model, i.e. map the junction table to a class WebsiteWebsitePage. Both Website and WebsitePage will now have
public ICollection<WebsiteWebsitePage> WebsiteWebsitePages { get; set; }
and WebsiteWebsitePage will have reference properties to both Website and WebsitePage. Now you can manipulate the junctions directly through the class model.
I consider this the best option, because everything happens the standard way of working with entities with validations and tracking and all. Also, chances are that sooner or later you will need an explicit junction class because you're going to want to add more data to it.
Option 3.
The box of tricks.
I tried to do this by removing a stub entity from the collection. In your case: create a WebsitePage object with a valid primary key value and remove it from Website.WebsitePages without loading the collection. But EF doesn't notice the change because it isn't tracking Website.WebsitePages, and the item is not in the collection to begin with.
But this made me realize I had to make EF track a Website.WebsitePages collection with 1 item in it and then remove that item. I got this working by first building the Website item and then attaching it to a new context. I'll show the code I used (a standard Product - Category model) to prevent typos.
Product prd;
// Step 1: build an object with 1 item in its collection
Category cat = new Category { Id = 3 }; // Stub entity
using(var db = new ProdCatContext())
{
db.Configuration.LazyLoadingEnabled = false;
prd = db.Products.First();
prd.Categories.Add(cat);
}
// Step 2: attach to a new context and remove the category.
using(var db = new ProdCatContext())
{
db.Configuration.LazyLoadingEnabled = false;
db.Products.Attach(prd);
prd.Categories.Remove(cat);
db.SaveChanges(); // Deletes the junction record.
}
Lazy loading is disabled, otherwise the Categories would still be loaded when prd.Categories is addressed.
My interpretation of what happens here is: In the second step, EF not only starts tracking the product when you attach it, but also its associations, because it 'knows' you can't load these associations yourself in a many to many relationship. It doesn't do this, however, when you add the category in the first step.
I have a distributed system where users can make changes into one single database. To illustrate the problem, let's assume we have the following entities:
public class Product{
public int Id{get;set;}
public List<ProductOwner> ProductOwners{get;set;}
}
public class ProductOwner{
public int ProductId { get; set; }
[ForeignKey("ProductId")]
[Inversroperty("ProductOwners")]
public Product Product{ get; set; }
public int OwnerId { get; set; }
[ForeignKey("OwnerId")]
public Owner Owner{ get; set; }
}
public class Owner{
public int Id{get;set;}
}
Let's also assume we have two users, UserOne and UserTwo connected to the system.
UserOne adds Product1 and assigns Owner1 as an owner. As a result, a new ProductOwner1 is created with key=[Product1.Id, Owner1.Id]
UserTwo does the same operation, another instance ProductOwner2 with key=[Product1.Id, Owner1.Id] is created. This will result in an EF exception on the server side, which is expected, as a row with key=[Product1.Id, Owner1.Id] already exists in the database.
Question
The issue above can be partly resolved by having some sort of real time data refresh on both UserOne and UserTwo machines (I am already doing this) and running a validation task on the server to ignore and not save entities that are already in the DB.
The remaining issue is how to tell Breeze on 'userTwo' machine to mark ProductOwner2 as saved and change its state from Added to Unchanged?
I think this is an excellent question and has been raised enough that I wanted to chime in on how I would do it given the above scenario in hopes others can find a good way to accomplish this from a Breeze.js perspective as well. This answer doesn't really address server logic so it is incomplete at best.
Step 1 - Open a web socket
First and foremost we need some way to tell the other connected clients that there has been a change. SignalR is a great way to do this if you are using the ASP.NET MVC stack and there are a bunch of other tools.
The point is that we don't need to have a great way of passing data down and forcing it in to the client's cache, we just need a lightweight way to tell the client that some information has changed and if they are concerned with this to refresh something. My recommendation in this area would be to use a payload that tells the client either what entity type and Id changed or give a resource to the client to let them know what collection of entities to refresh. Two examples of a JSON payload that would work well here -
{
"entityChanges": [
{
"id": "123",
"type": "product",
"new": false
},
{
"id": "234",
"type": "product",
"new": true
}
],
collectionChanges: [
{
"type": "productOwners"
}
]
}
In this scenario we are simply telling the client that the products with Ids of 123 and 234 have changed, and that 234 happens to be a new entity. We aren't pushing any data about what properties have changed to the client as that is their responsibility to decide whether to refresh or requery for data. There is also the possibility of telling the client to refresh a whole collection like in the second array but I will focus on the first example.
Step 2 - Handle the changes
Ok we got a payload from our web socket that we need to pass to some analyzer to decide whether to requery. My recommendation here is to check if that entity exists in cache, and if so, refresh it. If a flag comes down in the JSON that says it is a new entity we probably also need to requery it. Here is some basic logic -
function checkForChanges (payload) {
var parsedJson = $.parse(payload);
$.each(parsedJson.entityChanges, function (index, item) {
// If it is a new entity,
if (item.new === true) {
// Go get it from the database
manager.fetchEntityByKey(item.type, item.id)
.then(fetchSucceeded).fail(fetchFailed);
} else {
// Check local cache first
var localentity = manager.getEntityByKey(item.type, item.id);
// And if we have a local copy already,
if (localentity) {
// Go refresh it from the database
manager.fetchEntityByKey(item.type, item.id)
.then(fetchSucceeded).fail(fetchFailed);
}
}
}
}
Now there is probably some additional logic in your application that need to be handled but in a nut shell we are -
Opening up a lightweight connection to the client to listen for changes only
Creating a handler for when those changes occur
Applying some logic on how to query for or refresh the data
Some considerations here are you may want to use different merge strategies depending on various conditions. For instance if the entity already has changes you may want to preserve changes, where as if it is a entity that is always in a state of flux you may want to overwrite changes.
http://www.breezejs.com/sites/all/apidocs/classes/MergeStrategy.html
Hope this provides some insight, and if it doesn't answer your question directly I apologize for crowding up the answers : )
Would it be possible to catch the entity framework / unique key constraint error on the breeze client and react by creating a new entity manager (using the createEmptyCopy method), loading the relevant ProductOwner records and using them to determine which ProductOwner records in the original entityManager need to be set "unchanged" using the entity's entityAspect's setUnchanged method. Once this "synchronization" is done the save changes can be retried.
In other words, the client is optimistic the save will succeed but can recover if necessary. The server remains oblivious to the potential race condition and has no custom code.
A brute force approach, apologies if I'm stating the obvious.
I have a Note domain object which belongs to a Document object. Only an owner of a Document can add Notes so in the Document class there is a canUserAccess() method. In my service layer I can call canUserAccess() to ensure a user only adds Notes to Documents they own.
This works well for create but I have hit a problem with my Note edit action. On post, the viewmodel is mapped to a Note object, providing it with a DocumentID. Problem is, a malicious user could send in a DocumentID on which they do have permission and thus edit a Note belonging to a Document they don't. In my service layer I cannot reliably use the supplied DocumentID yet I need to get the related DocumentID in order to verify that the user can edit the Note. This is an example:
public void editNote(Note note)
{
note.Document = documentRepository.Find(note.DocumentID);
if(note.Document.canUserAccess())
}
How do I get around this? It seems I need to avoid passing the DocumentID with the edit viewmodel but how do I hydrate the related Document in the service layer? There is probably a really simple solution to this and I am just tying myself up in circles!
You do this with BindAtrribute for the model or for the action method by adding a white list with the properties you want to be bound :
for the model
[Bind(Include="NoteText,NoteTitle")]
public Model{}
for the action method
public ViewResult Edit([Bind(Include="NoteText,NoteTitle"]Note note)){}
or use a black list for the properties you don't want to bind :
[Bind(Exclude="DocumentID")]
public Model{}
I would personally use white list with the model class. You might find this article interesting. The last section for under-posting is your case.
Then you don't have the documentID passed, but in your action you can do this:
public ViewResult Edit(Note note)
{
Note updateNote = nodesRep.Get(note.noteID);
if(updateNote.Document.canUserAccess())
{
//replace the values of the updateNote
//you can place this line in your base Repository class
context.Entry<Note>(updateNote).CurrentValues.SetValues(note); //I assume EF 4.1
}
}
I want to create a form where the user creates a list and then saves it. The list can have an arbitrary number of rows. The user cannot save the list until it is complete (all rows/items are added) and I cannot use javascript to do it all on the clientside before posting.
The form will create a contact, like in an address book or something. The entity looks like this:
public class Contact
{
public string Name { get; set; }
public string Company { get; set; }
public List<ContactRow> ContactRows { get; set; }
}
The ContactRow looks like this:
public class ContactRow
{
public string Type { get; set; }
public string Value { get; set; }
}
So, basically it is a contact with a name and a company. The contact has a list of contact rows, where each row has a type (phone, email, etc) and a value (555 - 12334, test#email.com, etc).
My form will contain two ordinary textboxes for Name och Company respectively. But I'm not sure how to handle the list with contact rows. What I want the user to be able to do is adding new rows, editing rows and deleting rows. All this must be done before the save button i clicked, i.e. the list must be all done when saving.
This is not very hard to to with javascript and I've already done that. But now I want to implement this functionality without javascript. It is ok to post to the server while building up the list, but the list cannot be saved until it is done.
Ideas?
If you do not wish to use javascript a postback whilst building up the list is all you can do.
This user experience is often deemed poor as work is ongoing, but is the best starting point when using progressive-enhancement to make your application more user friendly.
The OP stated Javascript was not an option. I feel your pain as one of our applications had a similar requirement. We have a construct called an updateable list with the same functionality as you outlined.
We perform data handling on the server. Each list is displayed in a table. Each row contains the item data and has an action icon associated with it. The default is to display and we support new, updated and deleted actions.
After the user enters data for an item they press a button to add or remove the item from the list. Selecting existing items allows for an update operation. The list, and status, is determined on the server and the client does not run Javascript.
As #rich.kelly pointed out you will need to go to the server for each operation. It takes longer to do anything and your session gets a workout but it meets the client requirements.
I understand that the "proper" structure for separation-of-concerns in MVC is to have view-models for your structuring your views and separate data-models for persisting in your chosen repository. I started experimenting with MongoDB and I'm starting to think that this may not apply when using a schema-less, NO-SQL style database. I wanted to present this scenario to the stackoverflow community and see what everyone's thoughts are. I'm new to MVC, so this made sense to me, but maybe I am overlooking something...
Here is my example for this discussion: When a user wants to edit their profile, they would go to the UserEdit view, which uses the UserEdit model below.
public class UserEditModel
{
public string Username
{
get { return Info.Username; }
set { Info.Username = value; }
}
[Required]
[MembershipPassword]
[DataType(DataType.Password)]
public string Password { get; set; }
[DataType(DataType.Password)]
[DisplayName("Confirm Password")]
[Compare("Password", ErrorMessage = "The password and confirmation password do not match.")]
public string ConfirmPassword { get; set; }
[Required]
[Email]
public string Email { get; set; }
public UserInfo Info { get; set; }
public Dictionary<string, bool> Roles { get; set; }
}
public class UserInfo : IRepoData
{
[ScaffoldColumn(false)]
public Guid _id { get; set; }
[ScaffoldColumn(false)]
public DateTime Timestamp { get; set; }
[Required]
[DisplayName("Username")]
[ScaffoldColumn(false)]
public string Username { get; set; }
[Required]
[DisplayName("First Name")]
public string FirstName { get; set; }
[Required]
[DisplayName("Last Name")]
public string LastName { get; set; }
[ScaffoldColumn(false)]
public string Theme { get; set; }
[ScaffoldColumn(false)]
public bool IsADUser { get; set; }
}
Notice that the UserEditModel class contains an instance of UserInfo that inherits from IRepoData? UserInfo is what gets saved to the database. I have a generic repository class that accepts any object that inherits form IRepoData and saves it; so I just call Repository.Save(myUserInfo) and its's done. IRepoData defines the _id (MongoDB naming convention) and a Timestamp, so the repository can upsert based on _id and check for conflicts based on the Timestamp, and whatever other properties the object has just get saved to MongoDB. The view, for the most part, just needs to use #Html.EditorFor and we are good to go! Basically, anything that just the view needs goes into the base-model, anything that only the repository needs just gets the [ScaffoldColumn(false)] annotation, and everything else is common between the two. (BTW - the username, password, roles, and email get saved to .NET providers, so that is why they are not in the UserInfo object.)
The big advantages of this scenario are two-fold...
I can use less code, which is therefore more easily understood, faster to develop, and more maintainable (in my opinion).
I can re-factor in seconds... If I need to add a second email address, I just add it to the UserInfo object - it gets added to the view and saved to the repository just by adding one property to the object. Because I am using MongoDB, I don't need to alter my db schema or mess with any existing data.
Given this setup, is there a need to make separate models for storing data? What do you all think the disadvantages of this approach are? I realize that the obvious answers are standards and separation-of-concerns, but are there any real world examples can you think of that would demonstrate some of the headaches this would cause?
Its also worth noting that I'm working on a team of two developers total, so it's easy to look at the benefits and overlook bending some standards. Do you think working on a smaller team makes a difference in that regard?
The advantages of view models in MVC exist regardless of database system used (hell even if you don't use one). In simple CRUD situations, your business model entities will very closely mimick what you show in the views, but in anything more than basic CRUD this will not be the case.
One of the big things are business logic / data integrity concerns with using the same class for data modeling/persistence as what you use in views. Take the situation where you have a DateTime DateAdded property in your user class, to denote when a user was added. If you provide an form that hooks straight into your UserInfo class you end up with an action handler that looks like:
[HttpPost]
public ActionResult Edit(UserInfo model) { }
Most likely you don't want the user to be able to change when they were added to the system, so your first thought is to not provide a field in the form.
However, you can't rely on that for two reasons. First is that the value for DateAdded will be the same as what you would get if you did a new DateTime() or it will be null ( either way will be incorrect for this user).
The second issue with this 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 DB to whatever the user entered.
This is by design, as MVC's model binding mechanism looks at the data sent via POST and tries to automatically connect them with any available properties in the model. It has no way to know that a property that was sent over wasn't in the originating form, and thus it will still bind it to that property.
ViewModels do not have this issue because your view model should know how to convert itself to/from a data entity, and it does not have a DateAdded field to spoof, it only has the bare minimum fields it needs to display (or receive) it's data.
In your exact scenario, I can reproduce this with ease with POST string manipulation, since your view model has access to your data entity directly.
Another issue with using data classes straight in the views is when you are trying to present your view in a way that doesn't really fit how your data is modeled. As an example, let's say you have the following fields for users:
public DateTime? BannedDate { get; set; }
public DateTime? ActivationDate { get; set; } // Date the account was activated via email link
Now let's say as an Admin you are interested on the status of all users, and you want to display a status message next to each user as well as give different actions the admin can do based on that user's status. If you use your data model, your view's code will look like:
// In status column of the web page's 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's 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 you have a lot of business logic in your views now (user status of banned always takes precedence over activated users, banned users are defined by users with a banned date, etc...). It is also much more complicated.
Instead, a better (imho 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's constructor is a good place to do this) you can insert your business logic once to look at all the dates and figure out what status the user should be.
Then your code above is simplified as:
// In status column of the web page's 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's data grid
#if (user.Status == UserStatuses.Banned)
{
// .. Add buttons for banned users
}
else if (user.Status == UserStatuses.Activated)
{
// .. Add buttons for activated users
}
Which may not look like less code in this simple scenario, but it makes things a lot more maintainable when the logic for determining a status for a user becomes more complicated. You can now change the logic of how a user's status is determined without having to change your data model (you shouldn't have to change your data model because of how you are viewing data) and it keeps the status determination in one spot.
tl;dr
There are at least 3 layers of models in an application, sometimes they can be combined safely, sometimes not. In the context of the question, it's ok to combine the persistence and domain models but not the view model.
full post
The scenario you describe fits equally well using any entity model directly. It could be using a Linq2Sql model as your ViewModel, an entity framework model, a hibernate model, etc. The main point is that you want to use the persisted model directly as your view model. Separation of concerns, as you mention, does not explicitly force you to avoid doing this. In fact separation of concerns is not even the most important factor in building your model layers.
In a typical web application there are at least 3 distinct layers of models, although it is possible and sometimes correct to combine these layers into a single object. The model layers are, from highest level to lowest, your view model, your domain model and your persistence model. Your view model should describe exactly what is in your view, no more and no less. Your domain model should describe your complete model of the system exactly. Your persistence model should describe your storage method for your domain models exactly.
ORMs come in many shapes and sizes, with different conceptual purposes, and MongoDB as you describe it is simply one of them. The illusion most of them promise is that your persistence model should be the same as your domain model and the ORM is just a mapping tool from your data store to your domain object. This is certainly true for simple scenarios, where all of your data comes from one place, but eventually has it's limitations, and your storage degrades into something more pragmatic for your situation. When that happens, the models tend to become distinct.
The one rule of thumb to follow when deciding whether or not you can separate your domain model from your persistence model is whether or not you could easily swap out your data store without changing your domain model. If the answer is yes, they can be combined, otherwise they should be separate models. A repository interface naturally fits here to deliver your domain models from whatever data store is available. Some of the newer light weight ORMs, such as dapper and massive, make it very easy to use your domain model as your persistence model because they do not require a particular data model in order to perform persistence, you are simply writing the queries directly, and letting the ORM just handle the mapping.
On the read side, view models are again a distinct model layer because they represent a subset of your domain model combined however you need in order to display information to the page. If you want to display a user's info, with links to all his friends and when you hover over their name you get some info about that user, your persistence model to handle that directly, even with MongoDB, would likely be pretty insane. Of course not every application is showing such a collection of interconnected data on every view, and sometimes the domain model is exactly what you want to display. In that case there is no reason to put in the extra weight of mapping from an object that has exactly what you want to display to a specific view model that has the same properties. In simple apps if all I want to do is augment a domain model, my view model will directly inherit from the domain model and add the extra properties I want to display. That being said, before your MVC app becomes large, I highly recommend using a view model for your layouts, and having all of page based view models inherit from that layout model.
On the write side, a view model should only allow the properties you wish to be editable for the type of user accessing the view. Do not send an admin view model to the view for a non admin user. You could get away with this if you write the mapping layer for this model yourself to take into account the privileges of the accessing user, but that is probably more overhead than just creating a second admin model that inherits from the regular view model and augments it with the admin properties.
Lastly about your points:
Less code is only an advantage when it actually is more understandable. Readability and understand-ability of it are results of the skills of the person writing it. There are famous examples of short code that has taken even solid developers a long time to dissect and understand. Most of those examples come from cleverly written code which is not more understandable. More important is that your code meets your specification 100%. If your code is short, easily understood and readable but does not meet the specification, it is worthless. If it is all of those things and does meet the specification, but is easily exploitable, the specification and the code are worthless.
Refactoring in seconds safely is the result of well written code, not it's terseness. Following the DRY principle will make your code easily refactorable as long as your specification correctly meets your goals. In the case of model layers, your domain model is the key to writing good, maintainable and easy to refactor code. Your domain model will change at the pace at which your business requirements change. Changes in your business requirements are big changes, and care has to be taken to make sure that a new spec is fully thought out, designed, implemented, tested, etc. For example you say today you want to add a second email address. You still will have to change the view (unless you're using some kind of scaffolding). Also, what if tomorrow you get a requirements change to add support for up to 100 email addresses? The change you originally proposed was rather simple for any system, bigger changes require more work.