2010年1月16日土曜日

何のものさしかと思えば

http://careerzine.jp/monosashi/search
上場企業の年収って、情報ソースはどこだろう?

2010年1月10日日曜日

ASP.NETでのstatic変数の初期化と利用

先週、職場でASP.NETについての質問を受けましたが、よく分らなかったので週末調べてみました。
質問内容は、(Webサーバは1台でスケールアウトしないものとして)ASP.NETアプリ全体で利用する変数を使う場合に、static変数を使って、Application_Startで初期化する使い方で問題ないか?というもの。

これを調べるに当たって、「.NET エンタープライズWebアプリケーション開発大全Vol.3」とMSDNを利用しました。

まず、Vol.3のP.131
>プロセスリサイクリングやフェイルオーバ時、システム再起動時、アプリケーションリスタート時などにはデータが消失する。
とあります。「など」という言葉が非常に気持ち悪いので、データが消失するのは上記以外に無いものと仮定します。


で、フェイルオーバ時、システム再起動時は判るとして、プロセスリサイクリングとアプリケーションリスタートっていうのが
それぞれ、P.188と、201に書いてある。
プロセスリサイクルとは、プロセス単位で、新プロセスを立ち上げて次回以降の要求を新プロセス側で処理するもので、旧プロセスは(受付処理が終わるか、直ちに)落とされるというもの。
それに対して、アプリケーションリスタートでは、同じプロセスの中に新たなアプリケーションドメインを立てて旧アプリケーションドメインでは受付しなくなるというもの。

なので、プロセスリサイクルとアプリケーションリスタートの違いは、新プロセスを起すか起こさないかの違いで、新アプリケーションドメインを立てるのは違いが無いと読み取れる。

で、Application_Startはいつ呼ばれるかというと、
http://msdn.microsoft.com/ja-jp/library/ms178473(VS.80).aspx
で、

ASP.NET アプリケーションの最初のリソース (ページなど) が要求されたときに呼び出されます。Application_Start メソッドは、アプリケーションのライフ サイクル中に一度だけ呼び出されます。このメソッドを使用してデータのキャッシュへの読み込み、静的な値の初期化などのスタートアップ タスクを実行できます。

とあります。

で、結論(アプリケーションドメインでアセンブリをロードした段階ではリソースがまだ要求されていないので、)アプリケーションリスタートでもワーカープロセスリサイクリング時でもどちらでもその後のリクエスト1個目のときにApplication_Startは呼ばれていそうです。

2次元座標の距離のSQL

遅いっていって見せてもらったSQLがSQL1のもの、
(px,py)からの距離がdistanceの間にあるものを近場から最大100件表示するって言うもの。
Where区が列の計算式となっているためインデックスが聞かない。
距離を測るには、いきなり範囲円内で比較するのではなく、対称点を中心とした正方形の範囲内でまず絞ってから、次に範囲円内にあるかを調べたほうがいいんじゃないかなという記事です。
(ちなみに、3次元なら立方体→球ってな感じで。)


まずは、テーブル準備。こんな感じなテーブルにして見ます。

CREATE TABLE GeoData(
Id int IDENTITY(1,1) NOT NULL,
Name nchar(10) NULL,
X real NOT NULL,
Y real NOT NULL,
CONSTRAINT PK_GeoData
PRIMARY KEY CLUSTERED ( Id ASC) ON PRIMARY)
ON PRIMARY

で、テーブルデータ投入。満遍なく10万の点を投入したあと、x座標が300~310の範囲に満遍なく10万の点を投入し、最後にy座標が300~310の範囲に満遍なく10万の点を投入してみる。

declare @i as int
set @i=0
while @i < 100000
begin
insert GeoData(Name,X,Y)values(@i,rand()*10000,rand()*10000)
set @i=@i+1
end

set @i=0
while @i < 100000
begin
insert GeoData(Name,X,Y)values(@i,300+rand()*10,rand()*10000)
set @i=@i+1
end
while @i < 100000
begin
insert GeoData(Name,X,Y)values(@i,rand()*10000,300+rand()*10)
set @i=@i+1
end

このテーブルにインデックスをはります。

create index GeoData_X on GeoData(X)
create index GeoData_Y on GeoData(Y)


このテーブルについて、結果が同じSQL3つ(SQL1:元のSQL,SQL2:高速化を期待して作ったSQL文,SQL3:SQL2について最適なインデックスを使うように、毎回実行プランを再作成することを指定したSQL文。

流すSQLはこんな感じ。
DECLARE @SQL1 nvarchar(MAX),@SQL2 nvarchar(MAX),@SQL3 nvarchar(MAX),@ParmDefinition nvarchar(MAX)
SET @ParmDefinition = N'@px real,@py real,@distance real'

SET @SQL1 =N'select top(100) Id,Name,X,Y,distance from
(select Id,Name,X,Y,SQRT(SQUARE(X-@px)+SQUARE(Y-@py)) as distance
from GeoData) as Tab where distance< @distance
order by distance'

SET @SQL2 =N'select top(100) Id,Name,X,Y,SQRT(distance2) as distance from
(select Id,Name,X,Y,(X-@px)*(X-@px)+(Y-@py)*(Y-@py) as distance2
from GeoData where X > @px-@distance and X < @px + @distance
and Y > @py-@distance and Y < @py + @distance ) as Tab where distance2< @distance*@distance
order by distance'

SET @SQL3 = @SQL2 + N' option(recompile)'


exec sp_executesql @SQL1,@ParmDefinition,1000,1000,150
exec sp_executesql @SQL2,@ParmDefinition,1000,1000,150
exec sp_executesql @SQL3,@ParmDefinition,1000,1000,150

exec sp_executesql @SQL1,@ParmDefinition,305,1000,10
exec sp_executesql @SQL2,@ParmDefinition,305,1000,10
exec sp_executesql @SQL3,@ParmDefinition,305,1000,10

exec sp_executesql @SQL1,@ParmDefinition,1000,305,10
exec sp_executesql @SQL2,@ParmDefinition,1000,305,10
exec sp_executesql @SQL3,@ParmDefinition,1000,305,10


この結果が下の画像。(今回は厳密さよりも見易さのため、XMLではなく、グラフィカルな実行プランを貼り付けます。)




この結果からだと、SQL1よりもSQL2のほうが20倍ぐらい早くて、SQL2とSQL3の違いはほとんどない。
SQL2,3が同じ実行プランを返すみたいなので、結局はSQL2がいいみたいです。
それでは。

2009年12月27日日曜日

年賀状フォントエクスプローラ

Form1.csがここから。
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Text;
using System.Windows.Forms;

namespace WindowsFormsApplication8
{
public partial class NewYearCardFontExplorer : Form
{
public NewYearCardFontExplorer()
{
InitializeComponent();
}


private void Form1_Load(object sender, EventArgs e)
{
System.Drawing.Text.InstalledFontCollection ifc=new System.Drawing.Text.InstalledFontCollection();
List lstFontFamily = new List();
foreach (FontFamily tmpFontFamily in ifc.Families)
if (tmpFontFamily.IsStyleAvailable(FontStyle.Regular)) lstFontFamily.Add(tmpFontFamily);
comboBoxFont.DataSource = lstFontFamily;
comboBoxFont.DisplayMember = "Name";
comboBoxFont.DropDownStyle = ComboBoxStyle.DropDownList;
trackBarFontSize.Value = 12;
labelFontSize.Text = trackBarFontSize.Value.ToString();
textBoxInputText.Text = "謹賀新年\nあけまして おめでとうございます ことしもよろしくおねがいします。\n Merry Cristmas and Happy New Year";
comboBoxHorizontal.DataSource = Enum.GetNames(typeof(StringAlignment));
comboBoxHorizontal.DropDownStyle = ComboBoxStyle.DropDownList;
comboBoxVertical.DataSource = Enum.GetNames(typeof(StringAlignment));
comboBoxVertical.DropDownStyle = ComboBoxStyle.DropDownList;

listBoxFontStyle.DataSource = Enum.GetValues(typeof(FontStyle));
listBoxFontStyle.SelectionMode = SelectionMode.MultiExtended;
}

private void comboBox_SelectedIndexChanged(object sender, EventArgs e)
{
pictureBox1.Refresh();
}

private void pictureBox1_Paint(object sender, PaintEventArgs e)
{
StringFormat sf = new StringFormat();
sf.LineAlignment = (StringAlignment)Enum.Parse(typeof(StringAlignment), (string)comboBoxVertical.SelectedItem);
sf.Alignment = (StringAlignment)Enum.Parse(typeof(StringAlignment), (string)comboBoxHorizontal.SelectedItem);
FontStyle fs = 0;
foreach (FontStyle tfs in listBoxFontStyle.SelectedItems)
{
fs |= tfs;
}
Font font=new Font((comboBoxFont.SelectedItem as FontFamily).Name, trackBarFontSize.Value,fs);
RectangleF rectangleF = new RectangleF(e.Graphics.ClipBounds.X+50, e.Graphics.ClipBounds.Y+50, e.Graphics.ClipBounds.Width-50, e.Graphics.ClipBounds.Height-50);

//まずは黒字で書く。
if (checkBoxDefaultDrawString.Checked)
{

e.Graphics.DrawString(textBoxInputText.Text,
font,
Brushes.Black, rectangleF, sf);
}


//その上に1文字づつ書く。
if (checkBoxEachCharacter.Checked)
{
DrawEachCharacter(e.Graphics, textBoxInputText.Text, font, null, rectangleF, sf);
}
}

private void trackBarFontSize_Scroll(object sender, EventArgs e)
{
labelFontSize.Text=trackBarFontSize.Value.ToString();
pictureBox1.Refresh();
}
private void DrawEachCharacter(Graphics graphics, string str, Font font, Brush brush, RectangleF rectangleF, StringFormat stringFormat)
{
Brush[] brushes = { Brushes.Red, Brushes.Green, Brushes.Blue };

//文字補正のため、左寄せ上寄せのテキストで1文字目を表示する位置を取得する。
Rectangle r_firstCharacter;
{
CharacterRange[] cr_firstCharacter = { new CharacterRange(0, 1) };
StringFormat sf_firstCharacter = new StringFormat(stringFormat);
sf_firstCharacter.LineAlignment = StringAlignment.Near;
sf_firstCharacter.Alignment = StringAlignment.Near;
sf_firstCharacter.SetMeasurableCharacterRanges(cr_firstCharacter);
Region[] mcr_firstCharacter = graphics.MeasureCharacterRanges(str, font, rectangleF, sf_firstCharacter);
r_firstCharacter = Rectangle.Round(mcr_firstCharacter[0].GetBounds(graphics));
}


for (int i = 0; i < str.Length; i++)
{
CharacterRange[] cr = { new CharacterRange(i, 1) };
StringFormat tsf = new StringFormat(stringFormat);
tsf.SetMeasurableCharacterRanges(cr);
Region[] mcr = graphics.MeasureCharacterRanges(str, font, rectangleF, tsf);
Rectangle r = Rectangle.Round(mcr[0].GetBounds(graphics));
if (r.Left == 0 && r.Top == 0 && i != 0)
{
//1文字目でもないのに、(0,0)に文字を描画しようとするのは、
//おかしい。ただし、制御文字等で、(0,0)の描画はあり得るので
//ループを続ける。
continue;
}
Point WritePoint = new Point(r.Left - (r_firstCharacter.X - Convert.ToInt32(rectangleF.X)), r.Top - (r_firstCharacter.Top - Convert.ToInt32(rectangleF.Top)));
graphics.DrawString(textBoxInputText.Text.Substring(i, 1), font,
brush ?? brushes[i % brushes.Length], WritePoint);

}

}

}
}
Form1.csがここまで。

補正もこのぐらいが限界かなぁ。
アンダーラインとか、打ち消し線とか文字をかぶる物は、1文字ごとにやると難しい。




ちなみに
Form1.Designer.csがここから。
namespace WindowsFormsApplication8
{
partial class NewYearCardFontExplorer
{
///
/// 必要なデザイナ変数です。
///

private System.ComponentModel.IContainer components = null;

///
/// 使用中のリソースをすべてクリーンアップします。
///

/// マネージ リソースが破棄される場合 true、破棄されない場合は false です。
protected override void Dispose(bool disposing)
{
if (disposing && (components != null))
{
components.Dispose();
}
base.Dispose(disposing);
}

#region Windows フォーム デザイナで生成されたコード

///
/// デザイナ サポートに必要なメソッドです。このメソッドの内容を
/// コード エディタで変更しないでください。
///

private void InitializeComponent()
{
this.comboBoxFont = new System.Windows.Forms.ComboBox();
this.pictureBox1 = new System.Windows.Forms.PictureBox();
this.label1 = new System.Windows.Forms.Label();
this.label2 = new System.Windows.Forms.Label();
this.comboBoxHorizontal = new System.Windows.Forms.ComboBox();
this.label4 = new System.Windows.Forms.Label();
this.comboBoxVertical = new System.Windows.Forms.ComboBox();
this.label5 = new System.Windows.Forms.Label();
this.trackBarFontSize = new System.Windows.Forms.TrackBar();
this.labelFontSize = new System.Windows.Forms.Label();
this.textBoxInputText = new System.Windows.Forms.TextBox();
this.checkBoxDefaultDrawString = new System.Windows.Forms.CheckBox();
this.checkBoxEachCharacter = new System.Windows.Forms.CheckBox();
this.listBoxFontStyle = new System.Windows.Forms.ListBox();
((System.ComponentModel.ISupportInitialize)(this.pictureBox1)).BeginInit();
((System.ComponentModel.ISupportInitialize)(this.trackBarFontSize)).BeginInit();
this.SuspendLayout();
//
// comboBoxFont
//
this.comboBoxFont.FormattingEnabled = true;
this.comboBoxFont.Location = new System.Drawing.Point(98, 12);
this.comboBoxFont.Name = "comboBoxFont";
this.comboBoxFont.Size = new System.Drawing.Size(121, 20);
this.comboBoxFont.TabIndex = 0;
this.comboBoxFont.SelectedIndexChanged += new System.EventHandler(this.comboBox_SelectedIndexChanged);
//
// pictureBox1
//
this.pictureBox1.Location = new System.Drawing.Point(14, 220);
this.pictureBox1.Name = "pictureBox1";
this.pictureBox1.Size = new System.Drawing.Size(497, 196);
this.pictureBox1.TabIndex = 1;
this.pictureBox1.TabStop = false;
this.pictureBox1.Paint += new System.Windows.Forms.PaintEventHandler(this.pictureBox1_Paint);
//
// label1
//
this.label1.AutoSize = true;
this.label1.Location = new System.Drawing.Point(12, 20);
this.label1.Name = "label1";
this.label1.Size = new System.Drawing.Size(50, 12);
this.label1.TabIndex = 2;
this.label1.Text = "フォント名";
//
// label2
//
this.label2.AutoSize = true;
this.label2.Location = new System.Drawing.Point(12, 53);
this.label2.Name = "label2";
this.label2.Size = new System.Drawing.Size(67, 12);
this.label2.TabIndex = 2;
this.label2.Text = "フォントサイズ";
//
// comboBoxHorizontal
//
this.comboBoxHorizontal.FormattingEnabled = true;
this.comboBoxHorizontal.Location = new System.Drawing.Point(287, 13);
this.comboBoxHorizontal.Name = "comboBoxHorizontal";
this.comboBoxHorizontal.Size = new System.Drawing.Size(81, 20);
this.comboBoxHorizontal.TabIndex = 0;
this.comboBoxHorizontal.SelectedIndexChanged += new System.EventHandler(this.comboBox_SelectedIndexChanged);
//
// label4
//
this.label4.AutoSize = true;
this.label4.Location = new System.Drawing.Point(231, 17);
this.label4.Name = "label4";
this.label4.Size = new System.Drawing.Size(53, 12);
this.label4.TabIndex = 2;
this.label4.Text = "水平方向";
//
// comboBoxVertical
//
this.comboBoxVertical.FormattingEnabled = true;
this.comboBoxVertical.Location = new System.Drawing.Point(430, 11);
this.comboBoxVertical.Name = "comboBoxVertical";
this.comboBoxVertical.Size = new System.Drawing.Size(81, 20);
this.comboBoxVertical.TabIndex = 0;
this.comboBoxVertical.SelectedIndexChanged += new System.EventHandler(this.comboBox_SelectedIndexChanged);
//
// label5
//
this.label5.AutoSize = true;
this.label5.Location = new System.Drawing.Point(374, 15);
this.label5.Name = "label5";
this.label5.Size = new System.Drawing.Size(53, 12);
this.label5.TabIndex = 2;
this.label5.Text = "垂直方向";
//
// trackBarFontSize
//
this.trackBarFontSize.Location = new System.Drawing.Point(98, 53);
this.trackBarFontSize.Maximum = 100;
this.trackBarFontSize.Minimum = 1;
this.trackBarFontSize.Name = "trackBarFontSize";
this.trackBarFontSize.Size = new System.Drawing.Size(367, 42);
this.trackBarFontSize.TabIndex = 4;
this.trackBarFontSize.Value = 1;
this.trackBarFontSize.Scroll += new System.EventHandler(this.trackBarFontSize_Scroll);
//
// labelFontSize
//
this.labelFontSize.AutoSize = true;
this.labelFontSize.Location = new System.Drawing.Point(476, 53);
this.labelFontSize.Name = "labelFontSize";
this.labelFontSize.Size = new System.Drawing.Size(0, 12);
this.labelFontSize.TabIndex = 5;
//
// textBoxInputText
//
this.textBoxInputText.Location = new System.Drawing.Point(14, 90);
this.textBoxInputText.Multiline = true;
this.textBoxInputText.Name = "textBoxInputText";
this.textBoxInputText.Size = new System.Drawing.Size(451, 113);
this.textBoxInputText.TabIndex = 6;
this.textBoxInputText.TextChanged += new System.EventHandler(this.comboBox_SelectedIndexChanged);
//
// checkBoxDefaultDrawString
//
this.checkBoxDefaultDrawString.AutoSize = true;
this.checkBoxDefaultDrawString.Location = new System.Drawing.Point(518, 14);
this.checkBoxDefaultDrawString.Name = "checkBoxDefaultDrawString";
this.checkBoxDefaultDrawString.Size = new System.Drawing.Size(116, 16);
this.checkBoxDefaultDrawString.TabIndex = 7;
this.checkBoxDefaultDrawString.Text = "デフォルト文字表示";
this.checkBoxDefaultDrawString.UseVisualStyleBackColor = true;
this.checkBoxDefaultDrawString.CheckedChanged += new System.EventHandler(this.comboBox_SelectedIndexChanged);
//
// checkBoxEachCharacter
//
this.checkBoxEachCharacter.AutoSize = true;
this.checkBoxEachCharacter.Checked = true;
this.checkBoxEachCharacter.CheckState = System.Windows.Forms.CheckState.Checked;
this.checkBoxEachCharacter.Location = new System.Drawing.Point(518, 37);
this.checkBoxEachCharacter.Name = "checkBoxEachCharacter";
this.checkBoxEachCharacter.Size = new System.Drawing.Size(96, 16);
this.checkBoxEachCharacter.TabIndex = 8;
this.checkBoxEachCharacter.Text = "一文字毎描画";
this.checkBoxEachCharacter.UseVisualStyleBackColor = true;
this.checkBoxEachCharacter.CheckedChanged += new System.EventHandler(this.comboBox_SelectedIndexChanged);
//
// listBoxFontStyle
//
this.listBoxFontStyle.FormattingEnabled = true;
this.listBoxFontStyle.ItemHeight = 12;
this.listBoxFontStyle.Location = new System.Drawing.Point(478, 90);
this.listBoxFontStyle.Name = "listBoxFontStyle";
this.listBoxFontStyle.Size = new System.Drawing.Size(120, 88);
this.listBoxFontStyle.TabIndex = 9;
this.listBoxFontStyle.SelectedIndexChanged += new System.EventHandler(this.comboBox_SelectedIndexChanged);
//
// NewYearCardFontExplorer
//
this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 12F);
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
this.ClientSize = new System.Drawing.Size(636, 428);
this.Controls.Add(this.listBoxFontStyle);
this.Controls.Add(this.checkBoxEachCharacter);
this.Controls.Add(this.checkBoxDefaultDrawString);
this.Controls.Add(this.textBoxInputText);
this.Controls.Add(this.labelFontSize);
this.Controls.Add(this.trackBarFontSize);
this.Controls.Add(this.label2);
this.Controls.Add(this.label5);
this.Controls.Add(this.label4);
this.Controls.Add(this.comboBoxVertical);
this.Controls.Add(this.label1);
this.Controls.Add(this.comboBoxHorizontal);
this.Controls.Add(this.pictureBox1);
this.Controls.Add(this.comboBoxFont);
this.Name = "NewYearCardFontExplorer";
this.Text = "NewYearCardFontExplorer";
this.Load += new System.EventHandler(this.Form1_Load);
((System.ComponentModel.ISupportInitialize)(this.pictureBox1)).EndInit();
((System.ComponentModel.ISupportInitialize)(this.trackBarFontSize)).EndInit();
this.ResumeLayout(false);
this.PerformLayout();

}

#endregion

private System.Windows.Forms.ComboBox comboBoxFont;
private System.Windows.Forms.PictureBox pictureBox1;
private System.Windows.Forms.Label label1;
private System.Windows.Forms.Label label2;
private System.Windows.Forms.ComboBox comboBoxHorizontal;
private System.Windows.Forms.Label label4;
private System.Windows.Forms.ComboBox comboBoxVertical;
private System.Windows.Forms.Label label5;
private System.Windows.Forms.TrackBar trackBarFontSize;
private System.Windows.Forms.Label labelFontSize;
private System.Windows.Forms.TextBox textBoxInputText;
private System.Windows.Forms.CheckBox checkBoxDefaultDrawString;
private System.Windows.Forms.CheckBox checkBoxEachCharacter;
private System.Windows.Forms.ListBox listBoxFontStyle;

}
}


Form1.Designer.csがここまで。

2009年12月18日金曜日

なんだって!!!

.netで色つきDrawStringとして、TextureBrushとMeasureCharacterRangeを使って版画形式(=プリントごっこ形式)で実現しようと思っていたけど、文字の左や上の部分が少し欠けるときがある。
さっぱりわからなかったが、
http://www.microsoft.com/japan/msdn/archive/community/gdn/ShowPost-24735.htm
や、

http://dobon.net/vb/dotnet/graphics/measurestring.htmlをみると、思わぬことが、、、
>>文字列を描画したときの大きさを取得するには、Graphics.MeasureStringメソッドを使います。しかしMeasureStringメソッドはグリフの突出に備えて前後に余白を入れますので、Graphics.DrawStringメソッドで文字列を描画したとき、これよりも通常は狭い範囲に表示されます。

なんだってーーー。文字の大きさをとろうとしただけで、表示位置が変わるの???
MeasureStringと描画に関係しているところは、、、


stringFormat.SetMeasurableCharacterRanges(characterRanges);


http://msdn.microsoft.com/ja-jp/library/system.drawing.stringformat.setmeasurablecharacterranges.aspx
これの例外条件が問題になってくるかも。


だけど、、マイクロソフトの掲示板の方、

exsample(e.Graphics, "WWWWWWWWWWWWWWW", new Font("Century", 32));
とやると、やっぱりずれる。難しいです。

2009年12月16日水曜日

DrawString

で、色がえをしようとして、版画(=プリントごっこ)のように、TextureBrushを使って、文字を書いてみたけどなんか文字がにじむ。
MeasureCharacterRangesを使って、インクを置く場所に、FillRectangleを使ってみたけど、、、
さらに工夫が居るのかな?

FillRectangleの長方形指定の部分に、RectangleFを指定しているが、これを、上下左右に0.5ドットずつ拡張してみようか?それとも
DrawRectangleで、幅1のペンで塗ってみようか?
ひょっとしたら、FillRectangleに指定したものが、RectangleFでは無くRectangleF.Round()したものだったりしたおちだとうれしいんだけど。

2009年12月13日日曜日

誤訳フィードバック

以下の機能を使ってコードを書いていたら、OverflowExceptionが出てしまいました。
http://msdn.microsoft.com/ja-jp/library/system.drawing.stringformat.setmeasurablecharacterranges(VS.80).aspx#
で、MSDNの解説を見ると、すごい制限がありました。まさか32文字以上を囲むとOverflowExceptionが出るのか?とどきどきしましたが、そうはならず。。。。
結局いつものMSDN誤訳じゃないかと。

で、画面の上の星マークをクリックして、こんな感じでフィードバックしてみました。
”OverflowExceptionの条件が間違っています。
「32 文字を超える範囲が設定されています。」となっていますが、英語版の「32個を超えるCharacterRangeが設定されています。」が正しいと思います。”

前、SQL Server 2005の解説で外部キー波及の解説間違いにフィードバックしたときには知らないうちに修正されていたので、今度も知らないうちに変わってくれるといいんだけれど。。。どうなることやら。