views:

913

answers:

3

Hi!

I have the following scenario:

  1. Entities are loaded from the database.
  2. One of them is presented to the user in a Form (a WPF UserControl) where the user can edit properties of that entity.
  3. The user can decide to apply the changes to the entity or to cancel the editing.

How would I implement something like this with the EntityFramework?

My problem is that, when I bind the UI directly to the Properties of the Entity, every change is instantanously applied to the entity. I want to delay that to the moment where the user presses OK and the entity is validated successfully.

I thought about loading the Entities with NoTracking and calling ApplyPropertyChanges after the detached entity has been validated, but I'm not entirely sure about the correct way to do that. The docu of the EntityFramework at MSDN is very sparse.

Another way I could think of is to Refresh the entity with StoreWins, but I don't like resetting the changes at Cancel instead of applying changes at Ok.

Has anyone a good tutorial or sample?

A: 

The normal way of doing this is binding to something that implements IEditableObject. If and how that fits in with the entity framework, I'm not sure.

HTH, Kent

Kent Boogaart
A: 

I suggest IEditableObject, too, and additionally IDataErrorInfo.

The way i do it is, i basically have a viewmodel for an entity that takes the entity as constructor parameter (basically a wrapper object).

In BeginEdit, i copy the entity properties to my viewmodel, so if i do CancelEdit, the data is only changed in the ViewModel and the original Entity hasn't changed. In EndEdit, i just apply the ViewModel properties to the Entity again, or course only if validation has succeeded.

For validation i use the methods of IDataErrorInfo. I just implement IDataErrorInfo.Error so that it checks each Property name via IDataErrorInfo[string columnName] and concatenates eventual error messages. If it's empty, everything is ok. (not sure if Error is meant to be used that way, but i do it)

If i have other Entities attached to my original Entity, such as Customer.Orders, i create them as nested ViewModels in the original Entity's ViewModel. The original ViewModel calls it's subModels' Begin-,Cancel-,EndEdit / Error methods in it's own implementations of those methods then.

It's a bit more work, but i think it's worth it because between BeginEdit and EndEdit, you can be pretty sure that nothing changes without you noticing it. And having a code snippet for INotifyPropertyChanged-enabled properties helps a lot, too.

Botz3000
+3  A: 

One options is what you said do a no-tracking query.

ctx.Customers.MergeOption = MergeOption.NoTracking;
var customer = ctx.Customers.First(c => c.ID == 232);

Then the customer can modify 'customer' as required in memory, and nothing is actually happening in the context.

Now when you want actually make the change you can do this:

// get the value from the database
var original = ctx.Customers.First(c => c.ID == customer.ID);
// copy values from the changed entity onto the original.
ctx.ApplyPropertyChanges(customer); .
ctx.SaveChanges();

Now if you are uncomfortable with the query either for performance or concurrency reasons, you could add a new extension method AttachAsModified(...) to ObjectContext.

that looks something like this:

public static void AttachAsModified<T>(
    this ObjectContext ctx, 
    string entitySet, 
    T entity)
{
    ctx.AttachTo(entitySet, entity);

    ObjectStateEntry entry = 
            ctx.ObjectStateManager.GetObjectStateEntry(entity);

    // get all the property names
    var propertyNames = 
            from s in entry.CurrentValues.DataRecordInfo.FieldMetadata
            select s.FieldType.Name;

    // mark every property as modified    
    foreach(var propertyName in propertyNames)
    {
        entry.SetModifiedProperty(propertyName);
    }
}

Now you can write code like this:

ctx.Customers.MergeOption = MergeOption.NoTracking;
var customer = ctx.Customers.First();
// make changes to the customer in the form
ctx.AttachAsModified("Customers", customer);
ctx.SaveChanges();

And now you have no concurrency or extranous queries.

The only problem now is dealing with FK properties. You should probably look at my index of tips for help here: http://blogs.msdn.com/alexj/archive/2009/03/26/index-of-tips.aspx

Hope this helps

Alex

Alex James
I've currently implemented it with the ApplyPropertyChanges approach, but attaching a modified entity looks also interesting. Thx for the input!
Thomas Danecker