オブジェクト リレーショナル マッピングの拡張

最終更新日: 2010年3月12日

適用対象: SharePoint Foundation 2010

LINQ to SharePoint プロバイダーが提供するオブジェクト リレーショナル マッピングは、ビジネス ロジックの Microsoft SharePoint Foundation コンテンツ データベースへの LINQ アクセスについて、すべてのシナリオに対応するとは限りません。たとえば、次の場合はマッピングを拡張しなければならない可能性があります。

  • SPMetal で生成できるのがコンテンツ タイプのフィールドだけで、Properties プロパティ バッグ、Attachments プロパティなど、SPListItem オブジェクトのプロパティへの組み込みマッピングがない。

  • SPMetal で、ユーザー設定フィールドのデータ型を使用するフィールドのコードを生成できない。

  • SPMetal で将来を読み取ることができないので、ソリューションを展開した後にユーザーがリストに加えるであろうフィールド (列) をマッピングできない。

  • ターゲット リストのデザインが変更され新しいフィールドが追加されたときに、必ずしも SPMetal を再実行できるとは限らない。たとえば、他の開発チームが SPMetal 生成のコードを対象としている場合がある。

以上の理由から、ICustomMapping インターフェイスが提供されました。これを使用して、オブジェクト リレーショナル マッピングを拡張できます。

LINQ to SharePoint ソリューションの拡張

オブジェクト リレーショナル マッピングを拡張するための基本タスクは次のとおりです。

  • コンテンツ タイプ クラスの新しいプロパティにマップする SPListItem プロパティまたは新しい列を取得する。

  • 新しい列に関する同時実行の競合に対処する。

これらのタスクを容易に達成できるように、LINQ to SharePoint は ICustomMapping インターフェイスを提供します。このインターフェイスを実装するクラスは、新しい列を新しいプロパティにマップできます。また、新しい列を考慮に入れて、同時実行の競合の解決方式を拡張することもできます。

ユーザー設定フィールド タイプを使用する列をマッピングする

拡張コード ファイルでは、まず、新しい列を持つリストのコンテンツ タイプを表すクラスを再宣言します。このクラスは、partial (Visual Basic では Partial) とマークされている必要があります (またこのクラスは、通常、SPMetal で作成される元のコード ファイルの partial で宣言されている必要があります。推奨 SPMetal ツールでは、そのツールによって作成されるすべてのコンテンツ タイプ クラスが partial で自動的に宣言されます)。元の宣言から属性の装飾を繰り返さずに、クラスが ICustomMapping インターフェイスを実装することを指定します。

クラス内で、インターフェイスの 3 つのメソッドを宣言します。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 リストに追加された 2 つの新しい列の内部名の配列を作成する方法を示しています。

  • ISBN は、本の ISBN 番号を保持するように設計されたユーザー設定フィールドのデータ型 (ISBNData と呼ばれます) を使用する列です。

  • UPC-A は、本の UPC-A タイプのバー コードを生成するために使用できる構造化データを保持することを目的とするユーザー設定フィールド型 (UPCAData と呼ばれます) を使用する列です。この内部名は "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;
}

文字列 "*" を指定すると、データベースの SPListItem オブジェクト全体、すべてのプロパティ、およびすべてのフィールドを取得するように LINQ to SharePoint に指示されます。ただし、必ずしもこれを行う必要のないプロパティが 1 つあります。マップする SPListItem の唯一のプロパティが Attachments である場合、Columns 配列内の文字列として "Attachments" を使用できます。これにより、LINQ to SharePoint は、ブール型フィールド (列) "Attachments" だけでなく、Attachments プロパティも取得します。

展開後にユーザーが追加する列をマップする

"*" を Columns 配列内の唯一のアイテムとして使用すると、TKey が String で TValue が Object の場合に、ソリューションの展開後にユーザーがリストに追加する列を、コンテンツ タイプ クラス内の Dictionary<TKey, TValue> プロパティのメンバーにマップすることもできます。これを行うには、各フィールドの内部名を、同じ名前のキーを持つ値リスト エントリにマップします。次に例を示します。

[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 によって生成されるコードの一部であり、それぞれ、PropertyChanging イベントと PropertyChanged イベントを処理します。次の例は、このトピックの前半で説明した、ユーザー設定フィールド型を使用する列の 1 つを示しています。ユーザー設定フィールド型は 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 メソッドによってマップされる新しい列を表すプロパティにのみ書き込みを行います。実際の実装は同じポリシーに従う必要があります。古いプロパティは、ICustomMapping.Resolve(RefreshMode, Object, Object) が呼び出されるまでに、ObjectChangeConflict.Resolve() または MemberChangeConflict.Resolve() メソッドによって既に解決されています。実装が、古いプロパティのいずれかに書き込みを行う場合、前の 2 つのメソッドが完了した処理を無効にするおそれがあります。

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;
        }
    }      
}