如何:使用 LINQ to SharePoint 写入内容数据库

上次修改时间: 2015年3月9日

适用范围: SharePoint Foundation 2010

本文内容
步骤 1 和步骤 2:获取对网站和列表的引用
步骤 3:验证已启用对象更改跟踪
步骤 4:添加用于添加、删除、回收或更新列表项的基本代码
步骤 5:添加用于管理并发冲突的基础结构

本主题说明如何针对 LINQ to SharePoint 提供程序进行编码,以便在 Microsoft SharePoint Foundation 列表中添加或删除列表项,以及更改列表项中特定字段的值。

步骤 1 和步骤 2:获取对网站和列表的引用

有关如何在代码中获取对要更改其数据的网站和列表的引用的说明,请参阅主题如何:使用 LINQ to SharePoint 进行查询的步骤 1 和步骤 2。

步骤 3:验证已启用对象更改跟踪

ObjectTrackingEnabled 属性必须 为默认值 true,才能使用 LINQ to SharePoint 对数据库进行更改。如果代码将此属性设置为 false,并已使用此值查询了 DataContext 对象,则无法将该属性重置为 true。因此,如果代码要查询 DataContext 对象,然后对内容数据库进行更改,您有两个选择:

  • 避免代码将 ObjectTrackingEnabled 属性设置为 false。

  • 在代码中为同一网站创建新的 DataContext 对象,在执行查询之后、使用代码写入数据库之前找到它。将新 DataContext 对象的 ObjectTrackingEnabled 属性保持为其默认值 true,并使用新对象写入内容数据库。

步骤 4:添加用于添加、删除、回收或更新列表项的基本代码

用于写入内容数据库的基本代码非常简单。您首先要创建对网站和列表的引用,并通过调用 SubmitChanges() 完成操作。在此过程中,使用 EntitySet<TEntity> 类的其中一个 *OnSubmit 方法对代码进行所需更改,或使用原始属性设置语法写入列表字段。在以后的所有示例中,teamSite 都为代表网站的 DataContext 对象,TeamMembers 都为代表 Team Members 列表的 EntitySet<TEntity> 对象。

重要注释重要信息

代表您要添加、删除、回收或更新的列表项的对象必须具有 EntityStateIdVersion 属性。如果代表内容类型的类是由 SPMetal 生成的,则该对象具有这些属性。各 *OnSubmit 方法会向 EntityState 属性分配值。IdVersion 属性是由 SharePoint Foundation 运行时设置的。您的代码不应改写这三个属性的任何一个属性。

添加列表项

若要向列表中添加项目,请创建该列表的内容类型的对象,然后将其传递给 InsertOnSubmit(TEntity) 方法。

重要注释重要信息

代表该内容类型的必需字段的列表项对象的所有属性都必须 具有值,才能将列表项插入列表。(这些相同的属性是使用 [ColumnAttribute] 效果声明的,该效果具有 Required 属性,属性值设置为 true。)例如,所有内容类型都从 SharePoint Foundation 的基本 Item 内容类型继承,此内容类型具有必需的 Title 字段。在 InsertOnSubmit(TEntity) 的调用和 SubmitChanges() 的调用之间,可以向此类属性分配值;但最好尽快初始化必需属性。如果没有可初始化所有必需属性的类构造函数,则可以使用对象初始值,如下例所示。

下面的示例说明如何向列表中添加项目,然后将更改保存到数据库。

// Create the new list item.
TeamMember bob = new TeamMember() { Title="Bob Smith" };

// Set the item to be inserted.
teamSite.TeamMembers.InsertOnSubmit(bob);

// Write changes to the content database.
teamSite.SubmitChanges();

还可以使用 InsertAllOnSubmit(IEnumerable<TEntity>) 方法来插入多个项目。

删除和回收列表项

下面的示例说明如何使用 DeleteOnSubmit(TEntity) 方法从列表中删除项目。

// Set the item to be deleted.
foreach (TeamMember teamMember in teamSite.TeamMembers)
{
    if (teamMember.Title = "Bob Smith")
    {
        teamSite.TeamMembers.DeleteOnSubmit(teamMember);
    }
}

// Write changes to the content database.
teamSite.SubmitChanges();

如果想将项目放入用户的回收站而不是完全删除它,可以使用 RecycleOnSubmit(TEntity) 方法。还可以使用 DeleteAllOnSubmit(IEnumerable<TEntity>)RecycleAllOnSubmit(IEnumerable<TEntity>) 方法同时删除多个项目。

更改列表项中的字段

若要更改列表项的字段值,只需写入代表该字段的属性,如下例所示:

// Set the property to a new value.
foreach (TeamMember teamMember in teamSite.TeamMembers)
{
    teamMember.TopTask = "Fiscal Planning";
}

// Write changes to the content database.
teamSite.SubmitChanges();

通过 SubmitChanges 的单个调用进行多项更改

对于代码使用 SubmitChanges() 调用可以执行更改的数量并没有限制。为了获得最佳性能,代码应尽可能少调用该对象。下面的代码显示的是使用 SubmitChanges() 的单个调用将多种更改写入内容数据库的示例:

// ‘sally’ is a TeamMember object.
teamSite.TeamMembers.RecycleOnSubmit(sally);

// ‘leftCompany’ is an IList of TeamMember objects
teamSite.TeamMembers.DeleteAllOnSubmit(leftCompany);

foreach (TeamMember teamMember in teamSite.TeamMembers)
{
    teamMember.TopTask = "Fiscal Planning";
}

// Write changes to the content database.
teamSite.SubmitChanges();

所提交的更改不必只与一个列表相关。使用 SubmitChanges() 的单个调用可以提交对网站上任何列表和所有列表的更改。

步骤 5:添加用于管理并发冲突的基础结构

如果您对任何字段值进行了更改,则通过调用 SubmitChanges() 方法将任何更改提交到内容数据库之前,对象更改跟踪系统会进行检查,以确定在当前用户进程从数据库中检索列表项之后,是否有其他用户对任何受影响的列表项进行了更改。(有关此系统的详细信息,请参阅对象更改跟踪和乐观并发。)如果存在并发冲突,SubmitChanges() 会引发 ChangeConflictException。此外,还会生成表示该差异的信息的 MemberChangeConflict 对象。如果字段的当前客户端值与数据库中该字段的值存在其他差异,这种差异也用 MemberChangeConflict 对象表示。指定列表项的所有差异都会添加到 ObjectChangeConflict 对象的 MemberConflicts 属性。根据所调用的 SubmitChanges() 的重载以及传递给它的参数,可能存在多个 ObjectChangeConflict 对象。所有这些对象都会添加到 DataContext 对象的 ChangeConflicts 属性。

再次调用 SubmitChanges() 之前,代码必须捕获异常并解决所有差异。在某些情况下,处理这种问题的最佳方式是提示用户决定如何解决引发的每个差异。您可以使用 ChangeConflicts 属性中的 ObjectChangeConflict 对象及其子 MemberChangeConflict 对象(特别是 OriginalValueDatabaseValueCurrentValue 属性)的数据来填充向用户显示的 UI。

提示提示

请考虑在 UI 中清楚地区分两种差异:一种是表示真正的并发冲突(即 OriginalValueDatabaseValue 属性之间的差异),另一种只表示在没有并发冲突的情况下将自动写入内容数据库的更改(即 DatabaseValueCurrentValue 之间的差异,其中 OriginalValue = DatabaseValue

通过调用下列方法的某种组合来实现用户的选择:

在其他情况下,根据应用程序的用途以及它对内容数据库所做的更改的种类,您可以在编码时找到解决差异的最佳方法。在这种情况下,最好调用前面列表中各方法的某种组合,而不对用户进行提示。构建解决方案逻辑时应考虑以下四个主要问题:

  • 在内容数据库中应保留字段值的哪个版本:原始值、数据库中的当前值、应用程序进程中的值(客户端值)还是第四种值?有关指导,请参阅 RefreshMode 和前面列表中各方法的参考主题。此外,虽然下表中的文章是关于 LINQ to SQL 提供程序的,但其中介绍的逻辑也适用于 LINQ to SharePoint。

    如何:通过与数据库值合并解决并发冲突 (LINQ to SQL)

    如何:通过保留数据库值解决并发冲突 (LINQ to SQL)

    如何:通过覆盖数据库值解决并发冲突 (LINQ to SQL)

  • 如果当前用户针对其他用户已从列表中完全删除的列表项提交了更改,您希望怎么做?有关可用选项的详细信息,请参阅 Resolve(RefreshMode, Boolean)ResolveAll(RefreshMode, Boolean)

  • 关于前面两种情况,您需要在何种级别上应用您的决定?例如,假设您希望对每个列表的每个列表项的每个差异应用下列规则:

    • 忽略已被其他用户删除的列表项。

    • 保留您的应用程序进程以及其他用户进程所做的所有更改,如果您的更改与其他用户的更改冲突,则保留您的更改。

    您可以通过 ChangeConflictCollection.ResolveAll() 的单个调用应用此逻辑。

    但是,如果需要保留对某些列表项的所有更改,而取消您自己的进程对某些其他列表项的更改,则您的代码必须循环访问 ChangeConflicts 属性的成员,调用 ObjectChangeConflict.Resolve() 的不同重载,并为不同 ObjectChangeConflict 项目传递不同参数。

    对于某些内容类型,您可能需要对不同字段应用不同规则。在这种情况下,您的代码需要循环访问某些 ObjectChangeConflict 对象的 MemberConflicts 属性的成员,并调用 MemberChangeConflict.Resolve() 的不同重载,并为不同字段传递不同参数。

  • 您希望什么时候引发 ChangeConflictException 异常并停止进行进一步更改?虽然您是使用 SubmitChanges() 的调用来实现决定的,但这是您解决方案逻辑的一部分。您有两种选择:在检测到并发冲突时立即引发该异常,或者继续写入所有挂起的更改。在后一种情况中,如果检测到一个或多个并发冲突将引发异常(并将取消对存在冲突的字段所做的更改)。第二个选项的优点是,MemberConflicts 属性中包含所提交的所有更改引起的所有并发冲突(和其他差异)的完整详细列表。因此,您的解决方案逻辑可以处理所有更改。您可以决定通过调用 SubmitChanges() 的哪个重载来采用哪个选项,以及向它传递哪些参数。

下面的代码显示解决所有差异的最简单方式。

foreach (TeamMember teamMember in teamSite.TeamMembers)
{
    teamMember.TopTask = "Fiscal Planning";
}

try 
{
    teamSite.SubmitChanges();
}
catch (ChangeConflictException e) 
{
    teamSite.ChangeConflicts.ResolveAll();
    teamSite.SubmitChanges();
}

SubmitChanges() 的第一个调用是无参数重载。检测到第一个并发冲突时,它将立即引发异常。因此,对 ChangeConflictCollection.ResolveAll() 的调用只解决该点之前记录的所有差异。如果提交的更改中还包含其他并发冲突,它将导致 SubmitChanges() 的第二个调用引发异常。

下面的代码显示一个比较复杂的解决差异的示例。

foreach (TeamMember teamMember in teamSite.TeamMembers)
{
    teamMember.TopTask = "Fiscal Planning";
}

try 
{
    teamSite.SubmitChanges(ConflictMode.ContinueOnConflict);
}
catch (ChangeConflictException e) 
{
    foreach (ObjectChangeConflict changedListItem in teamSite.ChangeConflicts)
    {
        // If another user has changed properties of a non-manager,
        // leave that other user’s changes, except for the TopTask field.
        if (((TeamMember)changedListItem.Object).IsManager = false)
        {        
             foreach (MemberChangeConflict changedField in changedListItem.MemberConflicts)
            {
                if (changedField.Member.Name == "TopTask")
                {
                    changedField.Resolve(RefreshMode.KeepCurrentValues);
                }
                else
                {
                    changedField.Resolve(RefreshMode.OverwriteCurrentValues);
                }
            }
        }
        // But if another user has changed properties of a manager, let this
        // process’s changes override the other user’s changes.
        else
        {
            changedListItem.Resolve(RefreshMode.KeepCurrentValues);
        }    
    }

    teamSite.SubmitChanges();
} // end catch