【Web開発】「新館―花と植物の写真館」を高速化する

この記事以前に、「新館」「花と植物の写真館」をご利用いただいていた方ならご存じと思うのですが、非常に反応が緩慢でした。緩慢なのは、サーバが今ひとつ貧弱(安いサービスなので)というのもあるのですが、プログラミング的にサボっていた部分もあるのです。それは、左に表示される名前順のリスト、そして科名順のリストの作成です。

このリストは、ASP.NETのWebコンポーネントであるTreeViewを使っていますが、ページが最初に読み込まれる際に、データベースから動的に構築するということを行っていました。花の種類が増えてくるに従い、この処理が重くなってきており、サーバの状況によっては数十秒も待たされることがありました。さすがに、これでは見る気が起きませんよね。

考えてみれば、花の名前の一覧が入っているテーブルが、そんなに頻繁に更新されるわけありませんから、毎回毎回データベースから動的にメニューを構築する、などという必要はないわけです。ならば、メニューはあらかじめ作っておき、データベースが更新されたらメニューを作り直せばよい、そういうことになります。

このための布石が、昨日の記事だったのですね。この記事で、テーブルが最後に変更された日時を取得する方法は確立しました。するとあとは、この日時情報を何に使うか、というわけです。今回採用したのは、メニューをXMLファイルで作成しておき、TreeViewコントロールにバインドするというものです。XMLファイルからのバインドは、データベースから動的に生成するよりもはるかに高速です。

XMLファイルを更新するタイミングは、ファイルの最終更新日時より、データベースの更新日時の方が新しいときです。これで、データベースの最終更新日時をうまく利用することができましたね。こんなコードを書いておけばOKです。

DateTime dbtime = DateTime.MaxValue, filetime = DateTime.MinValue;
ConnectionStringSettings setting =
    ConfigurationManager.ConnectionStrings["ConnectionString"];
DbProviderFactory factory = DbProviderFactories.GetFactory(setting.ProviderName);
DbConnection db = factory.CreateConnection();
db.ConnectionString = setting.ConnectionString;
db.Open();
DbCommand command = factory.CreateCommand();
command.Connection = db;
command.CommandText =
    "SELECT TOP 1 modify_date FROM table ORDER BY modify_date DESC";
DbDataReader reader = command.ExecuteReader(CommandBehavior.SingleResult);
if (reader.Read())
{
    dbtime = (DateTime)reader["modify_date"];
}
reader.Close();
if (File.Exists(Server.MapPath(xml_filename)))
{
    filetime = File.GetLastWriteTime(Server.MapPath(xml_filename));
}
if ((filetime < dbtime))
{
    // XMLファイル作成処理
}
db.Close();

問題は、XMLファイルをどう作成するか?ですが、XmlTextWriterクラスを使ったり、XmlDocumentクラスを使ったりする方法などがあるのですが、多少のオーバヘッドはあるもののXmlDocumentクラスを使ってDOMとして扱った方があとあとのことを考えればよさそうでしたので、こちらを採用しました。

基本は、宣言部を書き出し、ルートノードを書き出し、ルートノードに下位ノードをどんどん書いていく、という感じになります。宣言部の書き出しはこんな感じで。XmlDeclarationオブジェクトを作成し、XmlDocumentオブジェクトにぶら下げる、という感じです。ちなみに、子要素をばんばん作っていくのですが、作成自体はXmlDocumentオブジェクトのメソッドを使って行い、ぶら下げたい要素のオブジェクトにAppendChildメソッドで追加する、そんな感じです。

XmlDocument xmldoc = new XmlDocument();
XmlDeclaration xmldecl = xmldoc.CreateXmlDeclaration("1.0", "UTF-8", null);
xmldoc.AppendChild(xmldecl);

ルート要素を書き出します。タグ名はrootです。要素はXmlElementクラス、作成するのはCreateElementメソッドです。

XmlElement root = xmldoc.CreateElement("root");
xmldoc.AppendChild(root);

ルート要素の下に子要素をぶら下げます。XmlElementメソッドで作るのは一緒です。違うのは、属性を1個作成して、子要素に指定している点です。属性の作成はCreateAttributeメソッドで行い、子要素のAttributesコレクションに加えます。

XmlElement node = xmldoc.CreateElement("child");
root.AppendChild(node);
XmlAttribute attr = xmldoc.CreateAttribute("value");
attr.Value = 0;
node.Attributes.Append(attr);

こんな感じで、ツリーを作っていけば終わるのですが、階層が深くなったり、属性が多くなってくると、冗長で複雑なコードになってきますので、オブジェクト名など、間違えないようにしましょう。

こんな感じでXMLファイルを作成し、それをTreeViewコントロールにバインドすることで、待ち時間は1秒もなくなりました。もちろん、最初にXMLファイルを作成するには相応の時間がかかりますが、これはたいてい私がテストする際に実行されますので、普通にご覧になる皆さんがこのタイミングに出くわすことはないでしょう。

ということで、日々改善、強化に努めている「新館」を、引き続きご愛顧下さいませ。

コメント