からめもぶろぐ。

SharePoint が得意なフレンズなんだね!すごーい!

LINQ to SharePoint の仕組みを理解する

SharePoint 2010 から LINQ to SharePoint という機能が追加されました。これまでの SPQuery によるクエリ検索をより簡単にする仕組みで、Entity Framework を使ったことのある開発者であれば、ほとんど違和感なく使うことができます。SPQuery で使われる CAML は開発者にとって直観的ではなかったので、LINQ to SharePoint によって SharePoint 開発がずっと簡単になります。
LINQ to SharePoint を使うためには、SPMetal というツールでコードを自動生成します。日本語環境だと、コードに日本語が混ざってしまうのと、不要なリストのエンティティ クラスまで作ってしまうのが困りものですが、普通に使うだけなら十分かと思います。今回は、SPMetal で自動生成されるコードを参考に、ゼロからコードを書いてみたいと思います。

事前準備

サンプルとして空のカスタム リストを作成します。

f:id:karamem0:20150826135647p:plain

サンプル コード

github.com

コンテキスト クラス

Entity Framework に DBContext クラスがあるように、LINQ to SharePoint にも DataContext クラスが存在します。コンストラクタでは接続文字列を受け取る代わりにサイトの URL を受け取ります。リストの取得は GetList メソッドで行いますので、これをプロパティとしてラップします。*1

public class SharePointDataContext : DataContext {

    public EntityList<Test> Tests { 
        get { return this.GetList<Test>("テスト"); }
    }

    public SharePointDataContext(string url)
        : base(url) { }

}

エンティティ クラス

SharePoint にはコンテンツ タイプという概念があり、LINQ to SharePoint ではこれをクラスの継承で表現します。すべてのコンテンツ タイプはアイテムから派生しますので、まずは Item クラスを作成します。
ContentTypeAttribute 属性でコンテンツ タイプの名前と ID を指定します。ここの名前がサイトの定義と一致していれば、クラス名は任意で構いません。

[ContentType(Name = "アイテム", Id = "0x01")]
public class Item : ITrackEntityState, ITrackOriginalValues, INotifyPropertyChanged, INotifyPropertyChanging { ... }

アイテムを取得するだけなら必要ないのですが、アイテムを更新するためには、4 つのインターフェースを継承する必要があります。それぞれのインターフェースは以下のような役割を持ちます。

  • ITrackEntityState インターフェイスはエンティティの状態管理に関するプロパティを定義します。
  • ITrackOriginalValues インターフェイスは変更前の値を保持するディクショナリを定義します。
  • INotifyPropertyChanged インターフェイスと INotifyPropertyChanging インターフェイスはプロパティの変更を通知するイベントを定義します。

リストの列は ColumnAttribute 属性でマッピングします。Storage にはプロパティ名ではなくフィールド名を指定する必要があります。プロパティの変更を通知するのを忘れずに。Title 列の例ですが、ID や Version も同様に定義します。

private string title;

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

OnPropertyChanged はおなじみの実装で問題ありませんが、OnPropertyChanging はイベントの発生前に変更前の値を保持するように追加の処理を行います。

public event PropertyChangedEventHandler PropertyChanged;

protected void OnPropertyChanged(string propertyName) {
    var handler = this.PropertyChanged;
    if (handler != null) {
        handler.Invoke(this, new PropertyChangedEventArgs(propertyName));
    }
}

public event PropertyChangingEventHandler PropertyChanging;

protected void OnPropertyChanging(string propertyName, object value) {
    if (this.OriginalValues.ContainsKey(propertyName) != true) {
        this.OriginalValues.Add(propertyName, value);
    }
    var handler = this.PropertyChanging;
    if (handler != null) {
        handler.Invoke(this, new PropertyChangingEventArgs(propertyName));
    }
}

最後に Item クラスを継承した Test クラスを作成します。これが実際のリストの定義になります。今回は列を追加していないのでそのままですが、列を追加した場合は、Item クラスの例と同様に設定できます。

[ContentTypeAttribute(Name = "テスト", Id = "0x01")]
public class Test : Item { ... }

実行

サンプル プログラムを作成します。

private static void Main(string[] args) {
    using (var context = new SharePointDataContext("http://localhost/")) {
        var item1 = new Test() { Title = "タイトル 1" };
        var item2 = new Test() { Title = "タイトル 2" };
        var item3 = new Test() { Title = "タイトル 3" };
        context.Tests.InsertOnSubmit(item1);
        context.Tests.InsertOnSubmit(item2);
        context.Tests.InsertOnSubmit(item3);
        context.SubmitChanges();
        foreach (var item in context.Tests) {
            Console.WriteLine("{0}:{1}", item.ID, item.Title);
        }
        Console.ReadLine();
    }
}

実行すると以下のように表示されます。

1:タイトル 1
2:タイトル 2
3:タイトル 3

SPMetal
コンテンツ タイプの基本的な階層

*1:リスト名の指定が表示名だけで URL や内部名は指定できないようです。