WCF RIA Services: conflict resolution

With Entity Framework, you can use default behavior to solve concurrent access: last writes wins. You can also choose that EF throws an exception if DB has been changed by another user.

With RIA Services, if you choose this second solution, you can fix conflicts using a systematic way: last writes wins (in this case, it’s useless to let EF throwing an exception) or when there is a conflict we refresh entity using the DB record.

For this, you just have to override DomainService ResolveConflicts method.

protected override bool ResolveConflicts(IEnumerable<ObjectStateEntry> conflicts)
{
    foreach (ObjectStateEntry conflict in conflicts)
        ObjectContext.Refresh(RefreshMode.StoreWins, conflict.Entity);
    return base.ResolveConflicts(conflicts);
}

However, this is often not efficient.

Imagine that, when we have conflict, we want to let the user to choose between his version and the DB one.

How to do this?

In order to factorize my code, I wrote a base class:

public abstract class ViewModelBase<DomainContextType, EntityType>
    where DomainContextType : DomainContext, new()
    where EntityType : Entity
{
    private DomainContextType _domainContext = new DomainContextType();
    private Action<ComparaisonViewModel<EntityType>> _compareEntities;
    private Func<EntityType, bool> _deleteCreation;

    public ViewModelBase(Action<ComparaisonViewModel<EntityType>> compareEntities, Func<EntityType, bool> deleteCreation, Func<DomainContextType, EntitySet<EntityType>> getEntitySet)
    {
        _compareEntities = compareEntities;
        _deleteCreation = deleteCreation;
        EntitySet = getEntitySet(_domainContext);
    }

    protected DomainContextType DomainContext
    {
        get { return _domainContext; }
    }

    private EntitySet<EntityType> _entitySet;
    protected EntitySet<EntityType> EntitySet
    {
        get { return _entitySet; }
        private set { _entitySet = value; }
    }

    private ICommand _saveCommand;
    public ICommand SaveCommand
    {
        get
        {
            return _saveCommand ?? (_saveCommand = new FuncCommand(() =>
            {
                DomainContext.SubmitChanges(so =>
                {
                    if (so.HasError)
                    {
                        Application.Current.RootVisual.Dispatcher.BeginInvoke((Action)(() =>
                        {
                            foreach (EntityType entity in so.EntitiesInError)
                            {
                                if (entity.EntityConflict.IsDeleted)
                                {
                                    EntitySet.Detach(entity);
                                    if (_deleteCreation(entity))
                                        EntitySet.Add(entity);
                                }
                                else
                                {
                                    EntityType dbEntity = (EntityType)entity.EntityConflict.StoreEntity;
                                    ComparaisonViewModel<EntityType> comparaisonviewModel = new ComparaisonViewModel<EntityType>() { Entity1 = entity, Entity2 = dbEntity };
                                    _compareEntities(comparaisonviewModel);
                                    comparaisonviewModel.Validated += () =>
                                    {
                                        if (comparaisonviewModel.SelectedEntity == entity)
                                            entity.EntityConflict.Resolve();
                                        else
                                        {
                                            EntitySet.Detach(entity);
                                            EntitySet.Attach(dbEntity);
                                        }
                                    };
                                }
                            }
                        }));
                        so.MarkErrorAsHandled();
                    }
                }, null);
            }));
        }
    }
}



 


public class ComparaisonViewModel<EntityType>
{
    public EntityType Entity1 { get; set; }
    public EntityType Entity2 { get; set; }

    public IEnumerable<EntityType> Entities
    {
        get
        {
            yield return Entity1;
            yield return Entity2;
        }
    }

    public EntityType SelectedEntity { get; set; }

    private ICommand _okCommand;
    public ICommand OkCommand
    {
        get
        {
            return _okCommand ?? (_okCommand = new FuncCommand(() =>
                {
                    if (Validated != null)
                        Validated();
                }));
        }
    }

    public event Action Validated;
}



Then we can inherit from our ViewModelBase class:



public class ProductViewModel : ViewModelBase<NorthwindDomainContext, Product>
{
    public ProductViewModel(Action<ComparaisonViewModel<Product>> compareProducts, Func<Product, bool> deleteCreation)
        : base(compareProducts, deleteCreation, northwindDomainContext => northwindDomainContext.Products)
    {
        DomainContext.Load<Product>(DomainContext.GetProductsQuery());
    }

    public EntitySet<Product> Products
    {
        get { return DomainContext.Products; }
    }
}



User choice has to be done by the view which isn’t known by the ViewModel which knows the logic, so we use delegates:



DataContext = new ProductViewModel(
    productComparaisonViewModel =>
    {
        ProductComparaison productComparaison = new ProductComparaison(productComparaisonViewModel);
        productComparaison.Show();
    },
    product => MessageBox.Show(string.Format("The product {0} was deleted, do you want to create it again?", product.ProductName), "", MessageBoxButton.OKCancel) ==
        MessageBoxResult.OK);



Hope that helps !

This entry was posted in 15143, 7671, 7674. Bookmark the permalink.

Leave a Reply

Your email address will not be published. Required fields are marked *


*

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>