LINQ to Desktop Search

この週末でノート PC の再セットアップを行っているのですが,フォーマット前に Visual C# Codename Orcas Express Edition beta 1 を Windows Vista に入れてみました.さすがに常用マシンで使うなら Virtual PC 版ですが,まあ消す前だしいいだろうと.
せっかく Vista に入れたということで,Windows Vista の売りであるデスクトップサーチを,LINQ から利用できるようにしてみました.ソースコードを付けておきますので,Orcas beta 1 を導入された方は試してみてください.多分 Vista でなくても Windows Desktop Search (WDS) 3.0 が入っていれば動くと思います.

Windows Desktop Search と OLEDB

皆さんもご存じのように,Windows Vista のファイル検索の仕組みは,当初予定されていたようなファイルシステムベースで組み込まれた WinFS 方式が中止され,従来のファイルシステム上にインデックスファイルを生成する WDS 3.0 ベースのものに変更されました.
WDS は OLEDB を通じて開発者に公開されており,Vista のスタートメニューのような検索機能を,それぞれのアプリケーションに組み込むことが可能です.
C# で書かれたインクリメンタル検索のデモが Coding4Fun で公開されていますので,クエリの実行方法についてはこちらを参考にすることにしましょう.

例えば,表示名に "zip" という部分文字列を含むを持つ要素を検索するときのクエリは,以下のようになります.

SELECT "System.ItemNameDisplay" FROM systemindex WHERE CONTAINS("System.ItemNameDisplay", '"*zip*"')

IQueryable<T> を実装して LINQ に対応する

まずはこの "zip" の部分を LINQ 形式の記述で置き換える仕組みを考えてみましょう.ちなみになぜ LINQ to SQL を使わないかというと,Orcas 世代の LINQ to SQL は OLEDB に対応しないらしい*1ためで,その部分を自分で書いてやる必要があるのです.
以下のような C# 3.0 コードに対応してみましょう.

var fragment = "zip";
var results = from file in desktop
              where file.ItemNameDisplay.Contains(fragment)
              select file;

IQueryable<T> インターフェイスを実装することで,LINQ を利用したカスタムのクエリ処理を実装する方法としては,LINQ in Action に LINQ から Amazon の Web Service にクエリを行うサンプルが存在します*2.これにちょうどプロパティの Contain メソッドの引数を評価する部分が存在するので,参考になるでしょう.

Orcas のタイミングで本が出るようなので,こういった LINQ の使い方が気になる方は予約しておくと良いでしょう.

Linq in Action

Linq in Action

使い方

以上 2 つのサンプルを組み合わせて,LINQ でデスクトップサーチを検索できるようにしてみました.
使い方としては,まずデータを受けるエンティティクラスを作成します.

public class DesktopSearchResult
{
    public string ItemNameDisplay;
    public string ItemUrl;
    public string ItemPathDisplay;
    public string[] Kind;
    public Int64? Size;
    public DateTime? DateCreated;
}

ここで,フィールドの名前と型は Shell Properties の頭の "System." を外したものにしてください.リフレクションで内部のクエリに使っています.
後はこんな感じで使います.

static void Main(string[] args)
{
    string fragment = null;
    if (args.Length > 0)
    {
        fragment = args[0];
    }
    else
    {
        while (string.IsNullOrEmpty(fragment))
        {
            Console.Write("Type a segment of filename: ");
            fragment = Console.ReadLine();
        }
    }

    var desktop = new DesktopSearch<DesktopSearchResult>();

    var results = from file in desktop
                  where file.ItemNameDisplay.Contains(fragment)
                  select file;

    foreach (var result in results)
    {
        Console.WriteLine(result.ItemNameDisplay);
    }
}

今のところ,where の条件で使えるのは file.ItemNameDisplay.Contains( expression ) という形のみです.また,expression の内容はクエリ開始時に一度だけ評価され,クエリ中はずっと使い回されます.クエリ開始時に expression を評価して string の確定値を持たないといけないので,expression の中で file を使われたりしてもアウトです.
とはいえ文章で書いても中々伝わらないでしょうから,「やっちゃダメ」なことをするとどうなるか興味がある方は,ステップ実行で確かめてみてください.

おまけ

LINQ って何? って人はとりあえずこの辺からどうぞ

注意

(追記:)このサンプルですが,このままコピペして実戦投入すると SQL Injection の問題がありますのでご注意を.

*1:[https://connect.microsoft.com/VisualStudio/feedback/ViewFeedback.aspx?FeedbackID=269647:title=Feedback ID 269647: LINQ to SQL - Support for OLEDB and ODBC drivers]

*2:id:NyaRuRu:20060727:p1