ASP.NETとSilverlightで作るドキュメント管理アプリ その3

つづき

データベースとの接続

次はツリービューにディレクトリの一覧を表示するために実際にデータベースにアクセスしてデータを取ってくるようにする。これにはLINQ to SQLを使用する。

「SLDoc.dbml」というファイル名で「LINQ to SQL」ファイルを追加する。「sldoc」データベースから全部のテーブルをLINQデザイナにDrag&Dropして、以下のようにデザイン(プロパティ名を変更)しておく。

~/SLDoc.dbml

これでデータの取り出しができるようになったので、後はツリービューに表示すればいいわけだが、単純にトップディレクトリの一覧を取ってくる場合、以下のようなクエリ文を書けばいい。

var db = new SLDocDataContext();
var result = from d in db.Directory
            where d.ParentId == null
            select d;

これをそのままべた書きすると意図がわかりづらいので、「SLDocDataContext」クラスのパーシャルクラスに以下のメソッドを定義する。

SLDoc.cs
using System;
using System.Linq;
using System.Collections.Generic;

namespace SLDoc {
  partial class SLDocDataContext {
      /// <summary>
      /// トップディレクトリの一覧を検索します。
      /// </summary>
      /// <returns>ディレクトリ情報の一覧</returns>
      public IEnumerable<Directory> FindTopDirectories() {
          return from d in this.Directory
                 where d.ParentId == null
                 select d;
      }

      /// <summary>
      /// 指定したディレクトリがサブディレクトリを持っているかどうかを調べます。
      /// </summary>
      /// <param name="directoryId">ディレクトリのId</param>
      /// <returns>サブディレクトリを持っていればtrue</returns>
      public bool HasSubDirectories(int directoryId) {
          var query = from d in this.Directory
                      where d.ParentId == directoryId
                      select d;

          return query.Count() > 0;
      }

      /// <summary>
      /// 指定したディレクトリのサブディレクトリを検索します。
      /// </summary>
      /// <param name="directoryId">ディレクトリのId</param>
      /// <returns>ディレクトリ情報の一覧</returns>
      public IEnumerable<Directory> FindSubDirectories(int directoryId) {
          return from d in this.Directory
                 where d.ParentId == directoryId
                 select d;
      }
  }
}

メソッド名から処理内容がわかるようにしておくのは地味だけど重要なことだ。
「HasSubDirectories」メソッドは指定したディレクトリが子ディレクトリを持っているかどうかを調べるのに使用する。「FindSubDirectories」はサブディレクトリの一覧を検索するのに使用する。

ディレクトリの一覧を表示

「Default.aspx」ページにちょこっと変更を加える。

~/Default.aspx
<%@ Page Language="C#" MasterPageFile="~/Shared/Default.Master" AutoEventWireup="true"
  CodeBehind="Default.aspx.cs" Inherits="SLDoc.Default" %>

<%@ Register Assembly="System.Web.Silverlight" Namespace="System.Web.UI.SilverlightControls"
  TagPrefix="asp" %>
<asp:Content ID="Content1" ContentPlaceHolderID="head" runat="server">

  <script type="text/javascript">
      function onPluginLoaded(s) {
      }
  </script>

  <style type="text/css">
      #main
      {
      }
      #side
      {
          position: absolute;
          width: 300px;
          border: solid 1px silver;
      }
      #content
      {
      }
  </style>
</asp:Content>
<asp:Content ID="Content2" ContentPlaceHolderID="ContentPlaceHolder1" runat="server">
  <div id="main">
      <div id="side">
          <asp:TreeView runat="server" ID="folderTree" ShowLines="True"
              ExpandDepth="0" OnTreeNodePopulate="folderTree_TreeNodePopulate">
          </asp:TreeView>
      </div>
      <div id="content">
          <asp:Silverlight runat="server" ID="xaml1" Width="100%" Height="100%" OnPluginLoaded="onPluginLoaded"
              Source="~/ClientBin/SLDoc.UI.Silverlight.xap" Windowless="True" />
      </div>
  </div>
</asp:Content>

ツリービューの「OnTreeNodePopulate」イベントにイベントハンドラを追加したのと、「ExpandDepth」プロパティを0に設定した。これらの設定はサブディレクトリを動的(+ポストバックなしに)に展開するために必要な設定だ。

「Default.aspx」ページのコードビハインドファイルを以下のように変更する。

~/Default.aspx.cs
using System;
using System.Web.UI.WebControls;

namespace SLDoc {
  public partial class Default : System.Web.UI.Page {
      protected void Page_Load(object sender, EventArgs e) {
          if(!IsPostBack) {
              var db = new SLDocDataContext();

              foreach(var d in db.FindTopDirectories()) {
                  folderTree.Nodes.Add(
                      new TreeNode(d.Name, d.Id.ToString()) {
                          PopulateOnDemand = db.HasSubDirectories(d.Id)
                      }
                  );
              }
          }
      }

      protected void folderTree_TreeNodePopulate(object sender, TreeNodeEventArgs e) {
          var db = new SLDocDataContext();
          var id = int.Parse(e.Node.Value);

          foreach(var d in db.FindSubDirectories(id)) {
              e.Node.ChildNodes.Add(
                  new TreeNode(d.Name, d.Id.ToString()) {
                      PopulateOnDemand = db.HasSubDirectories(d.Id)
                  }
              );
          }
      }
  }
}

ページがロードされた時にトップディレクトリの一覧を列挙して、TreeNodeを生成していく。「HasSubDirectories」メソッドを使用して子ディレクトリが存在するかどうか調べて、存在していれば「PopulateOnDemand」プロパティをtrueに設定している。

「PopulateOnDemand」プロパティをtrueにすると子ディレクトリが存在している事を示す「+」ボタンがツリーノードの横に表示されるようになる。そして、その「+」ボタンをクリックすると「folderTree_TreeNodePopulate」イベントハンドラが呼び出されることになる。

これを実行する前にデータベースにテストデータを突っ込んでおこう。

declare @id int
INSERT INTO Directory VALUES('100', NULL)
SET @id = @@IDENTITY

INSERT INTO Directory VALUES('101', @id)
INSERT INTO Directory VALUES('102', @id)
INSERT INTO Directory VALUES('103', @id)
INSERT INTO Directory VALUES('104', @id)
INSERT INTO Directory VALUES('105', @id)

INSERT INTO Directory VALUES('200', NULL)
SET @id = @@IDENTITY

INSERT INTO Directory VALUES('201', @id)
INSERT INTO Directory VALUES('202', @id)
INSERT INTO Directory VALUES('203', @id)
INSERT INTO Directory VALUES('204', @id)
INSERT INTO Directory VALUES('205', @id)

INSERT INTO Directory VALUES('300', NULL)
INSERT INTO Directory VALUES('400', NULL)
INSERT INTO Directory VALUES('500', NULL)
GO

そして、実行した画面が以下

スクリーンショット

おっと、こんな風に表示されるのはスキンファイルに変更を加えたからだった。

~/App_Themes/Default/Default.skin
<asp:TreeView runat="server">
  <NodeStyle ImageUrl="img/folder.png" />
</asp:TreeView>

フォルダの画像はそのへんから適当にパクってきたものを使用している。

To be continued...

ソース