Compartilhar via


Object Change Tracking and Optimistic Concurrency

Applies to: SharePoint Foundation 2010

You can add, delete, and edit list items and particular fields in list items by using the LINQ to SharePoint provider. The provider keeps track of the changes your code makes to objects that represent entities in the content database. Also, before it writes your changes, it first determines whether the entities have been changed by other users after they were retrieved by your code. This topic describes the system of object change tracking. For more information about making changes to Microsoft SharePoint Foundation data, see How to: Write to the Content Databases Using LINQ to SharePoint.

Entity Identity

The LINQ to SharePoint provider keeps track of all entities that are returned by queries and all changes to those entities. When a specified entity is returned more than once, the provider will always return the same instance of the entity that it returned the first time. This behavior ensures that an application is always working with the same instance of a specified entity and that it never works with an entity that has been changed by another application.

Optimistic Concurrency and Discrepancies

When DataContext.SubmitChanges() executes, it compares the current state of the entity in the database with the state of the entity when the LINQ to SharePoint provider first returned it (after the last call of the DataContext.SubmitChanges() method). If there is a discrepancy, then some other application has changed the entity after the first retrieval. Application developers can configure the behavior of SubmitChanges() to stop writing more changes when it finds the first discrepancy, or it can be set to continue writing changes to the database and recording each conflict found. Changes that involve a field that is involved in a conflict are not committed to the database. Calling code must resolve discrepancies before the DataContext.SubmitChanges() method can be called again. In an optimistic concurrency scenario, resolution usually involves prompting the user with information about the discrepancy and enabling the user to decide whether to cancel the changes and let the other user’s changes remain, or to overwrite the other user’s changes.

Critical Events, Methods, and Properties

The following sections provide an overview of the principal classes and members your code will use to take advantage of the object change tracking system and to implement writing to the content database with optimistic concurrency.

Tracking Entity State and Original Values

The state of an entity with respect to whether, and how, it has changed is stored in the EntityState property of the class that represents the entity. This property is declared in the ITrackEntityState interface, which you must implement in any class that represents entities that you want to include in the object change tracking system. In general, you want classes that represent content types to implement the ITrackEntityState interface, because each object of such a class represents a particular list item and there are properties in the class that represent the fields of the list item.

Your code may need to cancel changes if a concurrency conflict is detected, so these same content type classes also need to store the original values of their fields. You ensure this by having these classes implement the ITrackOriginalValues interface. The latter defines an OriginalValues property that is a dictionary of property names and values. When a property of an object that instantiates the class is changed, an entry is added to the dictionary that records the property’s original value.

All content type classes inherit from the basic Item content type of SharePoint Foundation. So, typically, only the class that represents the Item content type implements ITrackEntityState and ITrackOriginalValues.

We recommend that you use the SPMetal tool to generate your entity class declarations. This tool generates, by default, a declaration of an Item class to represent the Item content type and configures it to implement ITrackEntityState and ITrackOriginalValues. The following is an example, with extraneous details removed for readability:

[Microsoft.SharePoint.Linq.ContentTypeAttribute(Name="Item", Id="0x01")]
public partial class Item : ITrackEntityState, ITrackOriginalValues, INotifyPropertyChanged, INotifyPropertyChanging
{
    private EntityState _entityState;
    
    private IDictionary<string, object> _originalValues;

    EntityState EntityState { 
        get {
            return this._entityState;
        }
        set {
            this._entityState = value;
        }
    }

    IDictionary<string, object> OriginalValues {
        get {
            return this._originalValues;
        }
        set {
            this._originalValues = value;
        }
    }
    
    public Item() {
        this._entityState = EntityState.Unchanged;
        this.OnCreated();
    }

    // Other member declarations suppressed for readability.
}

Finally, these content type classes (or the Item class from which they inherit) must implement INotifyPropertyChanged and INotifyPropertyChanging. The following code shows how SPMetal implements these interfaces in the Item class.

[Microsoft.SharePoint.Linq.ContentTypeAttribute(Name="Item", Id="0x01")]
public partial class Item : ITrackEntityState, ITrackOriginalValues, INotifyPropertyChanged, INotifyPropertyChanging
{
    // Material omitted.

    public event PropertyChangedEventHandler PropertyChanged;
    
    public event PropertyChangingEventHandler PropertyChanging;
    
    protected virtual void OnPropertyChanged(string propertyName) {
        if ((EntityState.Unchanged == this._entityState)) {
            this._entityState = EntityState.ToBeUpdated;
        }
        if ((this.PropertyChanged != null)) {
            this.PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
        }
    }
    
    protected virtual void OnPropertyChanging(string propertyName, object value) {
        if ((null == _originalValues)) {
            this._originalValues = new Dictionary<string, object>();
        }
        if ((false == _originalValues.ContainsKey(propertyName))) {
            _originalValues.Add(propertyName, value);
        }
        if ((this.PropertyChanging != null)) {
            this.PropertyChanging(this, new PropertyChangingEventArgs(propertyName));
        }
    }

    // Other member declarations suppressed for readability.
}

As you can see, the purpose of the OnPropertyChanging method is to record the original value of a property whose value is changing and then raise the PropertyChanging() event; while the purpose of the OnPropertyChanged method is to raise the PropertyChanged() event after it sets the list item object’s entity state to ToBeUpdated This value means that at least one of the object’s properties has been changed so that, barring a concurrency conflict, the next call of SubmitChanges() writes that change to the corresponding field in the content database. The intention is that these two methods are called by the set accessor of a property that represents a field in the list item. For example, the following demonstrates how SPMetal creates the property of the Item class that represents the Title field of a list item. This version of the generated code is simplified for readability.

[Column(Name="Title", Storage="_title", Required=true, FieldType="Text")]
public string Title {
    get {
        return this._title;
    }
    set {
         if ((this._title != value)) {
            this.OnPropertyChanging("Title", this._title);
            this._title = value;
            this.OnPropertyChanged("Title");
         }
    }
}

DataContext Class

The DataContext class represents the lists and list items of a SharePoint Foundation Web site.; For this reason, it can be thought of as a subset of the content database. It has three members that are critical to the object change tracking system:

  • ObjectTrackingEnabled – This property must be set to true before code can make any changes to the SharePoint Foundation data.

  • SubmitChanges() – This method will write all changes made after the last time it was called to the content database, provided that no concurrency conflicts are found. If a conflict is found, it’s behavior varies depending on the parameters passed to it. It can be told to throw a ChangeConflictException as soon as a conflict is found and stop writing changes. Or it can be told to postpone throwing the exception and continue writing any changes that do not affect a field involved in a conflict to the content database. In the either case, it stores one or more objects that represent conflicts in the ChangeConflicts property.

  • ChangeConflicts – This property holds objects that represent concurrency conflicts found the last time SubmitChanges() was called and at least one concurrency conflict was detected.

EntityList<T> Class

The EntityList<TEntity> class represents a list on the Web site. It has several methods for writing to the content database. The following two are the most critical:

MemberChangeConflict Class

The MemberChangeConflict class represents a possible concurrency conflict with respect to a particular field in a particular list item. Its most important members include the following:

  • OriginalValue – This property represents the value in the field when it was last retrieved from the content database or immediately after the last time SubmitChanges() ran successfully.

  • DatabaseValue – This property represents the current value of the field in the content database. If this value is different from OriginalValue, then some other user’s process has changed the field and the MemberChangeConflict represents an actual concurrency conflict.

  • CurrentValue – This property represents the most recent value of the field in your code’s process. (This is also called the ‘client value,’ although "client” here refers to the front-end Web server.) If your code has changed the field value, CurrentValue is different from OriginalValue.

  • Resolve() – This method resolves a conflict by choosing what value should be applied to the field by the next call of SubmitChanges().

MemberChangeConflict objects are stored in the MemberConflicts property of an ObjectChangeConflict object.

Important

If any field in a list item shows a concurrency conflict; that is DatabaseValue is different from OriginalValue; then a MemberChangeConflict is instantiated not only for that field, but also for any field in which DatabaseValue is different from CurrentValue. This occurs because often changes to fields in a list object must be made on an all-or-none basis. For example, if there is a concurrency conflict for a postal code field, and the resolution is to let the other user’s changes remain, then other fields in an address, such as city, should also not be changed. For this reason, all discrepancies between client and database values must be represented for resolution.

ObjectChangeConflict Class

The ObjectChangeConflict class represents a list item for which there is a concurrency conflict for at least one of its fields. Its two most important members are as follows:

ChangeConflictCollection Class

A ChangeConflictCollection object is a collection of all ObjectChangeConflict objects that are generated by a call to SubmitChanges() that has detected at least one concurrency conflict. Its most important member is the ResolveAll() method, which enables calling code to resolve all child conflicts with a single method call.

ChangeConflictCollection objects are stored in the DataContext.ChangeConflicts property.

See Also

Other Resources

How to: Write to the Content Databases Using LINQ to SharePoint