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

2009年12月1日火曜日

.netのシステムリソース監視

.netframeworkでシステムリソースを確認するのなら、
http://msdn.microsoft.com/ja-jp/library/system.diagnostics.performancecounter(VS.71).aspx
と、
http://msdn.microsoft.com/ja-jp/library/system.management.managementobjectsearcher(VS.71).aspx
ぐらいかな?

アンチウィルスソフト、そろそろ乗り換えようかな

http://www.microsoft.com/security_essentials/default.aspx
正式運用してから2ヶ月以上たってるのでもうそろそろいいかな?

2009年11月30日月曜日

特定のアプリだけ英語キーボードになる

WordとExcelとInternetExprorとVisual Studioは、英語キーボードになって、notepadや、サクラエディタでは、日本語キーボードになる。

英語キーボードの動きになったときには、ナチュラルインプットっていうのを片っ端からIMEに置き換えていけば、どのアプリも日本語キーボードとしてつかえるけど、ネットを探してみたら、いかが見付りました。
http://support.microsoft.com/kb/880629/ja
レジストリを触らなきゃいけないみたい。

2009年11月15日日曜日

画面回転

びっくりした。
Vistaでは、[Ctrl]+[Alt]+矢印で、画面が回転するんですね。[Ctrl]+[Alt]+↓で、画面が逆さになったときにはどうしたものかと思っちゃいました。

それはそうと、Vistaって、[ネットワークプロジェクタへの接続]ってありますが、ネットワークプロジェクタのサーバソフトって無いかな?とか、RDPのプロトコルを実装しているクラスライブラリがないかなぁ?

メモ

マイクロソフトがリリース元のソフト
Process Explorer
Paint.NET


MSDN
http://msdn.microsoft.com/ja-jp/library/default.aspx

マイクロソフト テクニカルドキュメント
http://msdn.microsoft.com/ja-jp/library/bb905071.aspx

2009年11月14日土曜日

C#の仕様書を印刷してみた

Visual Studio2005から、C#の1.2と2.0の仕様書を印刷してみた。
中閉じ、とじしろ10mmで約120枚。
印刷して、中央で切って、のり付けして、ハンドドリルで穴をあけて、針金を通して製本テープで留めてみた。
製本はできたけど、切ったときちゃんと中央で切れていなかったのか、背の部分がガッタガタ。

http://www.geocities.jp/ryou_tanoue/

のページをみると、平とじっていう方法みたい。

他に、糸かがりとじっていうのがあるので、今度C#3.0の仕様書を印刷するときにやってみよう。
多分印刷するときに、分冊指定,5枚ごとにすれば多分よさそう。
綿糸は、裁縫のときに使う糸、麻糸は、タコ糸でいいのかな?

2009年11月11日水曜日

SQL Server 2005の表データをコピペしてExcelに貼り付けるときは

SQL Server Management Studioで、オブジェクトエクスプローラから「テーブルを開く」で、表示されたテーブルデータで左上見出し行の行番号列の交差する灰色文字なしの部分をクリック→コピー→Excelで貼り付けとすると日本語が文字化けする。

SQLのselect文の結果をコピペすると文字化けしないのに。。。

で、文字化けする場合は、形式を選択して貼り付け→Unicode テキスト で貼り付ければいいらしい。

http://social.msdn.microsoft.com/Forums/ja-JP/sqlserverja/thread/7752ea7b-0e2c-4562-a823-a68dbee1e22a


別に、形式を選択して貼り付け→テキスト でもいいと思う。
そもそも、デフォルトでHTMLで貼り付けしようとしているのが間違いのような。。。

範囲指定の日時について

画面で、A時点からB時点までという絞り込みを行う際、
日付と時間を指定しますが、とある画面では、範囲指定に、時刻を省略できるものがある。
省略した場合、どのように扱えばいいんだろう?人によって範囲内外の考え方は違うんじゃないのかな?

例えば、範囲を2009/11/10 10:00~2009/11/11 11:00と指定した場合、2009/11/11 11:00:05は範囲内?範囲外?

例えば、範囲を2009/11/10 ~2009/11/11と指定した場合、2009/11/11 00:00:00は範囲内? 2009/11/11 00:00:01は? 2009/11/11 14:20は?

境界値の扱いも難しいのに、境界が幅を持っていた場合、さらにややこしくなる。
一般的な範囲の考え方ってどういう風なんだろう?

時刻切り捨ての日付比較のsql文

時刻を無視して、日付だけでシステム日付と比較するSQL

SELECT COL_1,COL_2,...
FROM TABLE_1
WHERE
CONVERT(char, TABLE_1.LastReportDateTime,111) <= CONVERT(char, DATEADD(d, -7, CURRENT_TIMESTAMP), 111)
みたいなものを昔書いた覚えがあるけど、今考えると効率面でよくなかったですね。
問題は2点。TABLE_1の全行に対して、CONVERT関数を呼び出していることと、関数を呼び出した後の比較のため、インデックスが張ってあっても、条件の絞り込みには使えないこと。

以下のように書けば、CONVERT関数を処理するのはCURRENT_TIMESTAMPの1件(SQL処理中はCURRENT_TIMESTAMPは固定値なので)でインデックスが張ってある時に範囲外のものは比較しないで対象外にしてくれそう。
SELECT COL_1,COL_2,...
FROM TABLE_1
WHERE
TABLE_1.LastReportDateTime < CONVERT(char, DATEADD(d, -6, CURRENT_TIMESTAMP), 111)

2009年11月8日日曜日

ADO.NETでのタイムアウト値のまとめ

ADO.NETでデータベースにアクセスするときこんなかき方をすると思います。

using (TransactionScope ts = new TransactionScope())
{
DataSet1.ProjectDataTable dt1 = new DataSet1.ProjectDataTable();
using (ProjectTableAdapter ta1 = new ProjectTableAdapter())
{
dt1 = ta1.GetData();
}
}
上のプログラムで、SQL Server に接続してデータを取るのですが、このときのタイムアウトは3種類あります。
1.トランザクション処理のトランザクションタイムアウト(デフォルトでは 60 秒) 参考:http://msdn.microsoft.com/ja-jp/library/ms973865.aspx

2.SQL Serverへ接続したときの応答待ちのコネクションタイムアウト(デフォルトで15秒)参考:http://msdn.microsoft.com/ja-jp/library/system.data.sqlclient.sqlconnection.connectiontimeout(VS.80).aspx


3.SQLコマンドを実行したときの応答待ちのコマンドタイムアウト(デフォルト30秒)参考:
http://msdn.microsoft.com/ja-jp/library/system.data.sqlclient.sqlcommand.commandtimeout(VS.80).aspx

上のテーブルアダプタ操作でそれぞれのタイムアウト値を変更するのは以下の通り。


1.トランザクションタイムアウト変更

using (TransactionScope ts = new TransactionScop(TransactionScopeOption.Required,new TimeSpan(0,0,30))

2.コネクションタイムアウト変更
ta1.Connection.ConnectionTimeOut =10;
dt1 = ta1.GetData();
※TableAdapter.Connectionプロパティのスコープはinternalなので同一アセンブリからしか直接参照できない

3.コマンドタイムアウト変更
どうやら、コードを書かないと無理みたい。
http://gushwell.ldblog.jp/archives/50527778.html
で、CommandTimeOutのプロパティを外だしにして、

ta1.CommandTimeOut=60;
dt1 = ta1.GetData();

分散トランザクションへの昇格の防止について

ADO.NET2.0では、分散トランザクションへの昇格が自動で行われますが、Windows 2003やXPで開発していて、APサーバとDBサーバが分離されている場合、意図しなかったところで分散トランザクションに昇格して面倒になることがある。

例えば、以下のようなコードを書いたとき、MSDTCを設定していないとエラーとなってしまう。
using (TransactionScope ts = new TransactionScope())
{
DataSet1.ProjectDataTable dt = new DataSet1.ProjectDataTable();
using (ProjectTableAdapter ta = new ProjectTableAdapter())
{
dt = ta.GetData();
dt = ta.GetData();
}
}

エラーとなるのは以下の理由。
1.トランザクションを設定する。
2. 1個目のta.GetData()で、Connection.Open()が呼び出される。
3.GetData()用のSQLが実行される。
4.Connection.Close()が呼び出される。
5.2個目のta.GetData()で、Connection.Open()が呼び出される。
6.トランザクション中にConnection.Open()が2回目に呼び出されたので、トランザクションが分散トランザクション制御になる。
7.DBサーバ側からAPサーバ側に分散トランザクション用のやり取りが始まる。
8.MSDTCの設定で、受信を拒否していたりMSDTCが起動していなかったりすると、分散トランザクション制御の処理が出来ない。
9.トランザクション処理がエラーとなる。

なので、APサーバ側でもMSDTCの受信を許可するようにするか、分散トランザクションへの昇格を防止すればよいことになる。

そもそも、何で、同じテーブルアダプタなのにいちいちConnection.Close()をするかというと、GetData()の中の処理で、Fill()を呼び出しているから。Fill()の処理では、ConnectionがOpen()していないとOpen(),SQL実行,Close()をして、ConnectionがOpenしていると、SQL実行のみするという仕組みのため。

Fillの動きは以下のページを参照。
http://msdn.microsoft.com/ja-jp/library/ms172152(VS.80).aspx

なので、上のプログラムでも、分散トランザクションにしないために、最初からConnection.Open()をしておけばよかったことになる。
つまり、上のプログラムを書き直すと、こんな感じ。
using (TransactionScope ts = new TransactionScope())
{
DataSet1.ProjectDataTable dt = new DataSet1.ProjectDataTable();
using (ProjectTableAdapter ta = new ProjectTableAdapter())
{
ta.Connection.Open();
try
{
dt = ta.GetData();
dt = ta.GetData();
}
finally
{
ta.Connection.Close();
}
}
}

なお、違うクラスのテーブルアダプタでも、接続先DBや接続方法が同じであれば、以下の書き方のように、接続を使いまわすことにより、分散トランザクションへの昇格を回避できます。
using (TransactionScope ts = new TransactionScope())
{
DataSet1.ProjectDataTable dt1 = new DataSet1.ProjectDataTable();
DataSet2.TaskDataTable dt2 = new DataSet2.TaskDataTable();
using (ProjectTableAdapter ta1 = new ProjectTableAdapter())
{
using (TaskTableAdapter ta2 = new TaskTableAdapter())
{

ta1.Connection.Open();
try
{
dt1 = ta1.GetData();
ta2.Connection = ta1.Connection;
dt2 = ta2.GetData();
}
finally
{
ta1.Connection.Close();
}
}
}
}
ただ、テーブルアダプタのConnectionプロパティは、internal指定なので、テーブルアダプタと同じアセンブリからでしか、参照、設定が出来ないので、DLLで分離している場合は注意が必要。

windowsのドライブを分けるとしたら、インストール先の判断はどうすればいいんだろう?

http://detail.chiebukuro.yahoo.co.jp/qa/question_detail/q1015481010
の記事や、
http://www.microsoft.com/japan/athome/magazine/ucontents/users/winXP/0503/default.aspx
の記事を見て、何か気になった。
上の情報では、Windowsや、ソフト本体のインストールは、Cドライブにインストールして、データをDドライブに入れるべき、と書いてある。私も同意しますが、会社では、Cドライブは極力小さくして、SQL Server本体や、Visual Studio本体もDドライブに分けた方がいいという人もいる。(Cドライブがそれほど小さいわけでもないのに)
その人に何度聞いてもメリットを理解できなかったのだけれど、1番上の質問者もそうなので、何か本当の意味があるのかもしれません。何だろう???

といっても私は、Cドライブ1本で、最重要なファイル(家ではデジカメ画像、会社では開発物など)は外部の共有フォルダにおいて、それ以外のもの(メールぐらいまで)は、消えたら諦めるという立場をとってますが、、、そんなんで情報リテラシーが低いなんて言わないでね。

テーブル設計その3?

http://remindbook.blogspot.com/2009/10/blog-post_3083.html
で、コメントをいただいたので、もうちょっとだけ考えてみました。

とその前に、最初に言い訳。
昔、以下の記事で、テーブル設計をした際、
http://remindbook.blogspot.com/2008/06/blog-post.html
やっちゃ駄目といったIdや、Nameなど、バリバリに使っていますが、あれはすごく小さなデータベースなので、本当にあれで業務アプリを作ってはいけないと思ってます。
あんなのは説明するために作ったテーブルに過ぎません。まあ何とか外部キー定義しているので、クエリビルダ(Accessや SQL Server ManagementStudioのView作成や、Visual Studio のテーブルアダプタ作成などで出てくるもの)にProjectとTaskをおいたときにそれぞれIdで自動で連結されるということは無いのですが、外部キーが張ってなかったら、テーブルをおいた直後にId列同士でJoinされます。

で、本題。1人で作る業務データベースなら、列名はPersonIdなど一般的なものではなく、業務で意味ある名前AuthorIdとするべきだと思います。
列がカードだと思うと、カードを束ねるクリップのようなものがテーブル名で、クリップを外してバラバラにした後に、別のクリップでカードを束ねたときにも、なんとなく分るような列名にしたほうがいいんじゃないかな?と思っています。
列名を一般名にして、テーブル結合の際に列名変更を強制するというSQL作成ルールを作っても、きっと誰も守ってくれないと思うので、そんなルールは作るべきではないと思うし。。

まぁ、業務なんてどんどん変わっていくものなので、時には列名が決めにくいということはあるとは思いますが、データは、処理ほど頻繁に変わるわけではないし、ある程度変更を許容できるように設計するはずなのでなんとなくうまくいくんじゃないかなあ?


ただ、テーブル設計をする人が複数人になった場合は、列名など些細なことにこだわってられません。(テーブル数が数百~数千となり、列数が一万を超えたりしますから。)
そうなった場合は、各テーブルの管理はそれぞれのチームが管理することとして、DB共通ルールは、ドメインとキーのみ見ることになると思います。ドメインでは、ユーザー名は○桁の文字列/性別は1が男,2が女のtinyintなど/のルールに従った設計になっているかというドメインチェックと、主キーと外部キーが正しいか?という寒天だけになると思います。
ただ、ドメイン設計をしても、ユーザ定義型(create type)は絶対に作るべきではないと思ってます。そんなに人は賢くないので、ユーザ定義型を導入すると、ユーザ定義型を使っている部分/使っていない部分が混在するか、同じようなユーザ定義型ができるに決まってるから。
同じ意味でスキーマ名をつけることはないんじゃないかなと思います。今まで携わったプロジェクトでは、すべて、すべてdbo.テーブル名にしちゃってました。

2009年11月7日土曜日

C#のswitch文のフォールスルー

できないもんだと思ってたけど、会社のC#教材を読み漁ってたらgoto文で代用できるみたい。
こんな感じ。
int a = 2;
switch (a)
{
case 1:
a++;
break;
case 2:
a++;
goto case 3;
case 3:
a++;
goto case 1;
default:
a = 0;
break;
}


でも、こんなのやるべきじゃないね。

C#の言語仕様のありか

http://msdn.microsoft.com/ja-jp/library/ms228593.aspx
の通り、VisualStudio2008を入れたら一緒に入るみたい、

C:\Program Files\Microsoft Visual Studio 9.0\VC#\Specifications\1041\CSharp Language Specification.doc

を見てみると、おおぅ、C#なんか半歩いく音楽教本みたいだ。日本語でよかった。ページ数は、、、575ページもあるんだ。
とりあえず、ちょっとづつ読むとして、、、下のようなクラス定義があったとして、フィールドの初期化とインスタンスコンストラクタのどっちが先に動くか分かればいいんだけど…

using System.Collections.Specialized;
class cls
{
public cls()
{
this.data.Add(1, "Start");
}
private ListDictionary data = new ListDictionary();
}



2009/11/14追記:
仕様書を見ていてら。
10.11.2 インスタンス変数初期化子
インスタンス コンストラクタにコンストラクタ初期化子が指定されていない場合、または base(...) 形式のコンストラクタ初期化子が指定されている場合は、そのコンストラクタにより、そのクラスで宣言されたインスタンス フィールドの variable-initializer で指定された初期化が暗黙的に実行されます。これは、コンストラクタへのエントリ直後、および直接基本クラスのコンストラクタの暗黙的な呼び出しの前に実行される一連の代入に対応します。変数初期化子は、クラス宣言に出てくる順番どおりに実行されます。

だそうです。
だからいいみたい。

ADO.NETで自動コミットのトランザクションの分離レベルを既定のserializableから変える方法ってどうだったっけ?

なんかあったような気がしますが、忘れてしまいました。

SQLの中で、 select * from table with(nolock)
とwith句を付けたり、

TransactionOptions tso = new TransactionOptions();
tso.IsolationLevel = IsolationLevel.ReadUncommitted;
using (TransactionScope ts = new TransactionScope(TransactionScopeOption.Required,tso))
{
using (DataSet1TableAdapters.ProjectTableAdapter pta = new DataSet1TableAdapters.ProjectTableAdapter())
{
pta.GetData();
}
}

のように、トランザクションを張り、分離レベルを明示すれば書けるのはわかったんだけど。。。

2009年11月3日火曜日

PowerShellその2

Powershellモジュール上で、
PS C:\Windows\system32> ise
とすれば、開発環境(powershell_ise.exe)のウィンドウが立ち上がるので、ここからヘルプをみて、「Windows PowerShellのヘルプ」のファーストステップガイドを読み終わったところ。

でも、やっぱりマイクロソフト。文章を読んでもあまり分らない。

例えば、

技術的に言えば、.NET Framework オブジェクトは、データおよびそのデータに関連付けられている操作から構成される .NET Framework クラスのインスタンスです。言い換えると、オブジェクトとは、プロパティ (特性のようなもの) とメソッド (オブジェクトに対して実行できるアクション) を持つデータ エンティティであると考えることができます。

っていう文。この文で言い換えられた文の方が専門的過ぎて、元の文より意味が分らない。データエンティティって何なのさっ、プロパティやメソッドよりもよっぽど説明がいる言葉じゃないの?なんで言い換えて混乱させるようなことをするのかって。とか、



より重大なエラーの場合はコマンド処理が中止されます。このようなエラーは、"終了エラー" と呼ばれます。終了エラーが発生すると、コマンドの処理は中止されます。たとえば、無効なデータを送信した場合や、コマンドを実行するのに必要なアクセス許可がない場合、Windows PowerShell は、未終了エラーを生成します。


って「無効なデータを送信したとき」は終了エラーなのか未終了エラーなのかわからない




そういえば、ファーストステップガイドにあったけど、起動は、「スタート」→「すべてのプログラム」→「アクセサリ」→「Windows PowerShell」で起動するものみたい。「管理ツール」からいくものは、管理するためのものだから、UACが働くだけだったみたい。

やっぱりパイプでオブジェクトを引き渡すってちょっと違和感がある。慣れていないだけかもしれないけど、処理の主体が分らなくなるような予感がします。

Windows PowerShellを入れてみた

Vista用のPowerShell 2.0が出ていたのでインストールしてみた。

スタート→すべてのプログラム→管理ツール→Windows PowerShell Modules
UACに応答して起動

Windows PowerShell
Copyright (C) 2009 Microsoft Corporation. All rights reserved.

警告: スクリプトの実行がシステムで無効になっているため、ファイル
C:\Windows\system32\WindowsPowerShell\v1.0\Modules\PSDiagnostics\PSDiagnostics.psm1
を読み込めません。詳細については、「get-help about_signing」と入力してヘルプを参照してください。
PS C:\Windows\system32>

初期設定が必要のようです。早速歌い方について、、

PS C:\Windows\system32> get-help about_singing
Get-Help : トピック "about_singing" のヘルプが見つかりません。
発生場所 行:1 文字:9
+ get-help <<<< about_singing
+ CategoryInfo : ResourceUnavailable: (:) [Get-Help]、HelpNotFoundException
+ FullyQualifiedErrorId : HelpNotFound,Microsoft.PowerShell.Commands.GetHelpCommand

PS C:\Windows\system32>


あれ、歌い方を調べろって言われたから調べてみたのに。。。
気を取り直して、、、調べてみると、、こんな感じ。

PS C:\Windows\system32> get-executionpolicy
Restricted
PS C:\Windows\system32> set-executionpolicy remotesigned

実行ポリシーの変更
実行ポリシーは、信頼されていないスクリプトからの保護に役立ちます。実行ポリシーを変更すると、about_Execution_Policies
のヘルプ トピックで説明されているセキュリティ上の危険にさらされる可能性があります。実行ポリシーを変更しますか?
[Y] はい(Y) [N] いいえ(N) [S] 中断(S) [?] ヘルプ (既定値は "Y"):
PS C:\Windows\system32> get-executionpolicy
RemoteSigned
PS C:\Windows\system32>

とりあえず再起動
PS C:\Windows\system32> exit

Windows PowerShell
Copyright (C) 2009 Microsoft Corporation. All rights reserved.

PS C:\Windows\system32>

警告は消えた。


さて、VBやVBSやVBAに乗り遅れたけれど、PowerShellには乗り遅れたくないので2.0からだけどインストールしてみました。

LINQよりは使う頻度が高そうだし、今後もWindowsから離れることは無いと思うので。

2009年11月1日日曜日

映画を見てみようかな

最後に映画館に入ったのはもう5年以上前だけど、久しぶりにこの映画を見てみようかな?
http://black-genkai.asmik-ace.co.jp/

ブラック度検定では、0%だったんだけどね。

DB設計その2

その昔、大量の件数が入っているテーブルデータのものに、別のデータを載せなきゃいけないことがありました。でもそのデータっていうのは元データの生成/消滅タイミングが違っていて、生成確率も違うものだったんです。

例えば架空の図書館システムだと、以下のテーブルに、

所有図書(所有図書ID,状態,.....)

貸出状態をつけちゃったようなもの。

所有図書(所有図書ID,状態,...,貸出日時,貸出会員ID)

実際には、図書の貸出なんて所有図書の1%以下となるので、こう作るべきだったような気がします。

所有図書(所有図書ID,状態,...,)

現在貸出(所有図書ID,貸出日時,会員ID,貸出時状態,貸出時備考)


もし、履歴が必要なら追加でこんなテーブルかな?

貸出履歴(所有図書ID,貸出日時,会員ID,貸出時状態,貸出時備考,返却日時,返却時状態,返却時備考)


。。。やっぱり、自分の所為(せい)にして、記事を書くモチベーションを保てるほど、できてないや。元ねたが他人と絡む技術情報についての記事件数が少なくなっちゃうんだろうなぁ。

2009年10月26日月曜日

ドロップダウンリストのAutoPostBackにした後の対策

ASP.NET Webサイトで、AutoPostBack をtrueにしたドロップダウンリストで、マウスホイールを転がすと、PostBackが大量に走り、ついには同時処理数をオーバーしたというエラーが発生することがある。
この対応として、ドキュメントのReadyStateの値で制御した。
asp:DropDownListタグで、onchangeプロパティ内で、ReadyStateを書いた。そうすると、htmlに展開されたとき、asp:DropDownListは、selectタグに変わって、onchangeで、ポストバックのメソッド呼び出しをしている部分の前の部分にReadyStateに関する処理となるようになった。

TYPOの言い訳

とある掲示板で、TYPOかどうかを確認していたときのもの。
A:「○○は固有名詞ですか?」(○○とは、一字を直しただけで一般名詞の英単語。)
B:「○○というのは、設定ファイルで変えられます。」

...いいかも。今度使ってみよう。
「○○Infomation」っていうテーブルは何?って聞かれたら、「固定の名前です。もうこのテーブルを使っているプログラムはいっぱいあるので直せません」って言って逃げてみよう。(短縮形から戻すときにrを入れ忘れたなんて絶対に言わんで置こう。)

2009年10月25日日曜日

テーブル設計について

DBの話。
かなり昔、テーブル設計を出したときに先輩に、テーブル名が短くて列名が長いんですね、って言われてしまった。
具体的な例を挙げると、私が、Author(AuthorId,AuthorName,...)と作ったらところ、TechnicalAuthor(Id,Name)にするような感じで指摘受けたような感じ。

「そんな構造体みたいな考え方はだめに決まっているじゃん」と思ったけど、議論しても疲れそうなので、そのときは、「そうですね。でもつくっちゃったからこれでいきましょう」といっちゃいました。


構造体と違って、テーブルは、複数のテーブルを連結して利用することをするので、同じ意味のものは同じ列名にして、違う意味のものは違う列名にしなきゃいけないと思っています。
そこで、列名がId。これってBook.IdとAuthor.Idをリンクしてしまうと大変なことになってしまうし、SQLにおける自然結合は、列名の一致が前提条件になっています。この点から問題だと思うのですが。
(自然結合を実装したDBMSを見たことはありませんが、書き方は、「Table1 natural join Table2 on AuthorId」)
また、連結した時点でテーブル名の別名をつけることはよくある話で、テーブル名が分らなくなるとデータの意味が分らないのであれば、あんまりよくないテーブル設計と思うんだけど。
最後に、テーブルを連結して取得した値の列名(Name)に意味ある名前にするために、別名を使うより最初から意味ある名前にしておけばいい話だと思う。


最近も、外部キーは避けたいなんていっている人が居る。外部キー制約が気になるときには外部キー制約を無効にして、終わったら外部キー制約を有効にしたほうが、ずっと便利なのに。(たとえ投入データに制約違反がでて制約の有効化に時間がかかっても不整合データが居残るよりよっぽどまし。)
ま、なかなか思うようにはならないですね。

メモ

http://techbank.jp/Community/blogs/mymio/archive/2008/03/28/381.aspx

ページディレクティブ

前のプロジェクトで作ったASP.NETのページ、ポストバック時に、前回のブラウザ位置を覚えて再現するために、javascriptを使っていたっけ。
でも、ページディレクティブのMaintainScrollPositionOnPostbackを使えばよかったんじゃなかろうか?

2009年10月17日土曜日

Visual SourceSafeとアンチウィルスソフト

アンチウィルスソフトのF-Secureやってくれやがりました。
Visual SourceSafeのAnalyzeをやっている最中、VSSの構成ファイルの一部data\xxxaaaaaっていうファイル
ウィルスの疑いがあるからと検疫してくれました。

なにやってくれちゃってんの!

検疫から復元したんだけど、本当にVSSデータベースの整合性保たれてる?

analyzeでは、「ファイルが見つかりません」を大量に出力されましたが、本当にアンチウィルスソフトのリアルタイムスキャンとVisual SourceSafeの共存って駄目なの?

もうわからないことだらけ!

コンピュータの管理用のバッチ

ドメインに接続していれば、リモートコンピュータにドメインユーザを登録しておけば、コンピュータの管理からせつぞくできるのだけれど、ワークグループでは、リモートコンピュータのコンピュータの管理
に先立って、net useのログインを設定するひつようがある。
で、以下のバッチコマンドを用意し、ダブルクリックする。

echo off
net use \\SVR1 adminpass /user:administrator
compmgmt.msc /computer=SVR1
net use /d \\SVR1

SVR1は、サーバ名、adminpassは、SVR1のadministratorのパスワード。
でも、これ、Windows2003のIISの設定やターミナルサービスマネジャは使えない。

widows2003のadminpackを入れる以外でXPでWindows2003の管理ツールを使う方法が無いかなぁ?

2009年10月15日木曜日

Visual Studio2005のxsdファイルが開かない

Visual Studio2005のプロジェクトから、xsdファイルをデフォルトツールで開こうとすると、いきなりエラーダイアログが。

"次のエラーによりデータセットを読み込めませんでした:
指定された状態で使用するには無効なキーです。"
だって。プロジェクト内のすべてのxsdファイルで同様のエラーが出て開くことができません。
画面には、ビルドすれば直るかもってメッセージが出たのだけれど、やってみても全く直らず。
で、
似たような現象で、以下のがありました。
http://social.msdn.microsoft.com/Forums/ja-JP/vsgeneralja/thread/98a8828c-2f31-437a-b96e-6b0e3e2d14e9

ここでいう、「データを接続できません」っていうエラーでもなければ、発生箇所も違うのだけれど、
駄目もとで、以下を消してみた。
C:\Documents and Settings\<ユーザー名>\Application Data\Microsoft\VisualStudio\8.0\ServerExplorer\DefaultView.SEView

やっぱり開かず。

この後、Visual Studioのサーバエクスプローラペインで、DBに接続しなおすと、なんかうまくいったっぽい動き。

なんなんでしょうね。ま、解決したからいいか。

やっぱり解決していなかった。一日置いたらまた接続できなくなっていた。で、Visual Studio2005を閉じて、例のファイルを消して、Visual Studio2005を立ち上げなおしたらxsdファイルを開けるようになった。

もしかすると、PCを起動するたびに個々を消さないといけないのかもしれない。


原因は秘文?F-Secure?

メールの誤送信

今日、会社でメールの誤送信をしてしまいました。
よりにもよって、ご送信先は、会社の情報セキュリティー部門。
「なんでもないからすぐに消して。お願い。」って出したけど、はぁ~~~。

Visual SourceSafe2005 のanalyzeで、Visual C++ライブラリのエラー

先週末開発VSSにanalyzeをかけて、メンテナンスをしようとしたところ、途中で止まってしまいました。
で、VSSがロックしっぱなしになってしまいました。
なんとなく気になって土曜日に出社して、ロックを解除したので多分誰も気づいていないんだろうけど、あのまま火曜日を迎えたらえらいことになっていたかも。

多分原因はこれ。
http://support.microsoft.com/kb/975026/ja

でも、このパッチ、どこでダウンロードすればよいかわからない。

タイムスタンプを見ても、これより新しいパッチなんだけど。。
http://support.microsoft.com/Default.aspx?id=943847

今週末リトライをしようと思っていたので、明日、マイクロソフトに電話して聞いてみようかな?

2009年10月14日水曜日

SQL ServerのSQLのミスリード

なんか無性に腹が立ったので記事化。
問題は、以下のページ、
http://japan.internet.com/developer/20070206/26.html
のヒント9のもの、SQLで、COALESCEで条件分岐させるもの。
この解説で、
「この方法は、クエリの対象行が数百万に及ぶ場合でも、きわめて高速に動作します。」
なんていっているけど、絶対そんなわけがない。
このSQLは数百万行ある場合には絶対に書いては駄目なSQL。この筆者は一体何を考えているのだろう。
この前のプロジェクトでもCOALECSEを積極的に使おうというものがあって、結局性能が出ずに、結合テスト終盤でSQLを直したという苦い経験があるので、こんな記事を見ると本当にすごく嫌な気分になる。

詳しくは、「インサイドSQLServer 2005 T-SQL編」の8.4.動的SQLの使用方法のP.419に書いてあるんだけど、まとめると以下の2点。

・COALESCEを使うと、引数がNULLのときも値を指定したときも、同じ実行プランとなり、引数がNULLの場合、非常に効率が悪い実行プランで実行してしまう。
・実行プランを組み立てた後、変数に値を代入するので、OPTION RECOMPILEをつけて、毎回実行プランを作り直したとしても、まったく同じ効率の悪いSQLを作ってしまう。

要は、SQL ServerではCOALESCEはアドホックな使い方か、小さなテーブルしか使っては駄目で、プログラム内に書くのは良くないっていうこと。


上のページのSQLであれば、SELECT文のところ面倒でもCOALESCEを使わずに以下の組み立てSQL文にするべきです。

DECLARE @SQL AS NVARCHAR(4000);
SET @SQL =
N'SELECT * FROM CUSTOMERS WHERE 1=1'
+ CASE WHEN @PrmFirstName IS NOT NULL THEN N' AND FirstName = @PrmFirstName' ELSE '' END
+ CASE WHEN @PrmLastName IS NOT NULL THEN N' AND LastName = @PrmLastName' ELSE '' END
+ CASE WHEN @PrmAddress IS NOT NULL THEN N' AND Address = @PrmAddress' ELSE '' END
+ CASE WHEN @PrmCity IS NOT NULL THEN N' AND City = @PrmCity' ELSE '' END
+ CASE WHEN @PrmState IS NOT NULL THEN N' AND State = @PrmState' ELSE '' END
+ CASE WHEN @PrmZip IS NOT NULL THEN N' AND Zip = @PrmZip' ELSE '' END;

EXEC sp_executesql @SQL,
N'@PrmCity AS varchar(50), @PrmState AS varchar(50), @PrmZip AS varchar(50),@PrmFirstName AS varchar(50), @PrmLastName AS varchar(50), @PrmAddress varchar(50)',
@PrmCity = @City, @PrmState = @State, @PrmZip = @Zip, @PrmFirstName = @FirstName, @PrmLastName = @LastName, @PrmAddress = @Address;


前のプロジェクトは、以下のように、さらに悪い部分一致をしていました。こちらも、リリース前に直しが発生しました。

where Address like '%'+@keyword+'%'
で、@keywordの指定が無い場合、@keywordに空文字('')を指定すること


開発効率最優先じゃなくて、効率も気を留めておかないと非機能検証で、すべてがご破算になっちゃうよぉ。

2009年9月23日水曜日

このブログのアクセス解析結果を見てみると

http://remindbook.blogspot.com/2009/05/aspnet.htmlが、このブログで一番多く見られている。
ページビューでは、他の記事をトリプルスコアで圧倒しています。
で、この記事。
平日は平均10ページビュー/日なのに対して、休日は、1ページビュー/日。仕事でしか見てないんだねぇ。
続いて検索ワード。上位3つは、「別ウインドウで開く asp.net」、「asp.net showmodaldialog」、「asp.net ポップアップ モーダル」でした。

asp.netで別ウィンドウを開く要件で皆さん苦労されているようですね。

でも、モーダルダイアログで設計したものの、お客様要求でモードレスダイアログに戻すときに、セッション管理とか、むちゃくちゃ苦労しますので、ブラウザを使うwebアプリケーションの構造上、別ウィンドウ化(ウィンドウポップアップ化)は禁止です、、、、と宣言したいものです。

でも、当然そんなことは言えなくて、お客さんの中には、Webアプリケーションの構造を無視して、処理の途中に警告ダイアログを出して、ユーザ確認を促してって言うことも。。。

Webアプリケーションなら、
1.ボタンを押す→2.ブラウザ側javascriptを動かす→3.サーバ側で処理する→4.ブラウザに返事を返す。
っていう流れなのに、3.の処理の途中でダイアログっていう無茶を言う。
設計者も断ればいいのに、実現できますって答えちゃって。。。
結局、
http://remindbook.blogspot.com/2009/05/aspnet_29.html
のようなことをしなきゃならない。この辺をフレームワーク側で吸収してくれるであろう、VisualStudio2010,.netFramework4.0/asp.net4.0に期待かな。

2009年9月19日土曜日

ソースのヘッダコメントがコピペ

ソースファイルに、ヘッダコメントを残すルールになっていてリリースファイルの際バージョンがチェックされるんだけど、今回これが不正だった。
で、見てみると、
/*
Ver.01.00 20090701 担当者A 新規作成
Ver.01.01 20090805 担当者B ○○対応
Ver.01.02 20090813 私の名前 ○×対応
Ver.01.03 20090908 私の名前 ○×対応
Ver.01.03 20090913 私の名前 ○×対応
*/
で、今回01.03でバージョンがあがっていない変更ソースファイルがあるというエラー。
そもそも、私は、01.02しか触っていないのに・・・。他の担当者がヘッダをコピペして内容を変えいないままとなってる。
名前も修正内容もコピペ。さらに今回のはバージョンも変更していないためエラーになっちゃった。
修正担当者がいなくなったので01.03と(01.04)の修正は何だったのかははわからず、リリースまで時間がなかったためソースコメントはそのままでリリース。

間違ったコメントよりもコメントが少ないほうがまだマシなので、バージョンと日付だけ残してあとは消してくれればよかったのに。。。

2009年9月15日火曜日

リンクサーバのトランザクション設定有効化ってどうやるんだったっけ?

SQL Server 2005でリモートサーバへ、DBリンクを張って、
select * from [リモートサーバ名].[データベース名].[スキーマ名].[テーブル名]
で、アクセスすることはできた。また、insert文

insert [テーブル名]
select * from [リモートサーバ名].[データベース名].[スキーマ名].[テーブル名]
で、リモートサーバにある同名のテーブル名をローカルにコピーすることもできた。

ただ、

begin tran
insert [テーブル名]
select * from [リモートサーバ名].[データベース名].[スキーマ名].[テーブル名]

とやると、エラーとなった。(エラーメッセージは覚えていないが、nliが、とかトランザクションがなんとかとかいうメッセージだった。)
たぶん、リモートサーバのMSDTCの設定の問題だと思うのだけれど、なんだろう?
まさかリンクサーバアクセスではトランザクション処理ができないってことはないとと思うんだけど。

2009年9月13日日曜日

バッチの作り

月末までに引き継がなきゃいけないものの1つに、C#で作られたバッチがあります。(バッチの部分については、実際に携わったのは1日ぐらいですが、他に知っている人がいないもので。。)
で、このバッチプログラムはメイン処理では、テンプレートメソッドっぽい作り、機能群では、ストラテジっぽい作りでした。テンプレートメソッドはis-a,継承とオーバーライドによる処理の変更、ストラテジはhas-a,インターフェースとコンポジションによる処理の変更の意味で話していますが、デザインパターンは使ったことがないのでデザインパターンの理解が間違っているかも。。。
で、後任がオブジェクト指向の言語に慣れていないとちょっと戸惑うかも。
非オブジェクト指向の言語では、静的解析でも、充分に追跡可能だけども、オブジェクト指向言語だと、静的解析では追跡不可能になってしまう。
具体的に言うと、非オブジェクト指向言語では、問題のプログラムの最初から、呼び出し先に移動→呼び出し元に戻るという操作を繰り返していけば、最後まで到達可能だけれど、オブジェクト指向言語で作られている場合、呼び出し先に移動したところでベースクラスのメソッドやインターフェースのメソッド宣言に移動してしまい、ベースクラスの処理の中で(サブクラスでオーバーライドする処理でも)、ベースクラスのメソッドに移動してしまう。静的解析の場合は、これが限界のような気がします。
トラブルが起きたときに、普通にピンポイントで見たり、流れを順に追っていくとつらいことになりそう。
スーパークラスの役割を理解しておかないと何をやっているかさっぱり分からなくなるからね。

だから、デザインパターンは私にとって使うメリットがさっぱり理解できないです。私はアンチオブジェクト指向派です。
さらにこのスーパークラスで、サブクラスから呼び出すメソッドがあったりして、間違ったデザインパターンの作りになっています。
(テンプレートメソッドでは、スーパークラスに処理の流れが書いてあって、処理の流れの一部、他と違う処理をする機能のメソッドだけをサブクラスでオーバーライドする形で作りこむんだけど、今回のはスーパークラスに何故かユーティリティクラスみたいな機能を追加しています。ユーティリティの機能については、継承関係は全くないので、保守時の混乱を取り除くために別途ユーティリティクラスを起こすべきなのに。。。)

・・・わたくし、コピペ万歳の人なので。。。でも、コピペの作法を間違っているプログラムの作りをしてあって、さらにコピペ処理を失敗しているのを見るともっといらいらしてしまうのだけれど。。。


コピペの作法について、
コピペって、極力変更しないで使うもので、コピー元のプログラムの作りに工夫しておかなきゃいけないのに、その辺の工夫を全くしていないと酷いことになってしまう。(さらに変更しなきゃいけなくなった部分で変更していなかったりしてね。)

2009年9月11日金曜日

10月から別プロジェクトへ異動

今日上司に聞いたらそういうことらしい。
てっきり10月以降もいるものだと思っていろいろなものを引き継いだのに、すぐに引き継ぎをしなきゃいけないみたい。
でも、プロパーでそれなりに.netを知っている人がいなくなってしまうんだけどいいのかなぁ。。。

2009年9月8日火曜日

なんてこったい

昨日リリースしたバッチプログラムがおかしくなった。
ログからは、int64型の値をint32型の変数に格納できないとのメッセージが。
で、前回のソースファイルを見比べたら、~Designer.csの型が1箇所おかしい。前回リリースしたバージョンの~Designer.csでは、BigInt,Int64型のパラメータ引渡しになってるのに、今回のものはInt,Int32型になってた。
で、元となる~.xsdファイルでは、パラメタライズドクエリのパラメータの1つが、、、、Int32型になってた。
考えられるのは、~Designer.csファイルを手で修正したこと。

~Desinger.csは、VisualStudio2005が自動生成するファイルで、手で触っちゃいけないファイルなのです。また、~.xsdを更新すると~Designer.csが再生成されるのです。
で、今回~.xsdでSQL文をちょっと直したら、別のSQL文の方でで問題が発覚。しかもビルド時エラーじゃなく実行時エラーなので困ったものです。

あ~あ、前任者のトラップに引っかかっちゃった。

で、今回は、~.xsdの受け渡しパラメータを変更して、対応完了。

でも、なんだったんでしょうねぇ?

2009年9月3日木曜日

久しぶりの更新。今回もSQL_20090906追記

今日はSQLのチューニングに終始してました。
こんなテーブルがあって、
受付(受付ID,受付日時,担当者ID,ステータス),担当者マスタ(担当者ID,担当者名)

受付日時の直近5か月前から未来まで一カ月毎の各ステータスの合計を出すっていうもの。

元のSQLを1時間調べてたんだけど、解釈内容に自信が持てず、全部作り直ししました。
FROM句に副々問い合わせ、SELECT句にも副問い合わせな感じで130行OVERのSQLだったので。。。

で、作り直した最終的なSQLは、こんな感じ(他の条件を全部はしょっているのだけれど。。)。共通テーブル式を使ってて、文字列加工をした列の値を2段目のselect文でgroupbyを掛けているので、たぶん内部では一時的にテーブルを作っているはず。なのでこの部分が改良の余地がありそうだけど。。

WITH 共通テーブル式 (受付月,担当者ID,担当者名,ステータス) AS
(select
substring(convert(varchar,受付日時,111),1,7),
受付.担当者ID,
担当者名,
ステータス
from 受付 left outer join 担当者マスタ
on 受付.担当者ID=担当者マスタ.担当者ID
where 受付日時 >=convert(datetime,substring(convert(varchar,dateadd(m,-5,getdate()),111),1,7)+'/01')
)
select
担当月,
担当者ID,
担当者名,
count(case when ステータス='1' then 担当月 end) as ステータス_1合計,
count(case when ステータス='2' then 担当月 end) as ステータス_2合計,
count(case when ステータス='3' then 担当月 end) as ステータス_3合計,
count(case when ステータス='4' then 担当月 end) as ステータス_4合計
from
共通テーブル式
group by 担当月,担当者ID, 担当者名
order by ....

ま、PIVOTは使っても使わなくても変わりなさそうだし、元は10分かかってたものが2秒になったので、これ以上はいいよね?
こんなものかな。

ついでに思い出せるだけ、昔のSQLを書いてみる。
確か、範囲内の全受付に対して、いちいち担当者毎に月のカウントを取ってdistinctをしていたはずだから、、、

select distinct
substring(convert(varchar,受付_O.受付日時,111),1,7) as 担当月,
受付_O.担当者ID,
担当者名,
(select count(受付_I.受付日時) from 受付 AS 受付_I
where 受付_I.受付日時 >=convert(datetime,substring(convert(varchar,dateadd(m,-5,getdate(),111),1,7)+'/01') and
受付_I.担当者ID=受付_O.担当者ID and 受付_I.ステータス='1' and
受付_I.受付日時 >=convert(datetime,substring(convert(varchar,受付_O.受付日時,111),1,7)+'/01') and
受付_I.受付日時 < convert(datetime,substring(convert(varchar,dateadd(m,1,受付_O.受付日時),111),1,7)+'/01') and
受付_I.ステータス= 受付_O.ステータス
) as ステータス_1合計,
(select count(受付_I.受付日時) from 受付 AS 受付_I
where 受付_I.受付日時 >= convert(datetime,substring(convert(varchar,dateadd(m,-5,getdate(),111),1,7)+'/01') and
受付_I.担当者ID=受付_O.担当者ID and 受付_I.ステータス='2' and
受付_I.受付日時 >=convert(datetime,substring(convert(varchar,受付_O.受付日時,111),1,7)+'/01') and
受付_I.受付日時 < convert(datetime,substring(convert(varchar,dateadd(m,1,受付_O.受付日時),111),1,7)+'/01') and
受付_I.ステータス= 受付_O.ステータス ) as ステータス_2合計,
(select count(受付_I.受付日時) from 受付 AS 受付_I
where 受付_I.受付日時 >= convert(datetime,substring(convert(varchar,dateadd(m,-5,getdate(),111),1,7)+'/01') and
受付_I.担当者ID=受付_O.担当者ID and 受付_I.ステータス='3' and
受付_I.受付日時 >= convert(datetime,substring(convert(varchar,受付_O.受付日時,111),1,7)+'/01') and
受付_I.受付日時 < convert(datetime,substring(convert(varchar,dateadd(m,1,受付_O.受付日時),111),1,7)+'/01') and
受付_I.ステータス= 受付_O.ステータス ) as ステータス_3合計,
(select count(受付_I.受付日時) from 受付 AS 受付_I
where 受付_I.受付日時 >= convert(datetime,substring(convert(varchar,dateadd(m,-5,getdate(),111),1,7)+'/01') and
受付_I.担当者ID=受付_O.担当者ID and 受付_I.ステータス='4' and
受付_I.受付日時 >= convert(datetime,substring(convert(varchar,受付_O.受付日時,111),1,7)+'/01') and
受付_I.受付日時 < convert(datetime,substring(convert(varchar,dateadd(m,1,受付_O.受付日時),111),1,7)+'/01') and
受付_I.ステータス= 受付_O.ステータス ) as ステータス_4合計
from 受付 as 受付_O left outer join 担当者マスタ
on 受付_O.担当者ID = 担当者マスタ.担当者ID and
受付_O.受付日時 >= convert(datetime,substring(convert(varchar,dateadd(m,-5,getdate(),111),1,7)+'/01')


集計するつもりのない、group byとか、distinctとか、union allと動きの異なるunionって、最終手段であって、他の方法が無いかを考えなくちゃ、だね。上の場合は、その月に同じ担当者で、ステータス1のデータが1000件あったら、1000回、同じカウントを行って、出力された同一行をdistinct処理してるってことですよね。なので、残り999回のカウントは不要ってことかい、ってなるはずなんだけど。。。

2009年8月1日土曜日

SQL Server Developpers Edition

アマゾンでみると、2005、2008とも5千円~6千円で買えるのか。。。
Evaluation Editionをダウンロードするのが億劫なので、勉強用に買ってみてもいいのかな?

ExcelのTOCリストの記入

仕事で、
/aaa/bbb/ccc/ddd.txt
/aab/ccd/eef/ggh/iii.txt

というデータをExcelに
/aaa /bbb/ccc ddd.txt
/aab /ccd/eefggh iii.txt

のように張り付ける申請書があった時に、1個ずつ手で打ち込んでいる人がいたので、テキストエディタの正規表現の置換を使って
^(/[^/]*)(/.*)/(.*)$

$1\t$2\t$3
に変換した後にExcelの申請書に張り付ければいいんじゃないの?と提案してみました。
わかってくれるかなぁ?

その後追記、上の人は7月末までの契約でした。何やってんだか。。。


ちなみにこの記事では、TOCとはTable of Contentsの意味でトックリストと読んだときには一覧表という意味で使っています。

SQL Serverのトランザクション分離レベル

ADO.NETで自動トランザクションを使って開発するとデフォルトで、トランザクション分離レベルが'Serializable'となります。
なので、
以下の左のような、タスクごとに一番最後に報告した勤務時間のユーザIDを取得するというSQLを実行した場合、トランザクションを閉じるまで、右のような過去の実績報告を追加するということができません(追加だけでなく、勤務時間表のすべての更新作業(追加、更新、削除)ができないはずです)。



これは、Serializableの動きのためです。
この時点のロック状況は、こんな感じ。


Serializableの特徴であるファントム回避のためキーをレンジでロックしています。
報告日時で適切なインデックスがないためオールレンジロックと同様な結果となります。
なので、insert文のロックは、Waitになっています。


このSQLでは場合は、レンジロックが有効に働くインデックスは掛けるのは不可能だと思います。
なので、Select文を速く終わらせるように工夫するか、nolockを付ける(=ダーティリードを許す)かトランザクション分離レベルをSerializableでなくする(=ファントムを許す)ことをしなければなりません。



これが、Serializableの動きとしては正しいのですが、Oracle開発から来た人にはなかなか分かってもらえない。


ちなみに、分離レベルがRepeatable readであれば右のトランザクション中に左の更新ができました。





オラクル開発から来た人曰く、トランザクション分離レベルをリードコミットスナップショットを使うとよいというが、この変更は、テーブル単位ではなくDB単位に指定するものだし、ADO.NETのデフォルトはSerializableなので、プログラムをいじらなければ何も変わらない気がするし、ADO.NETでREADCOMMITTEDを使っているとDBを変えた瞬間から動きが変わる(ロックしなくなる)のでそれでよいかを検証しなければならないと思う。あんまり都合よくいかないような気がするんだけどなぁ。

2009年7月31日金曜日

パラメタライズドクエリの善し悪し

select文をパラメタライズドクエリで引数を指定して実行するプログラムがあったのだけれど、どうも遅い。プログラム内から動かした場合と、SQLトレースを掛けて抽出したSQL文を手で流した場合で、応答時間が全く違う。

で、いろいろ調べてみたところ、手で流した場合と、プログラムから動かした場合で、SQLの実行計画が違っていた。プログラムから動かすものは、SQLを実行したときに実行計画を作り直さず、前回の実行計画を使うみたい。確かに通常の場合は、実行計画を作る分を端折ることができてヨカッタとなるところだけど、、、
今回パラメタライズドクエリに渡す引数は、開始日、終了日の2つで範囲指定しているのだけれど、範囲が1日と10年では実行計画を変えてほしいのだけれど、変わっていないような動きをする。

結局SQLの末尾にOPTION(RECOMPILE)を付けて毎回SQLをコンパイルさせるようにした。
それと、遅くなった場合に、他の更新処理をブロックしないように、SELECT文で指定されているテーブルに対して、WITH(NOLOCK)ヒントを付けました。

テクシンのメモ

今年の報告論文は、、、内容をVSSと自動ビルドとリリース連動と宣言したので、書くときのいろいろメモ。


本書ではバージョン管理システムを示しプロジェクトでの有用性を示す。

Visual Studio .net親和性

どのようなプロジェクトでも発生するリリースの管理。

他のものとの連携を除き構成管理のみ取り上げる。
プロジェクトのみでなく個人的な運用ツールとしても活用可能。

1.はじめに

2.環境および対象範囲

3.構成管理の概要

4.リリース管理の概要

5.多重管理の回避と改修の競合対応
 ・リリース時期、頻度の違い
 ・要望対応と不具合対応の競合
 ・対象モジュール分け
 
6.リリースミス防止策
 ・リリースミス
   →リリースファイルの更新漏れ
   →ソースファイルのバージョン違い(古すぎる/新しすぎるソース)
 ・防止策
   →ダブルチェック:リリース依頼書(Excel)とソースファイル(VSS)とのマッチングチェック
    VSSの履歴・ソースファイルのバージョン抽出・excelとの申請チェック

7.今後の課題
 改修が競合した時の改修内容の横展。
 同一レーンに入れる粒度。
 レーンを増設、撤去するタイミングの取り決め
 
8.おわりに




あーーーーーどうしても書きたくないーーーーー、構成のイメージが全く思い浮かばないーーーー。めんどいーーーーーーー。

データベースのNULLの扱い

データベースには、決まっていない値という意味でNULLというものがあります。
で、このNULLについては、コッドさん曰く2種類の意味があるとのこと。

例えば、

A.自分の父親の生年月日をNULLとしたとき、
1.自分は孤児で父親がいない場合。
2.父親はいるが、生年月日を思い出せない場合。

B.自分の派遣元会社をNULLとしたとき、
1.そもそも自分は正社員で派遣元会社はナンセンスな場合。
2.派遣元を教える筋合いはないと考えている場合。


でも、現在のRDMSでは2のNULLは扱えるものはありません。すべて1の考え方でしか有りません。
ま、かろうじて安全側に倒れているような気がします。

というのは、例えばA-2.の場合、確かに父親の生年月日があるのでこれを扱おうとした場合、大変なことが起こりそう。こういう状態で、父親の誕生月を月ごとに人数を集計しようとした場合、1件でも「父親の誕生日がわからない」というデータがあると、すべての月の人数集計はUNKNOWNになるべきなのだけれど、そんなことになったら、なにもできなくなりそうだから。

というわけで、2を入れるという考えは全力で避けるようにしなければいけない。さらに1のものも避けることができれば、さらにシンプルに考えられるのだけれど。。。

単体テストの目標数について

単体のテストケースを見たところ、何かテストケースが不足している感じがしたので、プログラマに確認したところ、以下のような考え方でした。。
・テストケースを挙げれば無数に挙げられるが、それだとテストが終わらない
・コードステップ数から割り出した目標テストケース数を目標に設定している

うーん、コードステップ数と関係がない画面の表示位置やタブ順などがテストケースの3割ぐらい占めているのに、、、これを差し引かないで全体のテストケース数と目標テストケース数と比べるのは、何か不思議な感じ。それに目標テストケース数は単なる指標であって、理由があれば大幅に越えても仕方がないものだと思うのだけれど。

単体テストの目的は、機能がうまく動くことの確認だけでは不十分で、単体テストで見つけておくべきバグを見つけ潰しておくことで、確かに後半部分は完ぺきにはできないが、それでも今見ているケースは粗すぎるような気がする。

ま、今言っても仕方がないので、単体テストケースレビュは受けていて、特にテストケース不足は指摘されなかったとことを確認して終了。コードレビュもしているとのことなので、たぶん大丈夫なはず。

カスタムラベルコントロールのカスタムプロパティ

前、プログラマさんにこんなカスタムラベルを作ってと依頼しました。
・表示の際に最大表示文字数が指定できる(0を指定した場合は制限なし)
・サニタイズON/OFFを切り替えられる。
・改行を
タグに変換するかを切り替えられる。

で出来上がったものを見てちょっと驚きました。
1つめのプロパティは、int MaxLength:デフォルトは0
2つめのプロパティは、bool RenderHtmlEncode:デフォルトはfalse
3つめのプロパティは、bool RenderNewLine:デフォルトはfalse

もちろん注目は2つめと3つめのプロパティ。
RenderHtmlEncodeのtrue,falseってのは何?trueの場合どんな動きをするの?
RenderNewLineのtrue/falseって、、、

使う人のことをあまり考えてくれない。ま、使うのはプログラマだから毎回コードを見ればいいんだけど、今後、毎回見なきゃいけないのかなぁ。
プロパティ名を工夫するか、値を列挙型を使って示してほしかったところ。

2009年7月25日土曜日

SQLパフォーマンス問題発生

検索すると、びっくりするぐらい遅い。ado.netのデフォルトタイムアウト30秒が発生しない方が稀。
みてみると、インデックスが効かないことが原因。
つい最近書いた検索条件がダメだったらしい。
あの記事に追記しておきます。

2009年7月4日土曜日

SQL文で失敗 の続き

前の記事のやつは、SQL SERVER 2005では、APPLYを使えばよさそう。
こんなテーブルがあったとして、
(下線は主キー)
TBL_A(ID,NAME,ADDRESS),
TBL_B(ID,START_DATE,JOB)

SELECT A.NAME, B.JOB_NAME
FROM
TBL_A AS A
OUTER APPLY
(SELECT TOP(1) JOB AS JOB_NAME ,ID FROM TBL_B AS BB
WHERE A.ID=BB.ID ORDER BY START_DATE DESC) AS B

CROSS APPLYだと、左入力のみに発生するデータを検索できないためOUTER APPLYです。

SQLの検索条件メモ

検索SQLのWHERE句:

条件1:
検索条件の指定有無@Aが指定されていなかったら(=NULL)検索条件を指定しない。指定されていたらCOL1と比較する。

WHERE
...
(@A IS NULL OR COL1 = @A)


条件2:
検索条件のAND/OR切替@Aが1の場合COL1=@B AND COL2=@C,@Aが2の場合COL1=B OR COL2=@Cを指定する。

WHERE
...
((@A = 1 AND (COL1 = @B AND COL2 = @C)) OR
(@A = 2 AND (COL1 = @B OR COL2 = @C)))

条件3:
ソート順 @Aが1ならCOL1,COL2 @Aが2なら COL3 降順,COL4

ORDER BY
CASE @A WHEN 1 THEN ROW_NUMBER() OVER (ORDER BY COL1,COL2)
WHEN 2 THEN ROW_NUMBER() OVER (ORDER BY COL3 DESC,COL4)
END

こんなのでうまくいくのかな?




ダメだった。条件1をやると、インデックスを使ってくれない。開発者には、「処理中は固定のものだから速度は遅くならないはず」と答えたのにこのありさま。。。あ~あ、またひとつメッキがはがれた感じ。SQL Server2005はそんなに賢くないのかな?

さらにダメダメでした。
全部だ~め。
理由は、非効率なクエリプランができるためです。で、なんでそうなるかというと、、、最近買ったインサイドSQL Server2005に書いてありました。あの本では、ダメな理由と、代替策として、パラメタライズドクエリを組み立てて実行する方法が紹介されていました。

あ~あ。

2009年7月1日水曜日

インサイドSQL Server2005 3冊目

今回のは、T-SQL編。
「T-SQL Querying」と「T-SQL Programing」からちょっとづつ取って1冊にまとめたとのこと。
どうせなら2冊の翻訳本がほしかったところだけど、この本と同時発売の「プログラミングSQL Server2008」があるところをみると、時間制約のため仕方がなかったのかもね。
ちなみに、今回のは6600円でした。ページ単価でも本の値段でも、前2冊のインサイドSQL Server2005と比べて一番高いです。

2009年6月21日日曜日

SQL文で失敗

こんなテーブルがあったとして、(下線は主キー)
TBL_A(ID,NAME,ADDRESS),TBL_B(ID,START_DATE,JOB)

各人の名前と直近の仕事名を記述するSQL文で、プログラム仕様書では以下のように書いてしまいました。

SELECT
A.NAME, B.JOB_NAME
FROM
TAB_A AS A JOIN (SELECT TOP(1) JOB AS JOB_NAME ,ID FROM TAB_B AS BB
WHERE A.ID=BB.ID ORDER BY START_DATE DESC) AS B
ON
A.ID=B.ID

で、プログラマから、できませんよ。という冷たいお言葉。
JOIN句で、同列に配置したテーブルと副問い合わせでは、副問い合わせの中で、利用できないみたい(斜体の部分がバインドできないエラー)。副問い合わせの中でTOP 1 で絞り込んでいるので、副問い合わせの中で条件を入れないといけないのだが、さて困った。

追記:
そうそう、以下のように書けば言いなんていわないでね。TAB_Bで今回引っ張ってきたJOBをキーに別の2テーブルから合計4列抽出するSQLをデフォルメして説明しているのだからね。
SELECT
A.NAME,A.AGE,
(SELECT TOP 1 JOB FROM TAB_B AS B WHERE A.ID=B.ID ORDER BY START_DATE DESC)
FROM TAB_A AS A

今年は論文部門かな?

社内のものです。前回は、プロコンを出しましたが今回は、論文。
ねたは、、、、、構成管理とアプリケーションビルド環境構築の狙い。
理由は、2期連続の構成管理担当になったから。
次点は、Webアプリのメモリ不足不具合の現象と対応。
こちらは私が(種を振りまいただけで)刈り取りしていないので、次点扱いです。

2009年6月6日土曜日

カスタムコントロールにascxファイルを作る理由がわからない。

asp.netのWebControlのカスタマイズで、ユーザコントロールっていうものとカスタムコントロールっていうのがあって(他にもあるかもしれないがとりあえずここではこの2つ)両方を開発することがあったのですが、ソースを見てみると、どちらもascxファイルを作ってあった。ただユーザコントロールは派生元がUserControlなのに対し、カスタムコントロールの派生元がLabelなどに置き換わっていました。
なので、ソリューションエクスプローラにascxが表示されるのだけれど、本来のユーザコントロールと違って、カスタムコントロールの場合はソリューションエクスプローラからドラッグ&ドロップでページに配置する使い方はできない。(UserControlから派生していないので)
で、もうちょっと調べてみたらascxのファイルは使っていないみたい。
なんでカスタムコントロールにascxを使ったのかと開発者に聞いてみたら、Webパーツだからとのこと。
使ってもいないファイルなのに(ascxファイルなので使っているaspxと同じように)本番環境に入れてしまうのだけれど、別に有ったからといって問題にならないからいいのかな?
カスタムコントロールのクラスライブラリ化する際にascxのファイルが問題になりそうだけど、今回のものはカスタムコントロールのクラスライブラリ化をしないみたいだし、こちらも問題ないか。

でも、やっぱりなんでascxファイルを作ったか、そのメリットも全然わからない。なんでだろう?

2009年6月5日金曜日

ユーザーコントロールについて

複数のドロップダウンリストがあるユーザコントロールで、親ドロップダウンリスト選択時に子ドロップダウンリストが更新されるユーザコントロールがありました。(ex:県と市町村のドロップダウンがあって、最初は県の一覧のドロップダウンリストのみ選択可能で、県を特定するその県下の市町村がドロップダウンリストにセットされて、市町村ドロップダウンリストが利用可能になるような動きで、県と市町村は親子関係のテーブルでDBに定義されている。)

で、これの親のドロップダウンリストに対するプロパティsetの反映をどうするかで問題になりました。


ASP.NET2.0の動きだと、以下の順番に処理が動きます。

1.aspxページのPage_Load()

2.ascxユーザコントロールのPage_Load()

3.ボタンイベント処理。


DBから読み込み県のドロップダウンリストに県名一覧をセットする処理を、2でにやらせるとなると、非ポストバックのページ読み込み時(aspxのPage_Load())で、県のプロパティセットをしたときには、県のドロップダウンリストがまだできていない状態なので、ドロップダウンのSelectedValueにセットするというのは何かおかしな感じがする。
でも、2.の時点で、県のプロパティの値を見てSelectedValueをセットした場合は、1の処理でプロパティセットをした場合はうまくいくけど、3のボタンイベント処理で、プロパティセットした値は、ドロップダウンリストに反映されなくなる。


で、本当は、1.の前に動くであろう、ユーザコントロール内のドロップダウンリストのInitイベントで、IsPostBackがfalseのときだけ県名のドロップダウンリストに値を詰め込んでおけば、プロパティセット時には、ドロップダウンリストが出来上がっているのでプロパティセット時にドロップダウンリストのSelectedValueに値を詰め込めばよかっただけのような気がします。

つまり、


1.県のドロップダウンリストのInitイベントにてIsPostBackがfalseのときのみ県名一覧をセットする。
2.市のドロップダウンリストのInitイベントでは、選択不可とする。
3.県のプロパティがセットされた場合は、県のドロップダウンリストのSelectedValueをセットし、市のドロップダウンリストは、選択された県の市名一覧をセットする。(県のプロパティに""がセットされた時は、市のドロップダウンリストを空にして選択不可とする。)
4.市のプロパティをセットされた時は、市のドロップダウンリストのSelectedValueをセットする。

でもこの方法はとらなくなりそう。

2009年6月2日火曜日

複数の一行入力テキストボックスと直後のボタン

asp.netの話。
ページに複数の一行入力のテキストボックスとボタンを2つ配置すると。。
テキストボックスの入力中にEnterキーを押すと、テキストボックスの後で一番近い場所に配置したボタンクリックイベント処理が動く。
テキストボックスが1つだけだったときは、page_load処理だけが動くのに、テキストボックスが2つ以上あるとpage_load()とbutton_click()が動いてしまう。
。。。何かの嫌がらせなのだろうか?

2009年5月30日土曜日

asp.net更新ボタン無効化

昨日の続き。昨日はhiddenfieldを使った、なんでもpostback制御だったんだけど、あのやり方だと、更新ボタンを押されたときに破綻しそう。なので、それだけを回避する措置を。
こんな感じの実装にしてみた。

protected void Page_Load(object sender, EventArgs e)
{
if (!IsPostBack)
{
DateTime dt = System.DateTime.Now;
Session["TimeStamp"] = dt;
ViewState["TimeStamp"] = dt;
}
if (IsPostBack)
{
DateTime dt1 = (DateTime)Session["TimeStamp"];
DateTime dt2 = (DateTime)ViewState["TimeStamp"];
if (dt1 != dt2)
{
Button1.Text = "不正";
}
else
{
Button1.Text = "正常";
}
DateTime dt = System.DateTime.Now;
Session["TimeStamp"] = dt;
ViewState["TimeStamp"] = dt;
}
}

やっていることは、セッション(サーバ側)とビューステート(HiddenField制御のようなクライアント側)と両方で同一タイムスタンプを持たせ、ポストバック時に、これが前回の情報と同じことを確認することで更新ボタンを押されたかどうかをチェックしているということです。

更新ボタンを押すということは、前回の送信情報と同じ情報をサーバ側に渡すことです。なので更新ボタンを押したときにサーバに渡す(クライアント側からのビューステートの)タイムスタンプがセッションで保持している情報と食い違うので、上のプログラムではボタン表示名が「不正」となります。なお、サーバとのやり取りが発生するタイミングでタイムスタンプを最新化しているので、サーバに送るデータで、サーバで一回も処理していないものに限り、ボタン名が正常となります。

2009年5月29日金曜日

asp.netスパゲッティー

なんか無茶苦茶な感じがする。
asp.netの開発では、ボタン押下イベントは、処理が終わってからブラウザ側に制御が戻るんだけど、今回のはちょっと特殊な動きをしなければならない。
更新ボタンイベント→DBチェック→処理続行要否確認ダイアログ(はい/いいえ)→「はい」の場合にDB更新
という処理があって、これの実現方法が思いつかなかった。
で、プログラマに確認してみたところ、以下のようにやるとのこと。
1.ボタンイベントの処理では、DBチェックとダイアログ表示までを実施。
2.ダイアログにOKを押した場合、HIDDENタグに状態を記入してsubmitを行う。
3.ページロードのポストバック時の処理で、HIDDENタグの状態によって処理を変更。
というわけで、お化けのような馬鹿でっかいhiddenタグのif文だらけのPage_Load()になりそうだし、[F5]キーインによる、前回のデータ再送信なんかやられた日にはえらいことになる。なんでこんな仕様を許しちゃってるんだろう。


ちなみにプログラムはこんな感じ。

protected void Button2_Click(object sender, EventArgs e)
{
string scr="<script language='javascript'>window.onload = function() { if (window.confirm('メッセージ')) { document.getElementById('HiddenField1').value='1';form1.submit(); } }</script>";
Page.ClientScript.RegisterClientScriptBlock(GetType(), "startup", scr);
}
で、Page_Loadではこんな感じ。
protected void Page_Load(object sender, EventArgs e)
{
if (IsPostBack)
{
if (HiddenField1.Value == "1")
{
HiddenField1.Value = "0";
//DB更新
}
}
}

でもそういやぁ、
...showModalDialog();
location.href=location.href;
というプログラムがあったっけ、これは、ModalDialog処理後に、自ページを再読み込みさせたかったんだけど、hrefを使った画面遷移は、非ポストバック処理だし、ViewStateや当画面で入力した内容が消えてしまいそう。上のプログラムのようなsubmit()処理に変更せねば。。。

2009年5月28日木曜日

画面イメージキャプチャとソース行カウンタ

前は、キャプラとコロ助を使っていたけど、今はcaptstとbluestepcounterを使っています。
なんか、前よりもちょっといい感じがします。

遥か以前はキャプチャ&並べて印刷にWinShot+linarを使っていたなぁ。

ま、ツールはチーム内標準に合わせるだけなので特に思い入れはないですが。。。

2009年5月27日水曜日

Visual Studio のクエリビルダやDataTable構成ウィザードって

Visual StudioでDataSetを作って、データアダプタで、こんなSQLを作ってみたんです。

SELECT TaskId, Name
FROM Task
WHERE (TaskId = (CASE WHEN @A = '1' THEN 10 END))

@Aというパラメータを渡すパラメータクエリ。
でも、クエリビルダでは、変数の型がおかしいといわれ、アダプタ作成完了時には、@Aを宣言してください。という警告メッセージが。'1'と比較しているんだから@Aは文字列型だって認識してくれないものなのだろうか?一応以下のようなどうでもよいものを付けるとテーブルアダプタの[構成]を押してSQLを作り直してもエラーにならないけど、ダミーを入れるとわけがわからなくなる。

WITH [DUMMY] AS (SELECT 1 AS Expr1
WHERE (@A = 'AAA'))
SELECT TaskId, Name
FROM Task
WHERE (TaskId = (CASE WHEN @A = '1' THEN 10 END))

どうしたものかなぁ?

赤間さん本にだまされちゃった

'Microsoft Visual Studio 2005による
Webアプリケーション構築技法'の本について、
http://www.atmarkit.co.jp/fdotnet/bookpreview/vs2005webapp_07/vs2005webapp_07_04.html
で、
「SELECT TOP @top * FROM authors (TOP句はパラメータ化できない)」
とあるので、TOP句はパラメータ化できないよ、と同僚に伝えたら、できるけど、って。
見てみたら、確かに出来てた。
実行するパラメータSQLはこんな感じ。

SP_EXECUTESQL N'SELECT TOP (@A) * FROM TASK ORDER BY TASKID DESC' ,N'@A INT' ,N'3'

たしかに、TOPの後の()を省略するとエラーになるけど()をつけるとエラーにならない。

2009年5月23日土曜日

プログラミングLINQ

本買っちゃいました。今回は、約8000円、(ADO.NETの1部の機能だけなのに)
プログラミングASP.NET3.5も1万円ぐらいとバージョンが上がるたびに分厚く高くなっていきます。
次回のプログラミングADO.NETは、1500ページ1万5000円ぐらいになるのかな?
最新技術を追っかけるのも大変です。
(Visual Studio2010ももうすぐ出るし、.net frameworkも作り直しが行われるみたい。プログラミング.NET Framwowork4.0も出るのかな?)

asp.netのカスタムコントロールをVisual Stuidoのデザイナで編集する方法


流れとしては、
1.ASP.NETサーバーコントロールを含むアセンブリ(exe,dll)を作成する
2.Visual Studioのツールボックスで、右クリック→アイテムの選択で、参照ボタンを押して1.で作成したアセンブリを選択。
→ツールボックスに歯車アイコンでカスタムコントロールが表示される。
3.上の(2.の)操作によって出てきた、歯車アイコンをaspxやascxに張り付ける。

ふつうは、コントロール用のアセンブリと利用するWebプロジェクトはアセンブリを分けますが、一緒でもちょっとは動くみたい。
でも、Webプロジェクトとカスタムコントロールのアセンブリが一緒だと、デザイン時のレンダーとか、プロパティセットが正しく表示できないみたい。
(コントロールの描画エラー-Label4 ハンドルされていない例外が発生しました。'SANITIZE'をプロパティ'DisplayMode'で設定できませんでした。みたいな表示となる。)
アセンブリを分けてれば、こんなエラーは出ないんだけど。。。しょうがないのかなぁ。



ま、しょうがないとして、
今回は、カスタムのLabelコントロールを元にカスタムコントロールを作成します。Labelコントロールに2個プロパティを追加します。
1個は、表示方法(そのまま表示/サニタイズ表示/タグ抜き表示)もう1個は、改行文字変換(改行文字をBR変換するかどうか)です。


カスタムラベルのソースはこんな感じ。
各メタデータの意味は、以下のURLを参考になります。
http://msdn.microsoft.com/ja-jp/library/ms178658(VS.80).aspx


----ここからMyCustomControl.cs------
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Text;
using System.Web;
using System.Web.UI;
using System.Web.UI.WebControls;

namespace WebControlTest.WebParts
{
[DefaultProperty("Text")]
[ToolboxData("<{0}:Label runat=server></{0}:Label>")]
public class Label : System.Web.UI.WebControls.Label
{
/// <summary>
/// 文字列の変換指定列挙子
/// </summary>
public enum TranslateString
{
NONE,
SANITIZE,
DELETE_TAG
}

/// <summary>
/// 改行変換の列挙子
/// </summary>
public enum TranslateNewLine
{
NONE,
BR_TRANSLATION
}

private TranslateString displayMode=TranslateString.NONE;
private TranslateNewLine newLineMode=TranslateNewLine.NONE;

[Bindable(true)]
[Category("Appearance")]
[Description("文字変換")]
[DefaultValue("NONE")]
[Localizable(true)]
public TranslateString DisplayMode
{
get { return displayMode; }
set { displayMode = value; }
}

[Bindable(true)]
[Category("Appearance")]
[Description("改行モード変換")]
[DefaultValue("NONE")]
[Localizable(true)]
public TranslateNewLine NewLineMode
{
get { return newLineMode; }
set { newLineMode = value; }
}

protected override void RenderContents(HtmlTextWriter output)
{
string outputText = Text;
switch (displayMode)
{
case TranslateString.SANITIZE:
outputText = System.Web.HttpUtility.HtmlEncode(outputText);
break;
case TranslateString.DELETE_TAG:
outputText = System.Text.RegularExpressions.Regex.Replace(outputText, "<[^>]*>", "");
break;
default:
break;
}
switch (newLineMode)
{
case TranslateNewLine.BR_TRANSLATION:
outputText = outputText.Replace("\r\n", "<br>").Replace("\r", "<br>").Replace("\n", "<br>");
break;
}
output.Write(outputText);
}
}
}
----ここまでMyCustomControl.cs------

SQL Server2005のインデックスのソート順についての調査


確かSQL SERVER2000から、インデックスが双方向連結リストとなっていたと思うのでこれの調査
まずはデータ作成から。

testデータベースを作って、testtblを作って、1万件データを入れます。
ここまでのsqlはこんな感じ。
create database test
go
use test
create table testtbl
(
pk1 int primary key identity(1,1),
id1 int,
id2 int,
id3 int
)

declare @a as int, @b as int, @c as int, @i as int

set @i=1
while (@i <> 10000)
begin
set @a=rand()*10000
set @b=rand()*10000
set @c=rand()*10000
insert testtbl (id1,id2,id3) values(@a,@b,@c)
set @i=@i+1
end


次に、インデックスを張らずに、以下のSQLを実行し実行プランを確認する。
select * from testtbl order by id1




当然、SORTという処理が入っている。
続いて、以下のSQLにてid1昇順でid2,id3を付加列に先ほどのselect文をカバーするインデックスを作成する。
create index testtbl_id1 on testtbl (id1 asc) include(id2,id3)
インデックスにpk1が入っていないけど、testtblがクラスタ化インデックステーブルのため行ロケータとしてキー値が使われるので今作成したインデックスに必ず含まれているから指定しなくても付加列と同じ振る舞いが期待できるので、クエリをカバーするインデックスといえる。
で、同じSQL実行。
以下が実行プラン。






実行結果は、ソート処理がなくなっている。
続いて、今のインデックスのままソートキーで逆順に表示する以下のSQL文を発行。
select * from testtbl order by id1 desc
以下が実行プラン。




ちゃんとインデックスを使ってsort処理が不要となっている。
これによりインデックスが単方向リストではないことが確認できた。ソート項目が1項目であれば昇順/降順は注意しなくてもよさそうです。
続いて今のインデックスのまま、以下のSQLにて2つの項目でソートしてみます。
select * from testtbl order by id1,id2
以下が実行プラン




インデックスは使っているが、SORT処理が行われている。しかも第1キーではソート済みであるにもかかわらず、コストの95%はソート処理。

次に複数列のインデックスを張ってみる。
create index id1_id2 on testtbl (id1 asc,id2 asc) include (id3)

で先ほどのsqlを実行。




今回は、SORT処理は不要。
同様にid1 desc,id2 descではSORT処理は不要でした。ただし、id1 asc,id2 descやid1 desc,id2 ascでは、インデックスを使うがSORTも発生する。
同様に、id2,id1のソートでもSORTが発生する。

というわけで、インデックスのソート順はソート指定が複数ある場合にソートの向きが異なる場合、意識したほうがよい程度のものでした。

最後に、クエリをカバーしていないインデックスのみの場合、インデックスを使うかテーブルスキャンになるかを確認。
drop index id1_id2 on testtbl
drop index testtbl_id1 on testtbl
create index id1 on testtbl (id1) include (id2)
最後に作成したインデックスは、id3が付加列にないため、以下のクエリをカバーしていないインデックスになります。
select * from testtbl order by id1
実行プランは以下。





クエリをカバーしていないと全権検索ではインデックスを使わないみたい。

asp.netでモーダルウィンドウ



お仕事で、モーダルダイアログを表示する箇所があったので、その開発メモ。


1.ポップアップブロック対応

ieでは、以下のような処理を行うと、「ポップアップはブロックされました。このポップアップまたは追加オプションを表示するにはここをクリックしてください」という表示がされてしまう。

webページのボタン押下

サーバのポストバック処理

サーバのボタンイベント処理

PCへjavascript起動を含めたページ返信

javascriptのwindow.openやshowModalDialogの実行

これは、ユーザ操作ではなく、サーバからページが送られた直後にウィンドウを新たに起こそうとしているため発生するので、ユーザ操作によるウィンドウオープンにすれば回避できそう。


具体的には、以下のようなaタグを作って表示しておくか、

<a href="javascript:window.showModalDialog('ポップアップウィンドウ?次画面への受け渡し用クエリストリング')">ウインドウオープン</a>


ボタンを配置し、Page_Loadで、ボタンのonClickイベントにdialogを仕込み、
protected void Page_Load(object sender, EventArgs e)
{
Button.Attributes["onClick"] = "javascript:window.showModalDialog('ポップアップウィンドウ?次画面への受け渡し用クエリストリング');return false;";
}
これらを押すことにより『ユーザの操作により』ウィンドウを開くようにすればよさそう。
だけど、上のようにすると作りが複雑になること、リンクやボタン押下時に自画面でのポストバック処理やイベント処理が動かないという制限がある。


今回この制限が回避不可能な見込みのためポップアップブロック回避は見送りになりそう。





2.モーダルダイアログを経由する画面遷移
今回、以下の4つのページを使っていろいろ実験をしてみる。(すべてのページにボタンが1つづつある)
FirstPage.aspx:起動時のページ1つボタンが置かれており、ボタン処理は以下の通り。
protected void Button1_Click(object sender, EventArgs e)
{
Page.ClientScript.RegisterClientScriptBlock(GetType(), "CreateModalDialog",
"<script language='javascript'>window.onload=function(){window.showModalDialog('SecondPage.aspx');}"
+ "</script>");
}


SecondPage.aspx :FirstPage.aspxから呼ばれるページ、ボタン処理は以下の通り。
protected void Button1_Click(object sender, EventArgs e)
{
Response.Redirect("ThirdPage.aspx");
}

ThirdPage.aspx:SecondPage.aspxから呼ばれるページ。ボタン処理は以下の通り。
protected void Button1_Click(object sender, EventArgs e)
{
Page.ClientScript.RegisterClientScriptBlock(GetType(), "CreateModalDialog",
"<script language='javascript'>window.onload=function(){window.showModalDialog('ForthPage.aspx');}"
+ "</script>");
}

ForthPage.aspx:ThirdPage.aspxから呼ばれるページ。ボタン処理は以下の通り。
protected void Button1_Click(object sender, EventArgs e)
{

}

これで、画面遷移をしてみると、「ポップアップブロック」以外に以下の問題が発生した。
・FirstPage.aspx→SecondPage.aspx→SecondPage.aspxを×で閉じる→FirstPage.aspx→SecondPage.aspxでSecondPage.aspxのPage_Load()が呼ばれないでSecondPage.aspxが表示される。(キャッシュの影響か?)
・ThirdPage.aspxが(画面遷移ではなく)新ウィンドウで表示される。

上の2点の対応で、SecondPage.aspxのヘッダタグに以下の記述を追加。
<meta http-equiv="pragma" content="no-cache" />
<base target="_self" />
一応こうすることで、SecondPage.aspxの2回目の呼び出しもPage_Load()が動いてくれたし、ThirdPage.aspxも自ウィンドウで表示された。

ただ、ThirdPage.aspxからForthPage.aspxに遷移するとき、ThirdPage.aspxが別のウィンドウで開いてしまう。
このため、ThirdPage.aspxにも、<base target="_self" />をつけてみたところ、今度は、ForthPage.aspxが開く前に、「オブジェクトでサポートされていないプロパティまたはメソッドです」のメッセージが。。。
showModalDialog()中に、「ポップアップブロック」が走るとエラーとなるみたい。
ThirdPage.aspxで、ボタンの2個目を追加して、aspx.csに以下の通りにすると、エラーなく、showDialogが2重で意図通りの動きになった。
protected void Page_Load(object sender, EventArgs e)
{
Button2.Attributes["onClick"] = "javascript:window.showModalDialog('ForthPage.aspx');return false;";
}


というわけで、showModalDialogのまとめ。

・showModalDialogで表示する画面とModalDialogウィンドウで遷移する画面すべてのヘッダタグ内に、<base target="_self" />を入れる必要がある。
・showModalDialogで表示する画面は、キャッシュされないように、ヘッダタグ内に、<meta http-equiv="pragma" content="no-cache" />を入れる必要がある。
・window.openと同様に、ユーザ操作なしにウィンドウを開く場合は「ポップアップブロック」が動く。
・showModalDialogの処理から「ポップアップブロック」が動くと、javascriptがエラーとなってしまう。

なので、今回、2重のshowModalDialog()を利用できるかは微妙になっちゃいました。

2009年5月21日木曜日

SQL ServerのDBに入れる空白文字?

テーブルの項目varchar(10)でnull許可の列。
空白を入れて、といわれた場合何を入れればいいんだろう。
感覚でいうと、
string.Empty.(いわゆる'') : 50%
null : 40%
半角スペース1文字:5%
半角スペース10文字:2%
全角スペース5文字:1%
全角スペース1文字:1%
上のすべて不正解:1%
の確立であたるんじゃなかろうか。。
要は、使っているところに確認が必要ってこと。

セッション変数で嵌る。


セッション周りのコードを見ていたら、

1.クラスのインスタンスをセッション変数に格納していた。
2.セッション変数から、インスタンスを取り出し、セッション変数に再格納していた。

こんな感じ

CSession.Book cb1 = new CSession.Book();
cb1.Id=bookId;
cb1.Title=TxtBookTitle.Text;
cb1.Author=TxtAuther.Text;
:
Session["BOOK"]=cb1;


別の処理で、
CSession.Book cs = (CSession.Book)Session["BOOK"];
if(cs!=null)
{
cs.Id="...";
cs.Title="....";
cs.Author="...";
:
if(regFlg)
{
Session["BOOK"] = cs;
}
}

気になった点は、CSession.Bookのクラスがシリアル化可能かということと、
Session["BOOK"] = cs;の行。

ASP.NETの状態管理のサービスを立ち上げて、web.configで、
<sessionState mode="StateServer" />
と記述すると、セッション格納時に例外が発生する。

CSession.Bookのクラス定義の上に[Serializable()]をつけることにより、
例外が発生しなくなったが、問題はSession["BOOK"] = csの行。この行は
意味がないように見える。

直前のif文で条件分岐をしているんだけど、cs自体が参照のためcsのメンバー
に値を代入した時点で、セッションに格納したインスタンスのメンバーの値
も変わっていることをプログラム作成者が認識してるかな?

2009年5月20日水曜日

Application_Errorで嵌る。

今回のはまりはGlobal.asaxのApplication_Error()とエラーページについて、

最初の実装では、
Global.asax.csのApplication_Error()でこんな感じにして、

protected void Application_Error(object sender, EventArgs e)
{
Exception ex = Server.GetLastError();
Session["errormessage"] = ex.Message;
Server.Transfer("Error.aspx");
}

Error.aspx.csでは、こんな感じにしてみた。
protected void Page_Load(object sender, EventArgs e)
{
TextBox1.Text = (string)Session["errormessage"];
}

そうすると、なぜか、エラーページのメッセージにはいつもハンドルされていないエラーの旨のメッセージしかでなかった。
なぜかはわからないが、とりあえず、Server.GetLastError()で取得したエラーについて、innerErrorが存在する場合はinnerErrorを発生した例外として扱ってみた。

で、うまくいくように見えたのだけれど、nopage.aspx(存在しないページ)にアクセスすると、Application_Error()の処理のSessionに値を詰め込むところでエラーが発生した。
多分、aspxページが存在しないとSessionが扱える状態になる前にApplication_Errorが呼ばれたからじゃないかな?
というわけで、Application_Errorを以下のような感じに、
Server.Transfer("Error.aspx?errormessage="+ HttpUtility.UrlEncode(ex.Message));

Error.aspx.csのPage_Loadでは以下のような感じにしてみた。
TextBox1.Text = HttpUtility.UrlDecode( Request["errormessage"]);


そうすると、存在しないページにアクセスしたときも意図通りの動作になったが、、、

次は、@Pageディレクティブで、ValidateRequest="true"のページで、<a>などの入力をしてみると、Error.aspx(このページは@PageディレクティブにValidateRequest="false"としているにもかかわらず)の処理で、Request["errormessage"]のタイミングでも、ValidateRequestの例外が発生する。
多分、Server.Transfer()で行っているので、Error.aspxの処理の前の前段パイプライン処理が走っていないから、Error.aspxのページディレクティブを参照していなくて、元ページのValidateRequestの値が引き続き使われているからだろうなぁと想像しますが、かといってResponse.Redirect()にして、URLをさらすわけにもいかず、、、結局web.configでValidateRequest="false"にしてしまって様子見。。。はぁ。

2009年5月10日日曜日

MessageBoxのアイコン

MessageBoxのアイコンの指定を調べるために、こんなサンプルを作ってみました。
(ヘルプは、今回は使わないから調べませんでした。)
で、WindowsVistaHomePremiumで動かしてみてわかったこと。

・MessageBoxで×ボタンが表示される場合は、以下のボタン表示のときで、×ボタンが押されたら、
一番最後のボタンが押されたものと同じ。
OK
OKCancel
YesNoCancel

・×ボタンが表示されているときに限りEscキーで戻ることができ、その場合、戻り値が×ボタンを押したときと同じ。

・オプション指定が、DefaultDesktopOnlyやServiceNotificationの場合、MessageBox終了後、呼び出し元のウィンドウにはフォーカスが戻らない。

・アイコン指定は、Error,Hand,Stopは同じ値が振られているため同じ動きをしており、ExclamationとWarningも同じ、InformationとAsteriskも同じ。そもそもMessageBoxIcon.Error.ToString()や、MessageBoxIcon.Stop.ToString()をやっても"Hand"が返ってくる。(MessageBoxIcon.Exclamation.ToString()は"Warning",MessageBox.Information.ToString()は"Asterisk")

・(使うことはないが)オプション指定が、RtlReadingの場合、以下のように表示がわけがわからない。
 1.タイトルは右寄せ、表示文字はそのままの順(逆順に表示されることはない。)
 2.ウィンドウ内のアイコンは、(通常の左ではなく)右に表示される。
 3.ウィンドウ内の文字は、左寄せで表示される。表示文字はそのままの順。
 4.ボタンは、順序が逆転し()の変化が微妙でYesNoCancelを選んだ場合は以下のようになる。
   4-1:通常時:はい(Y),いいえ(N),キャンセル
   4.2:RtlReading選択時:キャンセル,(Nいいえ(,(Yはい(
 5.×ボタンが左に来ている。
ServiceNotificationを選んだ場合、


RtlReadingを選んだ場合、


なおサンプルに使ったデザインとプログラムは、

デザインはこんな感じ。



プログラムはこんな感じ。
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Text;
using System.Windows.Forms;

namespace WindowsFormsApplication1
{
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();


foreach (MessageBoxButtons b in Enum.GetValues(typeof(MessageBoxButtons))) listBox1.Items.Add(b);
foreach (string s in Enum.GetNames(typeof(MessageBoxIcon))) listBox2.Items.Add(s);
foreach (MessageBoxOptions o in Enum.GetValues(typeof(MessageBoxOptions))) listBox3.Items.Add(o);

listBox1.SelectedIndex = 0;
listBox2.SelectedIndex = 0;
listBox3.SelectedIndex = 0;
}

private void button1_Click(object sender, EventArgs e)
{


MessageBoxButtons b = ((MessageBoxButtons)listBox1.SelectedItem);
MessageBoxIcon i = ((MessageBoxIcon)new EnumConverter(typeof(MessageBoxIcon)).ConvertFromString(listBox2.SelectedItem.ToString()));
MessageBoxOptions o = ((MessageBoxOptions)listBox3.SelectedItem);

label1.Text = MessageBox.Show(
b.ToString()+Environment.NewLine+i.ToString()+Environment.NewLine+o.ToString(),
"MessageBoxテスト",b,i,MessageBoxDefaultButton.Button1,o).ToString();
}
}
}

C#のbase.base

C#のbase.base....について調べてみました。
結局はできないみたい。
namespace ConsoleApplication3
{
class Program
{
static void Main(string[] args)
{
D d = new D();
C c = new C();
B b = c;
A a = c;

string s01 = a.f(); //="C"
string s02 = b.f(); //="C"
string s03 = c.f(); //="C"

string s04 = ((B)b).f(); //="C" キャストを明示してもダメ
string s05 = ((A)a).f(); //="C" 同上
string s06 = (c as B).f(); //="C" このキャストもダメ
string s07 = d.f(); //="C" base.f()はできるけどbase.base.f()は書けない

D1 d1 = new D1();
C1 c1 = new C1();
B1 b1 = c1;
a = c1;
string s11 = a.f(); //=A" この時点で期待外れ
string s12 = b1.f();//="B1" 以下はどうでもいいや。
string s13 = c1.f();//="C1"
string s14 = ((B1)a).f();//="B1"
string s15 = ((C1)a).f();//="C1"
string s16 = ((A)b1).f();//="A"
string s17 = d1.f(); //="C1"

C21 c21 = new C21();
C22 c22 = new C22();

string s21 = c21.f(); //="A2" 期待どおりだがbaseクラス作成時にはをこんな風には作らない...
string s22 = c22.f(); //="B2"

a = c;
b = c;
string s31 = a.g();//="C"拡張メソッド内ではbaseは使えない...
string s32 = b.g();//同上

//型変換エラーが発生するためコメントアウト
//A aa = ((A)Convert.ChangeType(c, Type.GetType("ConsoleApplication3.A")));

string[] strl = { "aa", "bb" };
}

}

//オーバーライド
class A { public virtual string f() { return "A"; } }
class B : A { public override string f() { return "B"; } }
class C : B { public override string f() { return "C"; } }
class D : C { public override string f() { return base.f(); } }

//ある意味オーバーロード?
class B1 : A { public new string f() { return "B1"; } }
class C1 : B1 { public new string f() { return "C1"; } }
class D1 : C1 { public new string f() { return base.f(); } }

//ベースの呼び出しを継承クラスから呼び出せるようにしたもの
//ただA2を作る時点でこんなつくりにすることは考えられないなあ
class A2
{
public virtual string f() { return A2f(); }
protected string A2f() { return "A2"; }
}
class B2 : A2 { public override string f() { return "B2"; } }
class C21 : B2 { public override string f() { return A2f(); } }
class C22 : B2 { public override string f() { return base.f(); } }

//C#3.0からの拡張メソッド g
internal static class ClassUtility { internal static string g(this A a) { return a.f(); } }

}

久しぶりの.netメモ form開発の取り決め

今回windowsformを使った開発がありました。
んで、そのときのメモ。
windowsアプリケーションでは、1画面ごと、Formを使って作るみたい。
一番最初に、
program.csのMain()からApplication.Run(new Form1())が呼ばれて、Form1の処理が始まる。

Mainで、
Application.ThreadException += new System.Threading.ThreadExceptionEventHandler( f);
:

public static void f(object sender, System.Threading.ThreadExceptionEventArgs e)
{
}
で、asp.netのglobal.asaxのerrと同じようなことができる。
ただ、このイベントは、Thread中の例外を捕捉するものなので、Form1のコンストラクタ処理中に発生したの例外は(Run評価前でスレッド開始前なので)これでは取れないので、try/catchしないといけない。

次画面遷移については、親画面(Form1)で、子画面(Form2)をnew→form2.Ownerにthis(つまりform1自身)をセット→form2の引継ぎプロパティをセット→、form2.Show(),form1.Hide()を行って次画面遷移。ダイアログ表示は、form2.ShowDialog()で、戻り値によるダイアログ処理結果取得。

情報共有については、static変数(Application共通の場合のみ)
親画面(Form1)から子画面(Form2)への情報受け渡しは、
1.Owner.親画面のプロパティ
2.親画面でセットされた自画面のプロパティ
3.子画面のコンストラクタを呼ばれたときの引数
このうち1,2は、コンストラクタ生成→子画面のプロパティセット(Ownerプロパティ含む)→Showなので、子画面のコンストラクタやloadでは親画面のプロパティセット前のためプロパティアクセスは不可。
でも、いまのコーディングルール上は、Showをする場合は、2.の子画面側で、自分の画面のプロパティを参照することと、ShowDialogは親画面(Owner)のプロパティを参照することとなっている。

子画面→親画面への引渡し方法は、
1.子画面の処理中に行う親画面へのプロパティセット、
2.ShowDialog()の戻り値(DialogResult)

で、Formの削除、生成のタイミングは、
次画面へ遷移する場合、親画面側で、newすることとなっている。
戻る場合は、子画面側から親画面のShowを行い、自画面をdisposeするとなっている。(親画面側からしかdisposeが利かないような気がするが。。)