扩展对象关系映射

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

适用范围: SharePoint Foundation 2010

LINQ to SharePoint 提供程序提供的对象关系映射不涵盖您可能希望 LINQ 使用您的业务逻辑访问 Microsoft SharePoint Foundation 内容数据库的每个可能方案。在下列情况下,您可能需要扩展该映射。

  • SPMetal 只能为内容类型的字段 生成代码。不存在到 SPListItem 对象的属性(例如 Properties 属性包或 Attachments 属性)的内置映射。

  • SPMetal 不能为使用自定义字段数据类型的字段生成代码。

  • SPMetal 无法预测未来,因此无法映射在部署解决方案后用户将添加到列表中的字段(列)。

  • 目标列表的设计发生更改或向目标列表中添加新字段时,您并不总是能够重新运行 SPMetal。例如,其他开发团队可能已将 SPMetal 生成的代码作为目标。

由于这些原因,提供了 ICustomMapping 接口,以便您可以扩展对象关系映射。

LINQ to SharePoint 解决方案的扩展

以下是扩展对象关系映射的基本任务。

  • 获取映射到内容类型类的新属性的 SPListItem 属性或新列。

  • 处理与新列的并发冲突。

为了轻松完成这些任务,LINQ to SharePoint 提供了 ICustomMapping 接口。实现此接口的类可以将新列映射到新属性。它还可以扩展并发冲突解决方案系统,以便将新列考虑在内。

映射使用自定义字段类型的列

在您的扩展代码文件中,首先重新 声明表示包含新列的列表的内容类型的类。该类必须标记为 partial(在 Visual Basic 中为 Partial)。(它还必须已经在通常由 SPMetal 生成的原始代码文件中声明为 partial。建议您使用的 SPMetal 工具会自动将生成的所有内容类型类声明为 partial。)不要在原来的声明的基础上重复属性修饰,但要指示该类实现 ICustomMapping 接口。

在该类范围内,声明接口的三个方法。下面是内容类型为 Book 的示例。

public partial class Book : ICustomMapping
{
    public void MapFrom(object listItem)
    {
    }

    public void MapTo(object listItem)
    {
    }

    public void Resolve(RefreshMode mode, object originalListItem, object databaseObject)
    {
    }

    // New property declarations go here.

}

使用 CustomMappingAttribute 属性修饰 MapFrom(Object) 方法。在此属性内,构造 String 数组,它包含列的内部 名称,并将该数组分配给 Columns 属性。

备注

列的内部名称不能在 SharePoint Foundation 的 UI 中获取。您需要使用 InternalName 属性通过对象模型来获取它。

下面的示例显示了如何使用 CustomMappingAttribute 创建已添加到"Books"列表的两个新列的内部名称的数组。

  • "ISBN"是使用自定义字段数据类型(称为 ISBNData)的列,该类型用于保存书籍的 ISBN 编号。

  • "UPC-A"是使用自定义字段类型(称为 UPCAData)的列,该类型用于保存生成书籍的 UPC-A 类型条码时所用的结构化数据。此列的内部名称为"UPCA"。

[CustomMapping(Columns = new String[] { "ISBN", "UPCA" })]
public void MapFrom(object listItem)
{
}

LINQ to SharePoint 使用 MapFrom(Object) 方法设置来自内容数据库的属性值。传递到该方法的参数是表示从内容数据库提取的列表项的对象。该方法必须针对每个新列有一行,将该列的字段值分配给您要用于在对象关系映射的扩展中表示该列的属性。

MapTo(Object) 方法用于将值保存到内容数据库的相应字段。传递到该方法的参数是表示从内容数据库提取的列表项的对象。该方法必须针对每个表示新列的属性都有一行,每行将该属性的值分配给内容数据库中该列的字段。

重要注释重要信息

不要从您自己的代码中调用 MapFrom(Object)MapTo(Object) 方法。

下面的示例显示了如何实现这些方法。

[CustomMapping(Columns = new String[] { "ISBN", "UPCA" })]
public void MapFrom(object listItem)
{
    SPListItem item = (SPListItem)listItem;
    this.ISBN = item["ISBN"];
    this.UPCA = item["UPCA"];
}

public void MapTo(object listItem)
{
    SPListItem item = (SPListItem)listItem;
    item["ISBN"] = this.ISBN;
    item["UPCA"] = this.UPCA;
}

您可以向任一方法中添加所需的任何其他逻辑,例如,验证逻辑。

映射 SPListItem 属性

通常,如果您需要在 LINQ to SharePoint 代码中访问 SPListItem 对象的属性(除了该项的内容类型的字段以外),则必须扩展对象关系映射。在这种情况下,您将字符串"*"作为 CustomMappingAttributeColumns 属性的唯一成员。MapFrom(Object)MapTo(Object) 方法的逻辑只将 SPListItem 属性映射到内容类型类的相应属性。如果您在类中使用的属性名称与 SPListItem 类中的相应属性的名称相同,则编写调用代码可能会更容易。下面是一个示例。

[CustomMapping(Columns = new String[] { "*" })]
public void MapFrom(object listItem)
{
    SPListItem item = (SPListItem)listItem;
    this.File = item.File;
}

public void MapTo(object listItem)
{
    SPListItem item = (SPListItem)listItem;
    item.File = this.File;
}

"*"字符串通知 LINQ to SharePoint 从数据库中获取整个 SPListItem 对象、所有属性以及所有字段。有一个属性不需要此操作。如果您希望映射的 SPListItem 的唯一属性是 Attachments,则可以使用"Attachments"作为 Columns 数组中的字符串,LINQ to SharePoint 将不仅提取 boolean 字段(列)"Attachments",而且还提取 Attachments 属性。

映射用户在部署后添加的列

如果使用"*"作为 Columns 数组中唯一的项,则可以在部署解决方案后将用户添加到列表中的列映射到内容类型类中的 Dictionary<TKey, TValue> 属性的成员,其中,TKey 为 String,TValue 为 Object。您是通过将每个字段的内部名称映射到具有相同名称项的字典条目来完成此操作的。下面是一个示例。

[CustomMapping(Columns = new String[] { "*" })]
public void MapFrom(object listItem)
{
    SPListItem item = (SPListItem)listItem;
    foreach (var field in item.Fields)
    {
        this.Properties[field.InternalName] = item[field.InternalName];
    }
}

public void MapTo(object listItem)
{
    SPListItem item = (SPListItem)listItem;
    foreach (var kvp in this.Properties)
    {
        item[kvp.Key] = this.Properties[kvp.Key];
    }
}

映射列表项的属性包

ICustomMapping 方法还可以用于将属性映射到数据库字段中与 SPListItem.Properties 属性相对应的特定哈希表条目。在这种情况下,您不在内容类型类中声明属性包属性,而是为要映射的列表项的属性包中的每个属性声明单独的属性。实现 ICustomMapping 的方法,如下面的示例所示。

[CustomMapping(Columns = new String[] { "*" })]
public void MapFrom(object listItem)
{
    this.PreviousManager = ((SPListItem)listItem).Properties["PreviousManager"];
}

public void MapTo(object listItem)
{
    ((SPListItem)listItem).Properties["PreviousManager"] = this.PreviousManager;
}

管理新列的并发冲突

若要确保您的属性参与了对象更改跟踪系统,请确保属性的 set 取值函数调用内容类型类的 OnPropertyChangingOnPropertyChanged 方法,如下例所示。这些方法是 SPMetal 生成的代码的一部分。它们分别处理 PropertyChangingPropertyChanged 事件。以下是本主题上文讨论的使用自定义字段类型的一个列的示例。注意,自定义字段类型为 ISBNData。

private ISBNData iSBN;

public ISBNData ISBN 
{
    get 
    {
        return iSBN;
    }
    set 
    {
        if ((value != iSBN)) 
        {
            this.OnPropertyChanging("ISBN", iSBN);
            iSBN = value;
            this.OnPropertyChanged("ISBN");
        }
    }
}

备注

不要将 ColumnAttribute 添加到属性声明。

实现 Resolve(RefreshMode, Object, Object) 方法以便在并发冲突的解决过程中加入新列。ObjectChangeConflict.Resolve()MemberChangeConflict.Resolve() 方法检查每个列表项,以查看其内容类型是否实现 ICustomMapping。对于实现的那些列表项,这些方法中的每一个都将调用 ICustomMapping.Resolve(RefreshMode, Object, Object) 方法。传递到 ICustomMapping.Resolve(RefreshMode, Object, Object)RefreshMode 值就是传递到调用方法的那个值。

下面是实现 ICustomMapping.Resolve(RefreshMode, Object, Object) 的一个示例。

重要注释重要信息

该实现只写入到表示将由 ICustomMapping 方法映射的新列的属性。您的实现必须遵循同样的策略。旧的属性已经由 ObjectChangeConflict.Resolve()MemberChangeConflict.Resolve() 方法在调用 ICustomMapping.Resolve(RefreshMode, Object, Object) 时解决。如果您的实现写入到任何旧属性,则撤消前两个方法所执行的操作就会有风险。

public void Resolve(RefreshMode mode, object originalListItem, object databaseListItem)
{
    SPListItem originalItem = (SPListItem)originalListItem;
    SPListItem databaseItem = (SPListItem)databaseListItem;

    ISBNData originalISBNValue = (ISBNData)originalItem["ISBN"];
    ISBNData dbISBNValue = (ISBNData)databaseItem["ISBN"];

    UPCAData originalUPCAValue = (UPCAData)originalItem["UPCA"];
    UPCAData dbUPCAValue = (UPCAData)databaseItem["UPCA"];

    if (mode == RefreshMode.OverwriteCurrentValues)
    {
        this.ISBN = dbISBNValue;
        this.UPCA = dbUPCAValue;
    }
    else if (mode == RefreshMode.KeepCurrentValues)
    {
        databaseItem["ISBN"] = this.ISBN;
        databaseItem["UPCA"] = this.UPCA;        
    }
    else if (mode == RefreshMode.KeepChanges)
    {
        if (this.ISBN != originalISBNValue)
        {
            databaseItem["ISBN"] = this.ISBN;
        }
        else if (this.ISBN == originalISBNValue && this.ISBN != dbISBNValue)
        {
            this.ISBN = dbISBNValue;
        }

        if (this.UPCA != originalUPCAValue)
        {
            databaseItem["UPCA"] = this.UPCA;
        }
        else if (this.UPCA == originalUPCAValue && this.UPCA != dbUPCAValue)
        {
            this.UPCA = dbUPCAValue;
        }
    }
} 

在映射列表项的属性包的情况下,Resolve 的实现如此示例所示。

public void Resolve(RefreshMode mode, object originalListItem, object databaseListItem)
{
    SPListItem originalItem = (SPListItem)originalListItem;
    SPListItem databaseItem = (SPListItem)databaseListItem;

    string originalPreviousManagerValue = 
                originalItem.Properties["PreviousManager"].ToString();
    string dbPreviousManagerValue = 
                databaseItem.Properties["PreviousManager"].ToString();
    
    if (mode == RefreshMode.OverwriteCurrentValues)
    {
        this.PreviousManager = dbPreviousManagerValue;
    }
    else if (mode == RefreshMode.KeepCurrentValues)
    {
        databaseItem.Properties["PreviousManager"] = this.PreviousManager;
    }
    else if (mode == RefreshMode.KeepChanges)
    {
        if (this.PreviousManager != originalISBNValue)
        {
            databaseItem.Properties["PreviousManager"] = this.PreviousManager;
        }
        else if (this.PreviousManager == originalISBNValue && this.PreviousManager != dbPreviousManagerValue)
        {
            this.PreviousManager = dbPreviousManagerValue;
        }
    }      
}