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 returned from EF4 as IEnumerable<T>
collections in response to queries submitted from the business layer.
I had hoped to simply wrap the IEnumerable<T>
result set in an ObservableCollection<T>
. However, I found myself writing change-tracking code in my repository, or maintaining shadow collections of changed objects, just to keep the view model and persistence layer in sync. Every time an entity is added to the collection in the view model, I had to go to my repository to add it to the EF4 ObjectSet. I had to do the same sort of thing with updates and deletions.
To simplify things, I borrowed an EdmObservableCollection<T>
class from the WPF Application Framework project on CodePlex (http://waf.codeplex.com/). The class wraps an ObservableCollection<T>
with a reference to an EF4 ObjectContext
, so that the OC can be updated as the collection is updated. I have reprinted the EdmObservableCollection
class below. The class works pretty well, but it has a bit of a code smell about it, because I end up with a reference to EF4 in my view model.
Here's my question: In a WPF application, what's the usual way of keeping an EF4 entity collection in sync with its object context? Is the EdmObservableCollection a suitable approach, or is there a better way? Am I missing something fundamental in working with EF4? Thanks for your help.
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Data.Objects;
using System.Linq;
namespace Ef4Sqlce4Demo.ViewModel.BaseClasses
{
/// <summary>
/// An ObservableCollection for Entity Framework 4 entity collections.
/// </summary>
/// <typeparam name="T">The type of EF4 entity served.</typeparam>
/// <remarks>Developed from WPF Application Framework (WAF) http://waf.codeplex.com/</remarks>
public class EdmObservableCollection<T> : ObservableCollection<T>
{
#region Fields
// Member variables
private readonly string m_EntitySetName;
private readonly ObjectContext m_ObjectContext;
#endregion
#region Constructors
/// <summary>
/// Creates a new EDM Observable Collection and populates it with a list of items.
/// </summary>
/// <param name="objectContext">The EF4 ObjectContext that will manage the collection.</param>
/// <param name="entitySetName">The name of the entity set in the EDM.</param>
/// <param name="items">The items to be inserted into the collection.</param>
public EdmObservableCollection(ObjectContext objectContext, string entitySetName, IEnumerable<T> items)
: base(items ?? new T[] {})
{
if (objectContext == null)
{
throw new ArgumentNullException("objectContext");
}
if (entitySetName == null)
{
throw new ArgumentNullException("entitySetName");
}
m_ObjectContext = objectContext;
m_EntitySetName = entitySetName;
}
/// <summary>
/// Creates an empty EDM Observable Collection that has an ObjectContext.
/// </summary>
/// <param name="objectContext">The EF4 ObjectContext that will manage the collection.</param>
/// <param name="entitySetName">The name of the entity set in the EDM.</param>
public EdmObservableCollection(ObjectContext objectContext, string entitySetName)
: this(objectContext, entitySetName, null)
{
}
/// <summary>
/// Creates an empty EDM Observable Collection, with no ObjectContext.
/// </summary>
/// <remarks>
/// We use this constructor to create a placeholder collection before we have an
/// ObjectContext to work with. This state occurs when the program is first launched,
/// before a file is open. We need to initialize collections in the application's
/// ViewModels, so that the MainWindow can get Note and Tag counts, which are zero.
/// </remarks>
public EdmObservableCollection()
{
}
#endregion
#region Method Overrides
protected override void InsertItem(int index, T item)
{
base.InsertItem(index, item);
m_ObjectContext.AddObject(m_EntitySetName, item);
}
protected override void RemoveItem(int index)
{
T itemToDelete = this[index];
base.RemoveItem(index);
m_ObjectContext.DeleteObject(itemToDelete);
}
protected override void ClearItems()
{
T[] itemsToDelete = this.ToArray();
base.ClearItems();
foreach (T item in itemsToDelete)
{
m_ObjectContext.DeleteObject(item);
}
}
protected override void SetItem(int index, T item)
{
T itemToReplace = this[index];
base.SetItem(index, item);
m_ObjectContext.DeleteObject(itemToReplace);
m_ObjectContext.AddObject(m_EntitySetName, item);
}
#endregion
#region Public Methods
/// <summary>
/// Adds an object to the end of the collection.
/// </summary>
/// <param name="item">The object to be added to the end of the collection.</param>
public new void Add(T item)
{
InsertItem(Count, item);
}
/// <summary>
/// Removes all elements from the collection.
/// </summary>
/// <param name="clearFromContext">Whether the items should also be deleted from the ObjectContext.</param>
public void Clear(bool clearFromContext)
{
if (clearFromContext)
{
foreach (T item in Items)
{
m_ObjectContext.DeleteObject(item);
}
}
base.Clear();
}
/// <summary>
/// Inserts an element into the collection at the specified index.
/// </summary>
/// <param name="index">The zero-based index at which item should be inserted.</param>
/// <param name="item">The object to insert.</param>
public new void Insert(int index, T item)
{
base.Insert(index, item);
m_ObjectContext.AddObject(m_EntitySetName, item);
}
/// <summary>
/// Updates the ObjectContext for changes to the collection.
/// </summary>
public void Refresh()
{
m_ObjectContext.SaveChanges();
}
/// <summary>
/// Removes the first occurrence of a specific object from the collection.
/// </summary>
/// <param name="item">The object to remove from the collection.</param>
public new void Remove(T item)
{
base.Remove(item);
m_ObjectContext.DeleteObject(item);
}
#endregion
}
}
I would probably use a factory pattern to achieve a level of abstraction in your viewModel if you want it.
The downside is that you will have to limit yourself to calling the factory every time you create one of your collections.
so if your factory had an API like this(which you could switch with whatever you wanted):
so now you would:
IFactory
to return your EdmObservableCollection wheneverTCollection is ObservableCollection
ObjectBuilder.SetFactory()
ObjectBuilder.Create<ObservableCollection,MyEntity>();
also if/whenever you would need to change your ORM backend you simply implement a new
IFactory
and callObjectBuilder.SetFactory(factory)
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<T>
. That is for example a view on the WPF UI with only aDataGrid
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<T>
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<T>
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<T>
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 instanceRemove(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:
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 aOrderContactPersonsService.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<T>
) 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.
I think I have worked out the answer. The problem isn't with the collection, it's with what is being passed to the collection. The collection shouldn't be working directly with the ObjectContext; instead, it should work with the Repository for the type of entity that it collects. So, a Repository class should be passed to the collection's constructor, and all the persistence code in the collection should be replaced by simple calls to Repository methods. The revised collection class appears below:
EDIT: Slauma asked about data validation (see his response), so I have added a CollectionChanging event to the collection class I originally posted in my answer. Thanks, Slauma, for the catch! Client code should subscribe to the event and use it to perform validation. Set the EventArgs.Cancel property to true to cancel a change.
The Collection Class
The Event Args Class