对象更改跟踪和乐观并发

上次修改时间: 2010年3月4日

适用范围: SharePoint Foundation 2010

本文内容
实体标识
乐观并发和差异
重要事件、方法和属性

使用 LINQ to SharePoint 提供程序可以添加、删除和编辑列表项以及列表项中的特定字段。该提供程序会跟踪您的代码对表示内容数据库中实体的对象所做的更改。此外,在写入您的更改之前,该提供程序首先会确定在您的代码检索实体后其他用户是否对这些实体进行了更改。本主题介绍对象更改跟踪系统。有关对 Microsoft SharePoint Foundation 数据进行更改的详细信息,请参阅How to: Write to the Content Databases Using LINQ to SharePoint。

实体标识

LINQ to SharePoint 提供程序会跟踪查询返回的所有实体以及对这些实体所做的所有更改。如果指定实体返回多次,该提供程序将始终返回第一次返回的实体的同一实例。这种行为可以确保应用程序始终使用指定实体的同一实例,而不会使用已被其他应用程序更改的实体。

乐观并发和差异

执行 DataContext.SubmitChanges() 时,它会将数据库中实体的当前状态与 LINQ to SharePoint 提供程序第一次返回该实体时(对 DataContext.SubmitChanges() 方法最后一次调用之后)实体的状态进行比较。如果存在差异,则说明在第一次检索之后某个其他应用程序对该实体进行了更改。应用程序开发人员可以将 SubmitChanges() 的行为配置为在找到第一个差异时停止写入更多更改,也可以将其设置为继续向数据库中写入更改并记录发现的每个冲突。不会向数据库提交有关冲突所涉及的字段的更改。调用代码必须先解决差异,才能再次调用 DataContext.SubmitChanges() 方法。在乐观并发方案中,解决方案通常包含向用户提供有关差异的信息,让用户决定是取消更改、保留其他用户的更改,还是覆盖其他用户的更改。

重要事件、方法和属性

您的代码将借助一些主要类和成员来使用对象更改跟踪系统并通过乐观并发写入内容数据库。以下各节对这些主要类和成员进行了概述。

跟踪实体状态和原始值

实体的状态表明实体是否更改以及更改的方式,实体状态存储在表示该实体的类的 EntityState 属性中。此属性是在 ITrackEntityState 接口中声明的,必须在表示要包含在对象更改跟踪系统中的实体的所有类中实现该接口。通常,您需要表示内容类型的类来实现 ITrackEntityState 接口,因为这种类的每个对象都表示一个特殊的列表项,而表示该列表项的字段的类具有很多属性。

您的代码在检测到并发冲突时可能需要取消更改,因此这些相同的内容类型类还需要存储其字段的原始值。通过让这些类实现 ITrackOriginalValues 接口可以确保做到这一点。该接口会定义一个 OriginalValues 属性作为属性名称和值的词典。当实例化类的对象的属性发生更改时,会向记录属性原始值的词典中添加条目。

所有内容类型类都是从 SharePoint Foundation 的基本 Item 内容类型继承的。因此通常情况下,仅表示 Item 内容类型的类会实现 ITrackEntityStateITrackOriginalValues

建议您使用 SPMetal 工具来生成实体类声明。默认情况下,此工具生成 Item 类的声明来表示 Item 内容类型,并对其进行配置以实现 ITrackEntityStateITrackOriginalValues。请参阅下面的示例(为了提高可读性省略了多余的细节):

[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.
}

最后,这些内容类型类(或者它们从中继承的 Item 类)必须实现 INotifyPropertyChangedINotifyPropertyChanging。下面的代码显示 SPMetal 如何在 Item 类中实现这些接口。

[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.
}

显而易见,OnPropertyChanging 方法用来记录其值发生变化的属性的原始值,然后引发 PropertyChanging() 事件;而 OnPropertyChanged 方法则用于在将列表项对象的实体状态设置为 ToBeUpdated 之后引发 PropertyChanged() 事件。该状态值表示至少一个对象的属性已被更改,因此,除非存在并发冲突,否则 SubmitChanges() 的下一个调用会将该更改写入内容数据库中的相应字段。这样做是为了让表示列表项字段的属性的 set 取值函数调用这两个方法。例如,下面的示例演示 SPMetal 如何创建表示列表项的 Title 字段的 Item 类的属性。为了提高可读性,此版本对生成代码进行了简化。

[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 类

DataContext 类表示 SharePoint Foundation 网站的列表和列表项。因此,可以将该类视为内容数据库的子集。该类具有 3 个对对象更改跟踪系统至关重要的成员:

  • ObjectTrackingEnabled – 必须先将此属性设置为 true,代码才能对 SharePoint Foundation 数据进行更改。

  • SubmitChanges() – 如果未发现并发冲突,此方法会将上次调用之后的所有更改写入内容数据库。如果发现冲突,则它的行为将因传递给它的参数而有所不同。可以将其设置为一旦发现冲突立即引发 ChangeConflictException,并停止写入更改;也可以将其设置为暂不引发异常,并继续在内容数据库中写入不影响冲突所涉及的字段的任何更改。在这两种情况下,此方法都会在 ChangeConflicts 属性中存储一个或多个表示冲突的对象。

  • ChangeConflicts – 此属性保存这样一些对象:它们表示上次调用 SubmitChanges() 时发现并发冲突并且至少检测到一个并发冲突。

EntityList<T> 类

EntityList<TEntity> 表示网站上的一个列表。它提供多个用于写入内容数据库的方法。下面的两个方法最重要:

MemberChangeConflict 类

MemberChangeConflict 类表示针对特定列表项中的特定字段可能发生的并发冲突,其最重要的成员包括:

  • OriginalValue – 此属性表示上次从内容数据库中检索时得到的字段的值,或 SubmitChanges() 上次刚成功运行后的字段的值。

  • DatabaseValue – 此属性表示内容数据库中字段的当前值。如果此值与 OriginalValue 不同,则有某个其他用户的进程更改了该字段,MemberChangeConflict 表示实际的并发冲突。

  • CurrentValue – 此属性表示您的代码进程中字段的最新值。(该值也称为"客户端值",但此处的"客户端"指的是前端 Web 服务器。)如果您的代码更改了字段值,则 CurrentValueOriginalValue 不同。

  • Resolve() – 此方法通过选择 SubmitChanges() 的下一次调用应该对字段应用哪个值来解决冲突。

MemberChangeConflict 对象存储在 ObjectChangeConflict 对象的 MemberConflicts 属性中。

重要注释重要信息

如果列表项中的任何字段显示并发冲突(即,DatabaseValueOriginalValue 不同),则不仅要为该字段实例化 MemberChangeConflict,还要为 DatabaseValueCurrentValue 不同的任何字段实例化该对象。这样做是因为对列表对象中字段的更改通常必须是彻底的。例如,如果邮政编码字段存在并发冲突,而解决方案是保留其他用户的更改,则地址中的其他字段(如城市)则不应更改。因此,为了找到解决办法,必须显示客户端值和数据库值之间的所有差异。

ObjectChangeConflict 类

ObjectChangeConflict 类表示至少一个字段存在并发冲突的列表项。下面是它的两个最重要的成员:

ChangeConflictCollection 类

ChangeConflictCollection 对象是所有 ObjectChangeConflict 对象(该对象由至少检测到一个并发冲突的 SubmitChanges() 的调用生成)的集合。其最重要的成员是 ResolveAll() 方法,调用代码使用该方法可通过单个方法调用解决所有子冲突。

ChangeConflictCollection 对象存储在 DataContext.ChangeConflicts 属性中。

请参阅

其他资源

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