I am writing a WPF application, using an MVVM design with Entity Framework 4 as the ORM. I have collection properties in my view model that will contain collections of entities
I will throw in some thoughts but without having a final answer.
The basic question is in my opinion: Are operations a user can do on a UI always in a unique way related to database operations? Or more specific: If a user can remove an item from a list on the UI or insert a new item into a list, does that necessarily mean that a record has to be deleted from or inserted into the database?
I think, the answer is: No.
At first I can see a good use case to work with the EdmObservableCollection
. That is for example a view on the WPF UI with only a DataGrid
which is bound to a collection of customers. A list of customers will be fetched by a query specification. Now the user can edit in this DataGrid: He can change rows (single customers), he can insert a new row and he can delete a row. The DataGrid supports these operations quite easily and the databinding engine writes those "CUD" operations directly to the bound EdmObservableCollection. In this situation deleting a row or inserting a new row is actually supposed to be directly reflected on the database, so the EdmObservableCollection might be quite useful as it handles Inserts and Deletes in the ObjectContext internally.
But even in this simple situation there are a few points to take into account:
You probably need to inject the ObjectContext/Repository into your ViewModel anyway (to query for the objects you want to put into the collection) - and it must be the same context as injected into the EdmObservableCollection to handle object updates (editing a customer row) properly. You also must work with change tracking objects/proxies if you don't want to do manual "late" change tracking before you call SaveChanges.
This kind a "generic" delete operation the EdmObservableCollection
provides doesn't consider database or business constraints. What happens for instance if a user tries to delete a row for a customer who is assigned to various orders? If there is a foreign key relationship in the database SaveChanges will fail and throw an exception. Well, you might catch this exception, evaluate it and give a message to the user. But perhaps he has done a lot of other changes (edited many other rows and inserted new customers) but due to this violated FK constraint the whole transaction failed. OK, also this you could handle (remove this deleted customer from the ObjectContext and try again to save the changes) or even give the customer a choice what to do. And up to here we have only considered database constraints. There can be additional business rules which are not reflected in the database model (a customer can't be deleted before he hasn't paid all invoices, deletion must be approved by the sales department boss, customer must not be deleted before 6 month after his last order, and so on and so on...). So, there can be much more involved than a simple "ObjectContext.DeleteObject" to execute deletion in a safe and user friendly way.
Now let's consider another example: Imagine there is a view to assign contact persons to an order (well, unusual probably but let's say these are large, complex, very individual orders which include a lot of customer services and every order needs different contact persons at the customer site for various aspects of the order). This view may contain a small readonly view of the order, a readonly list of a pool of contact persons which are already in the customer's master data and then an editable list of contact persons which are assigned to the order. Now, like in the first example, the user can do similar things: He can delete a contact person from the list and he can maybe drag and drop a contact person from the master list to insert it into that list of order contact persons. If we had bound this list again to a EdmObservableCollection
nonsense would happen: New contact persons would be inserted into the database and contact persons would be deleted from the database. We don't want that, we actually only want to assign or unassign references to existing records (the customer's contact person master data) but never delete or insert records.
So we have two examples of similar operations on the UI (rows are deleted from and inserted into a list) but with quite different business rules behind them and also different operations in the data store. For WPF the same happens (which can be handled with an ObservableCollection in both cases), but different things must be done in the business and database layer.
I would draw a few conclusions from this:
EdmObservableCollection
can be useful in special situations when you have to deal with collections on the UI and you don't have to consider difficult business rules or database constraints. But it many situations it isn't applicable. Of course you could possibly create derived collections for other situations which overload and implement for instance Remove(T item)
in another way (for example don't delete from the ObjectContext but set a reference to null or something instead). But this strategy would move responsibilities of repositories or a service layer more and more into those specialized ObservableCollections. If your application does basically CRUD-like operations in DataGrid/List views then EdmObservableCollection might be well suited. For anything else, I doubt.
As described there are in my opinion more arguments against coupling database/repository operations with Insert/Remove operations of ObservableCollections and therefore against using a construct like EdmObservableCollection. I believe that in many cases your ViewModels will need a repository or service injected to fulfill the specific needs in your business and database layer. For instance for delete operations you could have a Command in the ViewModel and in the command handler do something like:
private void DeleteCustomer(Customer customer)
{
Validator validator = customerService.Delete(customer);
// customerService.Delete checks business rules, has access to repository
// and checks also FK constraints before trying to delete
if (validator.IsValid)
observableCustomerCollection.RemoveItem(customer);
else
messageService.ShowMessage(
"Dear User, you can't delete this customer because: "
+ validator.ReasonOfFailedValidation);
}
Complex stuff like this doesn't belong into a derived ObservableCollection in my opinion.
Generally I tend to keep units of work as small as possible - not for technical but for usability reasons. If a user does a lot of stuff in a view (edit something, delete something, insert and so on) and clicks on a "Save" button late after a lot of work, also a lot of things can go wrong, he might get a long list of validation errors and be forced to correct a lot of things. Of course basic validation should have been done in the UI before he can press the "Save" button at all, but the more complex validation will happen later in the business layer. For example if he deletes a row, I delete through the service at once (after confirmation message box perhaps) like in the example above. The same for Inserts. Updates can become more complicated (especially when many navigation properties in an entity are involved) since I don't work with change tracking proxies. (I am not sure if I shouldn't better do.)
I have no big hope to make different things look like they were the same. To separate concerns it makes sense to have a CustomerService.Delete
and a OrderContactPersonsService.Delete
which the ViewModels don't care about what happens behind. But somewhere (business layer, repository, ...) those operations will be different and the hard work has to be done. EdmObservableCollection with an intrinsic IRepository is over-generic the whole chain from the presentation layer down to the database and tries to hide these differences which is unrealistic in any other than the simplest CRUD applications.
Having an ObjectContext/DbContext versus an IRepository in the EdmObservableCollection is in my opinion the least problem. The EF context or ObjectSets/DbSets are almost UnitOfWork/Repositories anyway and it is questionable if you don't need to change the interface contracts when you ever should change the database access technology. Personally I have things like "Attach" or "LoadNavigationCollection" on my generic repository and it's not clear for me if these methods with their parameters would make sense at all with another persistance layer. But making the repository even more abstract (in a hope to have a real Add-Update-Delete-Super-Persistance-Ignorant-Interface-Marvel
) would only move it more towards uselessness. Abstracting EF away into an IRepository does not solve the concerns I've described.
Last note as a disclaimer: Read my words with scepticism. I am not an experienced WPF/EF developer, I am just working on my first somewhat bigger application (since around 2 months) which combines these two technologies. But my experience so far is that I have trashed a lot of over-abstracting code reduction attempts. I'd be happy - for maintenance reasons and for the sake of simplicity - if I could get along with an EdmObservableCollection and only a generic repository but finally there are application and customer demands which unfortunately require a lot of differently working code.