Thursday, March 31, 2011

DomainDataSource and Composites, problem with HasChanges

There is a subtle bug in current version of WCF RIA (v1 sp1) related to using composites client-side. Sometimes when you change not only you CompositionRoot object but also one of its children and then submit that changes, you may notice that change tracking started behave strangely i.e. any further modifications of any part of composite will change entity’s HasChanges and DomainContext’s HasChanges to true, but associated DomainDataSource will remain with “no changes” (HasChanges == false). To fix that you need to do two things

  1. Subscribe on SubmittedChanges of your DomainDataSource
  2. AcceptChanges for your entire EntityContainer inside event handler

or speaking C#

YourDomainDataSource.SubmittedChanges += SubmittedChanges;
// workaround to prevent stale HasChanges on DDS
private void SubmittedChanges(object sender, SubmittedChangesEventArgs e)
{
    if (!e.HasError)
    {
        ((IChangeTracking)Context.EntityContainer).AcceptChanges();
    }
}

Monday, March 28, 2011

Composites in WCF RIA (take 2)

Composites are great! Unfortunately this feature of WCF RIA Services is probably one of the most misused and feared by many developers. Let’s just recap WHY it is great.

(for complete description read http://msdn.microsoft.com/en-us/library/ee707346%28v=VS.91%29.aspx)

  1. It gives you composite change tracking and commit/rollback support client-side, i.e. when you change any part of composite you effectively change the whole composite (CompositeRoot.HasChanges == true and all children included in change set)
  2. It gives you single “point of persistence” server-side, i.e. you can persist whole composite inside CRUD methods of its root entity

Point one gives you ability to change/commit/rollback all changes made to composite entity plus you can easily indicate UI that your composite entity has changes (just bind to CompositeRoot.HasChanges)

Point two allows you to encapsulate all you composition CRUD inside 3 methods

  • InsertCompositeRoot
  • UpdateCompositeRoot
  • DeleteCompositeRoot

ok, enough theory, let’s consider some code now

Our domain model will be simple, we have a Spy entity Winking smile Each spy can have multiple names and weapons given

How would our SpyUpdate method look like? (we are using LinqToEntitiesDomainService service)

// composition root
public void UpdateSpy(Spy spy)
{
    spy.Names.Clear();
    spy.Weapons.Clear();

    var original = ChangeSet.GetOriginal(spy);
    if (spy.EntityState == EntityState.Detached)
    {
        if(original != null)
        {
            ObjectContext.Spy.AttachAsModified(spy, original);
        }
        else
        {
            ObjectContext.Spy.Attach(spy);
        }
    }

    ApplyCompositeListChanges(spy, c => c.Names, ObjectContext.SpyName);
    ApplyCompositeListChanges(spy, c => c.Weapons, ObjectContext.SpyWeapon);
}

You might be wondering now, why we need to clear Names and Weapons in first two lines and the reason is … EF will choke if you don’t. When you attach composite root to EF context it will try to attach all related elements as well (names and weapons). It’s ok, the problem is that if you added more that one name, for example, EF will throw an exception on you with something like “Submit operation failed. An object with the same key already exists in the ObjectStateManager. The ObjectStateManager cannot track multiple objects with the same key” (yes, our two new names would have the same empty key)

Don’t worry, we don’t lose that changes by clearing out collection… it will be handled inside ApplyCompositeListChanges method

private void ApplyCompositeListChanges<TEntity, TListElement>(TEntity root, 
    Expression<Func<TEntity, EntityCollection<TListElement>>> selector, 
    ObjectSet<TListElement> objectSet)
    where TEntity : EntityObject
    where TListElement : EntityObject
{
    foreach (TListElement change in ChangeSet.GetAssociatedChanges(root, selector))
    {
        var op = ChangeSet.GetChangeOperation(change);
        
        switch (op)
        {
            case ChangeOperation.Insert:
                if (change.EntityState == EntityState.Added) break;
                
                if (change.EntityState != EntityState.Detached)
                {
                    ObjectContext.ObjectStateManager.ChangeObjectState(change, EntityState.Added);
                }
                else
                {
                    objectSet.AddObject(change);
                }
                break;
            
            case ChangeOperation.Update:
                objectSet.AttachAsModified(change, ChangeSet.GetOriginal(change));
                break;
            
            case ChangeOperation.Delete:
                if (change.EntityState == EntityState.Detached)
                {
                    // RIA bug: EntityKey is null for deleted items ;( 
                    ObjectContext.CreateKey(change);
                    ObjectContext.Attach(change);
                }
                ObjectContext.DeleteObject(change);
                break;
            
            default:
                break;
        }
    }
}

As you can see the logic here is quite simple, we get changes associated to RootEntity (Spy in our case) and apply those changes to EF context one by one. The tricky part here is delete – at the moment of writing (WCF RIA V1 SP1) RIA has this strange behaviour (bug?) - all deleted entities in changeset would have empty EntityKey, preventing you from ObjectContext.Attach. The workaround is simple – we create EntityKey if it’s empty

public static void CreateKey(this ObjectContext context, EntityObject entity)
{
    if (entity.EntityKey == null)
    {
        entity.EntityKey = context.CreateEntityKey(context.GetEntitySetName(entity.GetType()), entity);
    }
}
public static string GetEntitySetName(this ObjectContext context, Type entityType)
{
    var type = entityType;
    while (type != null)
    {
        foreach (EdmEntityTypeAttribute typeattr in 
            type.GetCustomAttributes(typeof (EdmEntityTypeAttribute), false))
        {
            var container = context.MetadataWorkspace
                .GetEntityContainer(context.DefaultContainerName, DataSpace.CSpace);
            var entitySetName = 
                (from meta in container.BaseEntitySets
                where meta.ElementType.FullName == typeattr.NamespaceName + "." + typeattr.Name
                select meta.Name).FirstOrDefault();
            if (entitySetName != null) return entitySetName;
        }
        type = type.BaseType;
    }
    throw new InvalidOperationException(
        string.Format("Unable to determine EntitySetName of type '{0}'.", entityType));
}


In one of my previous posts I described how you can implement complete generic solution to save composites, however this explicit method we have just looked at could help you understand the logic behind it and can be used in real applications as well.

Happy coding!

Friday, March 25, 2011

Long time no see…

I’ve been away from blogging for quite a while, lots of project have been done during that time, literally had no time to blog... obvious excuse for being silent, I know Winking smile… but not anymore… let’s continue our quest to perfect code! Resume mode is on!